@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
@@ -1,10 +1,10 @@
1
+ import { text } from "./messages.js";
1
2
  import { DataRow } from "./grid-row.js";
2
3
  import { GripVertical } from "lucide-react";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  import { DndContext, KeyboardSensor, MouseSensor, TouchSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
5
6
  import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
6
7
  //#region src/data-grid/row-reorder.tsx
7
- const instructions = "To move a row, press Space on its handle, move it with the up and down arrow keys, and press Space again to drop it there. Escape puts it back.";
8
8
  /** The rows of a grid that are moved by hand, `ids` in their order, each loaded or not, so the drop's index is the row's place in the whole list. */
9
9
  const ReorderRows = ({ ids, rowAt, reorder, children }) => {
10
10
  const sensors = useSensors(useSensor(MouseSensor, { activationConstraint: { distance: 4 } }), useSensor(TouchSensor, { activationConstraint: {
@@ -14,13 +14,13 @@ const ReorderRows = ({ ids, rowAt, reorder, children }) => {
14
14
  const place = (id) => ids.indexOf(String(id));
15
15
  const name = (id) => {
16
16
  const row = rowAt(place(id));
17
- return row ? reorder.label(row) : "row";
17
+ return row ? reorder.label(row) : text("row");
18
18
  };
19
19
  const announcements = {
20
- onDragStart: ({ active }) => `Picked up ${name(active.id)}, row ${place(active.id) + 1} of ${ids.length}.`,
21
- onDragOver: ({ active, over }) => over ? `${name(active.id)} is over row ${place(over.id) + 1} of ${ids.length}.` : void 0,
22
- onDragEnd: ({ active, over }) => over ? `${name(active.id)} was dropped at row ${place(over.id) + 1} of ${ids.length}.` : `${name(active.id)} was put back.`,
23
- onDragCancel: ({ active }) => `Moving ${name(active.id)} was cancelled. It is back at row ${place(active.id) + 1}.`
20
+ onDragStart: ({ active }) => text("pickedUp", name(active.id), place(active.id) + 1, ids.length),
21
+ onDragOver: ({ active, over }) => over ? text("isOver", name(active.id), place(over.id) + 1, ids.length) : void 0,
22
+ onDragEnd: ({ active, over }) => over ? text("droppedAt", name(active.id), place(over.id) + 1, ids.length) : text("putBack", name(active.id)),
23
+ onDragCancel: ({ active }) => text("cancelled", name(active.id), place(active.id) + 1)
24
24
  };
25
25
  const end = ({ active, over }) => {
26
26
  if (!over || active.id === over.id) return;
@@ -32,7 +32,7 @@ const ReorderRows = ({ ids, rowAt, reorder, children }) => {
32
32
  onDragEnd: end,
33
33
  accessibility: {
34
34
  announcements,
35
- screenReaderInstructions: { draggable: instructions }
35
+ screenReaderInstructions: { draggable: text("reorderInstructions") }
36
36
  },
37
37
  children: /* @__PURE__ */ jsx(SortableContext, {
38
38
  items: ids,
@@ -47,7 +47,7 @@ const SortableRow = ({ row, label, ...props }) => {
47
47
  const handle = /* @__PURE__ */ jsx("button", {
48
48
  ref: setActivatorNodeRef,
49
49
  type: "button",
50
- "aria-label": `Move ${label}`,
50
+ "aria-label": text("move", label),
51
51
  ...attributes,
52
52
  ...listeners,
53
53
  onClick: (event) => event.stopPropagation(),
@@ -1,3 +1,4 @@
1
+ import { text } from "./messages.js";
1
2
  import { Lock, X } from "lucide-react";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  //#region src/filter-panel/active-filter-chips.tsx
@@ -15,7 +16,7 @@ const ActiveFilterChips = ({ chips, lockedLabels = [], onRemove, onClear, scroll
15
16
  className: chipClass,
16
17
  children: [chip.label, /* @__PURE__ */ jsx("button", {
17
18
  type: "button",
18
- "aria-label": `Remove ${chip.label}`,
19
+ "aria-label": text("remove", chip.label),
19
20
  onClick: () => onRemove(chip.key),
20
21
  className: "rounded-full p-0.5 hover:bg-primary-border/60",
21
22
  children: /* @__PURE__ */ jsx(X, { className: "size-3" })
@@ -25,7 +26,7 @@ const ActiveFilterChips = ({ chips, lockedLabels = [], onRemove, onClear, scroll
25
26
  type: "button",
26
27
  onClick: onClear,
27
28
  className: "ml-1 text-xs font-medium text-muted-foreground hover:text-foreground",
28
- children: "Clear all"
29
+ children: text("clearAll")
29
30
  })
30
31
  ]
31
32
  });
@@ -1,18 +1,19 @@
1
1
  import { cn } from "../lib/cn.js";
2
- import { datePresetLabels, defaultDatePresets } from "./filter-config.js";
2
+ import { text } from "./messages.js";
3
+ import { datePresetLabel, defaultDatePresets } from "./filter-config.js";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  //#region src/filter-panel/date-presets.tsx
5
6
  const DatePresets = ({ value, presets = defaultDatePresets, onChange }) => /* @__PURE__ */ jsx("div", {
6
7
  className: "flex flex-wrap gap-1.5",
7
8
  children: presets.map((id) => {
8
- const label = datePresetLabels[id];
9
+ const label = datePresetLabel(id);
9
10
  return /* @__PURE__ */ jsx("button", {
10
11
  type: "button",
11
12
  disabled: !label,
12
- title: label ? void 0 : `The server offers the preset "${id}", which this UI does not know`,
13
+ title: label ? void 0 : text("unknownPreset", id),
13
14
  onClick: () => onChange(value === id ? void 0 : id),
14
15
  className: cn("h-7 rounded-md border px-2.5 text-xs font-medium transition-colors", !label && "border-dashed text-danger-text", value === id ? "border-primary-border bg-primary-soft text-primary-text" : "border-border-strong bg-background text-muted-foreground hover:bg-muted"),
15
- children: label ?? `${id} (unsupported)`
16
+ children: label ?? text("unsupported", id)
16
17
  }, id);
17
18
  })
18
19
  });
@@ -1,5 +1,6 @@
1
1
  import { cn } from "../lib/cn.js";
2
2
  import { formatInt } from "../format/number.js";
3
+ import { text } from "./messages.js";
3
4
  import { Token } from "../primitives/token.js";
4
5
  import { facetLabel } from "./filter-state.js";
5
6
  import { comboboxOptions, moveActive } from "./combobox-options.js";
@@ -64,7 +65,7 @@ const FacetCombobox = ({ config, selected, onToggle, inline = false }) => {
64
65
  "aria-controls": `${id}-options`,
65
66
  "aria-autocomplete": "list",
66
67
  "aria-activedescendant": options[active] ? `${id}-${active}` : void 0,
67
- placeholder: selected.length > 0 ? "" : `Search ${config.label.toLowerCase()}`,
68
+ placeholder: selected.length > 0 ? "" : text("searchIn", config.label),
68
69
  className: "h-6 min-w-16 flex-1 bg-transparent text-foreground outline-none placeholder:text-faint-foreground",
69
70
  value: query,
70
71
  onChange: (event) => search(event.target.value),
@@ -113,7 +114,7 @@ const FacetCombobox = ({ config, selected, onToggle, inline = false }) => {
113
114
  }, option.value);
114
115
  }), options.length === 0 && /* @__PURE__ */ jsx("li", {
115
116
  className: "px-1.5 py-1 text-xs text-faint-foreground",
116
- children: "No matches"
117
+ children: text("noMatches")
117
118
  })]
118
119
  })]
119
120
  });
@@ -1,6 +1,7 @@
1
1
  import { Input } from "../primitives/input.js";
2
2
  import { formatInt } from "../format/number.js";
3
3
  import { Checkbox } from "../primitives/checkbox.js";
4
+ import { text } from "./messages.js";
4
5
  import { matchesSearch } from "./filter-state.js";
5
6
  import { useState } from "react";
6
7
  import { Search } from "lucide-react";
@@ -15,7 +16,7 @@ const FacetGroup = ({ config, selected, onToggle }) => {
15
16
  config.searchable && /* @__PURE__ */ jsx(Input, {
16
17
  wrapperClassName: "mb-1 h-7",
17
18
  leading: /* @__PURE__ */ jsx(Search, { className: "size-3.5" }),
18
- placeholder: `Search ${config.label.toLowerCase()}`,
19
+ placeholder: text("searchIn", config.label),
19
20
  value: query,
20
21
  onChange: (event) => setQuery(event.target.value)
21
22
  }),
@@ -39,7 +40,7 @@ const FacetGroup = ({ config, selected, onToggle }) => {
39
40
  }, option.value)),
40
41
  options.length === 0 && /* @__PURE__ */ jsx("span", {
41
42
  className: "px-1 py-1 text-xs text-faint-foreground",
42
- children: "No matches"
43
+ children: text("noMatches")
43
44
  })
44
45
  ]
45
46
  });
@@ -1,3 +1,4 @@
1
+ import { text } from "./messages.js";
1
2
  //#region src/filter-panel/filter-config.ts
2
3
  const defaultDatePresets = [
3
4
  "today",
@@ -5,16 +6,18 @@ const defaultDatePresets = [
5
6
  "30d",
6
7
  "year"
7
8
  ];
8
- const datePresetLabels = {
9
- today: "Today",
10
- yesterday: "Yesterday",
11
- "24h": "Last 24 hours",
12
- "7d": "Last 7 days",
13
- "30d": "Last 30 days",
14
- "90d": "Last 90 days",
15
- month: "This month",
16
- quarter: "This quarter",
17
- year: "This year"
18
- };
9
+ const knownPresets = /* @__PURE__ */ new Set([
10
+ "today",
11
+ "yesterday",
12
+ "24h",
13
+ "7d",
14
+ "30d",
15
+ "90d",
16
+ "month",
17
+ "quarter",
18
+ "year"
19
+ ]);
20
+ /** A date preset's label in the screens' language; undefined for one the server offers that this UI does not know. */
21
+ const datePresetLabel = (id) => knownPresets.has(id) ? text(id) : void 0;
19
22
  //#endregion
20
- export { datePresetLabels, defaultDatePresets };
23
+ export { datePresetLabel, defaultDatePresets };
@@ -1,3 +1,4 @@
1
+ import { text } from "./messages.js";
1
2
  import { ActiveFilterChips } from "./active-filter-chips.js";
2
3
  import { DatePresets } from "./date-presets.js";
3
4
  import { activeChips, emptyFilterState, removeChip, setRange, toggleFacetValue, toggleFlag } from "./filter-state.js";
@@ -67,7 +68,7 @@ const FilterPanel = ({ config, layout, value, defaultValue, onChange, lockedLabe
67
68
  className: "flex items-center justify-between pb-1",
68
69
  children: /* @__PURE__ */ jsx("h2", {
69
70
  className: "font-semibold",
70
- children: "Filters"
71
+ children: text("filters")
71
72
  })
72
73
  }),
73
74
  (chips.length > 0 || (lockedLabels?.length ?? 0) > 0) && /* @__PURE__ */ jsx("div", {
@@ -84,11 +85,11 @@ const FilterPanel = ({ config, layout, value, defaultValue, onChange, lockedLabe
84
85
  children: rangeView(range)
85
86
  }, range.id)),
86
87
  dateView && /* @__PURE__ */ jsx(FilterSection, {
87
- title: "Date",
88
+ title: text("date"),
88
89
  children: dateView
89
90
  }),
90
91
  toggleViews.length > 0 && /* @__PURE__ */ jsx(FilterSection, {
91
- title: "Show only",
92
+ title: text("showOnly"),
92
93
  children: toggleViews
93
94
  })
94
95
  ]
@@ -1,4 +1,4 @@
1
- import { datePresetLabels } from "./filter-config.js";
1
+ import { datePresetLabel } from "./filter-config.js";
2
2
  //#region src/filter-panel/filter-state.ts
3
3
  const emptyFilterState = {
4
4
  facets: {},
@@ -46,7 +46,7 @@ const activeChips = (config, state) => {
46
46
  });
47
47
  const dateChips = state.date ? [{
48
48
  key: "date",
49
- label: datePresetLabels[state.date] ?? state.date
49
+ label: datePresetLabel(state.date) ?? state.date
50
50
  }] : [];
51
51
  const toggleChips = state.toggles.map((id) => ({
52
52
  key: `toggle:${id}`,
@@ -0,0 +1,52 @@
1
+ import { defineMessages } from "../locale/messages.js";
2
+ //#region src/filter-panel/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ today: "Today",
6
+ yesterday: "Yesterday",
7
+ "24h": "Last 24 hours",
8
+ "7d": "Last 7 days",
9
+ "30d": "Last 30 days",
10
+ "90d": "Last 90 days",
11
+ month: "This month",
12
+ quarter: "This quarter",
13
+ year: "This year",
14
+ unknownPreset: (id) => `The server offers the preset "${id}", which this UI does not know`,
15
+ unsupported: (id) => `${id} (unsupported)`,
16
+ remove: (label) => `Remove ${label}`,
17
+ clearAll: "Clear all",
18
+ searchIn: (label) => `Search ${label.toLowerCase()}`,
19
+ noMatches: "No matches",
20
+ moreFilters: "More filters",
21
+ filters: "Filters",
22
+ date: "Date",
23
+ showOnly: "Show only",
24
+ minimum: (label) => `${label} minimum`,
25
+ maximum: (label) => `${label} maximum`
26
+ },
27
+ nl: {
28
+ today: "Vandaag",
29
+ yesterday: "Gisteren",
30
+ "24h": "Afgelopen 24 uur",
31
+ "7d": "Afgelopen 7 dagen",
32
+ "30d": "Afgelopen 30 dagen",
33
+ "90d": "Afgelopen 90 dagen",
34
+ month: "Deze maand",
35
+ quarter: "Dit kwartaal",
36
+ year: "Dit jaar",
37
+ unknownPreset: (id) => `De server biedt de periode "${id}", die deze UI niet kent`,
38
+ unsupported: (id) => `${id} (niet ondersteund)`,
39
+ remove: (label) => `${label} verwijderen`,
40
+ clearAll: "Alles wissen",
41
+ searchIn: (label) => `Zoeken in ${label.toLowerCase()}`,
42
+ noMatches: "Niets gevonden",
43
+ moreFilters: "Meer filters",
44
+ filters: "Filters",
45
+ date: "Datum",
46
+ showOnly: "Alleen tonen",
47
+ minimum: (label) => `${label} minimum`,
48
+ maximum: (label) => `${label} maximum`
49
+ }
50
+ });
51
+ //#endregion
52
+ export { text };
@@ -1,4 +1,5 @@
1
1
  import { menuItemClass } from "../primitives/menu-item.js";
2
+ import { text } from "./messages.js";
2
3
  import { FilterMenu } from "./filter-menu.js";
3
4
  import { menuKeyTarget } from "../primitives/menu-keys.js";
4
5
  import { useEffect, useRef } from "react";
@@ -16,7 +17,7 @@ const Items = ({ items, onPick, close }) => {
16
17
  };
17
18
  return /* @__PURE__ */ jsx("ul", {
18
19
  role: "menu",
19
- "aria-label": "More filters",
20
+ "aria-label": text("moreFilters"),
20
21
  onKeyDown,
21
22
  className: "-mx-3 -my-3",
22
23
  children: items.map((item, index) => /* @__PURE__ */ jsx("li", {
@@ -41,7 +42,7 @@ const Items = ({ items, onPick, close }) => {
41
42
  const MoreFiltersMenu = ({ items, onPick }) => {
42
43
  if (items.length === 0) return null;
43
44
  return /* @__PURE__ */ jsx(FilterMenu, {
44
- label: "More filters",
45
+ label: text("moreFilters"),
45
46
  activeCount: 0,
46
47
  children: (close) => /* @__PURE__ */ jsx(Items, {
47
48
  items,
@@ -1,4 +1,5 @@
1
1
  import { cn } from "../lib/cn.js";
2
+ import { text } from "./messages.js";
2
3
  import { useDebounced } from "../lib/use-debounced.js";
3
4
  import { barInRange, fraction, moveThumb } from "./range-math.js";
4
5
  import { useEffect, useState } from "react";
@@ -48,7 +49,7 @@ const RangeFilter = ({ config, value, onChange }) => {
48
49
  children: [0, 1].map((thumb) => /* @__PURE__ */ jsx("input", {
49
50
  type: "range",
50
51
  className: "pb-range",
51
- "aria-label": `${config.label} ${thumb === 0 ? "minimum" : "maximum"}`,
52
+ "aria-label": thumb === 0 ? text("minimum", config.label) : text("maximum", config.label),
52
53
  min,
53
54
  max,
54
55
  step,
@@ -1,3 +1,4 @@
1
+ import { text } from "./messages.js";
1
2
  //#region src/filter-panel/secondary-filters.ts
2
3
  /** The filter a chip belongs to: `facet:<id>`, `range:<id>`, `toggle:<id>` or `date`. A filter is active exactly when it has a chip. */
3
4
  const activeFilterKeys = (chips) => [...new Set(chips.map((chip) => chip.key.split(":").slice(0, 2).join(":")))];
@@ -21,7 +22,7 @@ const barFilters = (config, revealed) => {
21
22
  })),
22
23
  ...config.datePresets ? [{
23
24
  key: "date",
24
- label: "Date",
25
+ label: text("date"),
25
26
  secondary: config.dateSecondary
26
27
  }] : [],
27
28
  ...(config.toggles ?? []).map((toggle) => ({
@@ -0,0 +1,24 @@
1
+ import { defineMessages } from "../locale/messages.js";
2
+ //#region src/forms/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ loading: "Loading",
6
+ notFound: "Not found",
7
+ noForm: "There is no form at this address.",
8
+ setByDatabase: "Set by the database",
9
+ notSent: "Not sent",
10
+ send: "Send",
11
+ checkTooLong: "The check that you are not a bot took too long; send the form again"
12
+ },
13
+ nl: {
14
+ loading: "Laden",
15
+ notFound: "Niet gevonden",
16
+ noForm: "Er is geen formulier op dit adres.",
17
+ setByDatabase: "Ingevuld door de database",
18
+ notSent: "Niet verzonden",
19
+ send: "Verzenden",
20
+ checkTooLong: "De controle of je geen bot bent duurde te lang; verzend het formulier opnieuw"
21
+ }
22
+ });
23
+ //#endregion
24
+ export { text };
@@ -1,3 +1,4 @@
1
+ import { text } from "./messages.js";
1
2
  import { solveChallenge } from "altcha-lib";
2
3
  import { deriveKey } from "altcha-lib/algorithms/web/sha";
3
4
  //#region src/forms/prove-human.ts
@@ -11,7 +12,7 @@ const proveHuman = async (client, name) => {
11
12
  challenge,
12
13
  deriveKey
13
14
  });
14
- if (!solution) throw new Error("The check that you are not a bot took too long; send the form again");
15
+ if (!solution) throw new Error(text("checkTooLong"));
15
16
  return {
16
17
  challenge,
17
18
  solution: {
@@ -5,6 +5,7 @@ import { useClient } from "../data/api-provider.js";
5
5
  import { AuthLayout } from "../auth/auth-layout.js";
6
6
  import { humanize } from "../live/naming.js";
7
7
  import { Field } from "../record-view/field.js";
8
+ import { text } from "../app/meta-gate-messages.js";
8
9
  import { Notice } from "../app/meta-gate.js";
9
10
  import { createBody, hasDatabaseDefault, initialDraft, isRequired } from "../app/create/create-values.js";
10
11
  import { useDocumentTitle } from "../app/document-title.js";
@@ -12,6 +13,7 @@ import { ErrorBanner } from "../app/error-banner.js";
12
13
  import { FieldEditor } from "../app/field-editor.js";
13
14
  import { fieldErrors, fieldMessage } from "../app/field-errors.js";
14
15
  import { useFocusFirstInvalid } from "../app/use-focus-first-invalid.js";
16
+ import { text as text$1 } from "./messages.js";
15
17
  import { proveHuman } from "./prove-human.js";
16
18
  import { useState } from "react";
17
19
  import { isApiError } from "@protobase/client";
@@ -30,16 +32,20 @@ const PublicFormPage = ({ name }) => {
30
32
  });
31
33
  if (form.isPending) return /* @__PURE__ */ jsxs("div", {
32
34
  className: "flex h-full min-h-48 items-center justify-center gap-2 text-muted-foreground",
33
- children: [/* @__PURE__ */ jsx(Spinner, {}), " Loading"]
35
+ children: [
36
+ /* @__PURE__ */ jsx(Spinner, {}),
37
+ " ",
38
+ text$1("loading")
39
+ ]
34
40
  });
35
41
  if (form.error) {
36
42
  if (isApiError(form.error) && form.error.status === 404) return /* @__PURE__ */ jsx(Notice, {
37
- title: "Not found",
38
- children: "There is no form at this address."
43
+ title: text$1("notFound"),
44
+ children: text$1("noForm")
39
45
  });
40
46
  return /* @__PURE__ */ jsx(Notice, {
41
- title: "Cannot reach the API",
42
- children: isApiError(form.error) ? form.error.message : "The server did not answer. Is it running?"
47
+ title: text("cannotReach"),
48
+ children: isApiError(form.error) ? form.error.message : text("noAnswer")
43
49
  });
44
50
  }
45
51
  return /* @__PURE__ */ jsx(LoadedForm, { form: form.data });
@@ -117,14 +123,14 @@ const LoadedForm = ({ form }) => {
117
123
  hints,
118
124
  value: draft[name] ?? (field.type === "boolean" ? false : ""),
119
125
  stored: void 0,
120
- placeholder: hasDatabaseDefault(field) ? "Set by the database" : void 0,
126
+ placeholder: hasDatabaseDefault(field) ? text$1("setByDatabase") : void 0,
121
127
  invalid: Boolean(problem),
122
128
  onChange: (next) => change(name, next)
123
129
  })
124
130
  }, name);
125
131
  }),
126
132
  failure !== void 0 && /* @__PURE__ */ jsx(ErrorBanner, {
127
- title: "Not sent",
133
+ title: text$1("notSent"),
128
134
  error: failure
129
135
  }),
130
136
  /* @__PURE__ */ jsx(Button, {
@@ -132,7 +138,7 @@ const LoadedForm = ({ form }) => {
132
138
  type: "submit",
133
139
  loading: sending,
134
140
  className: "min-h-10",
135
- children: "Send"
141
+ children: text$1("send")
136
142
  })
137
143
  ]
138
144
  })
@@ -1,6 +1,7 @@
1
1
  import { formatTag } from "../locale/active-locale.js";
2
2
  import { viewerZone, zoneLabel } from "../format/zone.js";
3
3
  import { formatDate, formatDateTime, formatRelativeTime } from "../format/date.js";
4
+ import { text } from "./messages.js";
4
5
  import { fileShown } from "./file-values.js";
5
6
  import { enumLabel } from "@protobase/schema";
6
7
  //#region src/live/display-value.ts
@@ -12,7 +13,7 @@ const affixed = (text, hints) => `${hints.prefix ?? ""}${text}${hints.suffix ??
12
13
  const displayValue = (field, value, hints = {}, zone = viewerZone(), now = Date.now()) => {
13
14
  if (value === null || value === void 0 || value === "") return "—";
14
15
  switch (field.type) {
15
- case "boolean": return value ? "Yes" : "No";
16
+ case "boolean": return value ? text("yes") : text("no");
16
17
  case "file": return fileShown(value)?.name ?? "—";
17
18
  case "enum": return enumLabel(String(value), hints.valueLabels);
18
19
  case "timestamp": {
@@ -39,7 +40,7 @@ const valueTitle = (field, value, zone = viewerZone(), hints = {}) => {
39
40
  const ms = Date.parse(value);
40
41
  if (Number.isNaN(ms)) return void 0;
41
42
  if (hints.format === "relative") return formatDateTime(ms, zone);
42
- return `Shown in ${zoneLabel(ms, zone)}`;
43
+ return text("shownIn", zoneLabel(ms, zone));
43
44
  };
44
45
  //#endregion
45
46
  export { displayValue, valueTitle };
@@ -1,3 +1,4 @@
1
+ import { text } from "./messages.js";
1
2
  //#region src/live/file-values.ts
2
3
  const isStoredFile = (value) => typeof value === "object" && value !== null && typeof value.uri === "string";
3
4
  const isFileDraft = (value) => typeof value === "object" && value !== null && typeof value.uploaded?.value === "string";
@@ -10,7 +11,7 @@ const fileShown = (value) => {
10
11
  };
11
12
  if (!isStoredFile(value)) return void 0;
12
13
  if ("missing" in value) return {
13
- name: "File missing",
14
+ name: text("fileMissing"),
14
15
  type: "",
15
16
  missing: true
16
17
  };
@@ -0,0 +1,18 @@
1
+ import { defineMessages } from "../locale/messages.js";
2
+ //#region src/live/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ yes: "Yes",
6
+ no: "No",
7
+ shownIn: (zone) => `Shown in ${zone}`,
8
+ fileMissing: "File missing"
9
+ },
10
+ nl: {
11
+ yes: "Ja",
12
+ no: "Nee",
13
+ shownIn: (zone) => `Getoond in ${zone}`,
14
+ fileMissing: "Bestand ontbreekt"
15
+ }
16
+ });
17
+ //#endregion
18
+ export { text };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.23.9";
2
+ var version = "0.23.11";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };
@@ -1,4 +1,5 @@
1
1
  import { cn } from "../lib/cn.js";
2
+ import { text } from "./messages.js";
2
3
  import "../primitives/menu-item.js";
3
4
  import { usePopoverMenu } from "../primitives/popover-menu.js";
4
5
  import { MoreVertical } from "lucide-react";
@@ -9,7 +10,7 @@ import { createPortal } from "react-dom";
9
10
  * The actions of an action rail as a menu behind a "..." button, as a mobile screen's top bar holds them: each item the
10
11
  * rail's, a link where the rail draws one, disabled with its reason when it cannot run. `className` styles the button.
11
12
  */
12
- const ActionMenu = ({ actions, label = "Actions", className }) => {
13
+ const ActionMenu = ({ actions, label = text("actions"), className }) => {
13
14
  const { open, setOpen, place, button, menu } = usePopoverMenu();
14
15
  if (actions.length === 0) return null;
15
16
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
@@ -1,4 +1,5 @@
1
1
  import { cn } from "../lib/cn.js";
2
+ import { text } from "./messages.js";
2
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
4
  //#region src/record-view/action-rail.tsx
4
5
  const itemClass = "flex min-h-11 items-center gap-2 rounded-md border border-border-strong px-3 text-left hover:bg-surface disabled:cursor-default disabled:opacity-60 md:min-h-0 md:w-full md:items-start md:gap-3 md:rounded-none md:border-0 md:px-4 md:py-3";
@@ -7,7 +8,7 @@ const ActionRail = ({ actions }) => /* @__PURE__ */ jsxs("div", {
7
8
  className: "overflow-hidden rounded-lg border border-border bg-background",
8
9
  children: [/* @__PURE__ */ jsx("h3", {
9
10
  className: "hidden border-b border-border px-4 py-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground md:block",
10
- children: "Actions"
11
+ children: text("actions")
11
12
  }), /* @__PURE__ */ jsx("ul", {
12
13
  className: "flex flex-wrap gap-2 p-2 md:block md:divide-y md:divide-border md:p-0",
13
14
  children: actions.map(({ name, description, icon: Icon, onRun, blocked, running, tone, render }) => {
@@ -1,6 +1,7 @@
1
1
  import { Avatar } from "../primitives/avatar.js";
2
2
  import { FieldControlContext } from "../primitives/field-control.js";
3
3
  import { Button } from "../primitives/button.js";
4
+ import { text } from "./messages.js";
4
5
  import { useId } from "react";
5
6
  import { TriangleAlert } from "lucide-react";
6
7
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -31,7 +32,7 @@ const Field = ({ name, label, required, help, example, error, conflict, wide, ch
31
32
  })]
32
33
  }), conflict && /* @__PURE__ */ jsxs("span", {
33
34
  className: "inline-flex items-center gap-1.5 text-warning-text",
34
- title: `${conflict.user.name} saved a different value`,
35
+ title: text("savedDifferent", conflict.user.name),
35
36
  children: [/* @__PURE__ */ jsx(TriangleAlert, { className: "size-3.5" }), /* @__PURE__ */ jsx(Avatar, {
36
37
  initials: conflict.user.initials,
37
38
  hue: conflict.user.hue,
@@ -57,7 +58,11 @@ const Field = ({ name, label, required, help, example, error, conflict, wide, ch
57
58
  example && /* @__PURE__ */ jsxs("p", {
58
59
  id: `${id}-example`,
59
60
  className: "mt-0.5 text-xs text-faint-foreground",
60
- children: ["Example: ", example]
61
+ children: [
62
+ text("example"),
63
+ " ",
64
+ example
65
+ ]
61
66
  }),
62
67
  error && /* @__PURE__ */ jsx("p", {
63
68
  id: `${id}-error`,
@@ -68,20 +73,20 @@ const Field = ({ name, label, required, help, example, error, conflict, wide, ch
68
73
  className: "mt-2 flex items-center justify-between gap-3 rounded-md border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-warning-text",
69
74
  children: [/* @__PURE__ */ jsxs("span", { children: [
70
75
  /* @__PURE__ */ jsx("strong", { children: conflict.user.name }),
71
- " saved this ",
72
- conflict.ago,
73
- ": ",
76
+ " ",
77
+ text("savedThis", conflict.ago),
78
+ " ",
74
79
  /* @__PURE__ */ jsx("strong", { children: conflict.theirValue })
75
80
  ] }), /* @__PURE__ */ jsxs("span", {
76
81
  className: "flex gap-1.5",
77
82
  children: [/* @__PURE__ */ jsx(Button, {
78
83
  size: "sm",
79
84
  onClick: conflict.onKeepMine,
80
- children: "Keep mine"
85
+ children: text("keepMine")
81
86
  }), /* @__PURE__ */ jsx(Button, {
82
87
  size: "sm",
83
88
  onClick: conflict.onUseTheirs,
84
- children: "Use theirs"
89
+ children: text("useTheirs")
85
90
  })]
86
91
  })]
87
92
  })
@@ -0,0 +1,38 @@
1
+ import { defineMessages } from "../locale/messages.js";
2
+ //#region src/record-view/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ savedDifferent: (name) => `${name} saved a different value`,
6
+ example: "Example:",
7
+ savedThis: (ago) => `saved this ${ago}:`,
8
+ keepMine: "Keep mine",
9
+ useTheirs: "Use theirs",
10
+ actions: "Actions",
11
+ saving: "Saving...",
12
+ savedAgo: (ago) => `Saved ${ago}`,
13
+ notSavedRetrying: "Not saved, retrying",
14
+ lastEditedBy: "Last edited by",
15
+ lastUpdated: (ago) => `Last updated ${ago}`,
16
+ viewing: (count) => `${count} viewing`,
17
+ savingSubject: (subject) => `Saving ${subject}...`,
18
+ subjectSaved: (subject) => `${subject} saved`
19
+ },
20
+ nl: {
21
+ savedDifferent: (name) => `${name} bewaarde een andere waarde`,
22
+ example: "Voorbeeld:",
23
+ savedThis: (ago) => `bewaarde dit ${ago}:`,
24
+ keepMine: "Mijn waarde houden",
25
+ useTheirs: "Hun waarde gebruiken",
26
+ actions: "Acties",
27
+ saving: "Bewaren...",
28
+ savedAgo: (ago) => `Bewaard ${ago}`,
29
+ notSavedRetrying: "Niet bewaard, opnieuw proberen",
30
+ lastEditedBy: "Laatst bewerkt door",
31
+ lastUpdated: (ago) => `Laatst bijgewerkt ${ago}`,
32
+ viewing: (count) => `${count} kijken mee`,
33
+ savingSubject: (subject) => `${subject} bewaren...`,
34
+ subjectSaved: (subject) => `${subject} bewaard`
35
+ }
36
+ });
37
+ //#endregion
38
+ export { text };