nucleus-core-ts 0.9.911 → 0.9.913

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 (28) hide show
  1. package/dist/.build-ok +1 -1
  2. package/dist/fe/components/IntegrationsPage/components/ConnectionExport.js +4 -3
  3. package/dist/fe/components/IntegrationsPage/components/CredentialFields.js +2 -2
  4. package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.js +2 -2
  5. package/dist/fe/components/IntegrationsPage/components/EndpointList.js +1 -1
  6. package/dist/fe/components/IntegrationsPage/components/EndpointSample.js +5 -5
  7. package/dist/fe/components/IntegrationsPage/components/MappingAccountFields.js +11 -10
  8. package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +1 -1
  9. package/dist/fe/components/IntegrationsPage/components/MappingList.js +19 -15
  10. package/dist/fe/components/IntegrationsPage/components/MappingScheduleFields.js +6 -5
  11. package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.js +2 -2
  12. package/dist/fe/components/IntegrationsPage/components/PaginationFields.js +1 -1
  13. package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.js +1 -1
  14. package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.js +2 -2
  15. package/dist/fe/components/IntegrationsPage/components/RunHistory.js +2 -2
  16. package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.js +7 -6
  17. package/dist/fe/components/IntegrationsPage/components/RunPanel.js +1 -1
  18. package/dist/fe/components/IntegrationsPage/components/RunPasswordField.js +2 -1
  19. package/dist/fe/components/IntegrationsPage/components/RunPlanReferences.js +11 -13
  20. package/dist/fe/components/IntegrationsPage/components/RunPlanSuggestions.js +2 -2
  21. package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.js +6 -6
  22. package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.js +2 -1
  23. package/dist/fe/components/IntegrationsPage/components/RunsTab.js +1 -1
  24. package/dist/fe/components/IntegrationsPage/components/credentialSlots.d.ts +15 -2
  25. package/dist/fe/components/IntegrationsPage/components/credentialSlots.js +12 -12
  26. package/dist/fe/components/IntegrationsPage/text/index.d.ts +114 -0
  27. package/dist/fe/components/IntegrationsPage/text/index.js +91 -0
  28. package/package.json +1 -1
package/dist/.build-ok CHANGED
@@ -1 +1 @@
1
- 0.9.911
1
+ 0.9.913
@@ -30,7 +30,8 @@ import { Notice } from './Primitives';
30
30
  * is a confusing half hour otherwise.
31
31
  */ /** A filename somebody can still recognise a week later. */ const fileNameFor = (slug)=>`integration-${slug || 'connection'}.json`;
32
32
  export function ConnectionExport({ actions, source }) {
33
- const t = useLabels().transfer;
33
+ const labels = useLabels();
34
+ const t = labels.transfer;
34
35
  const store = useIntegrationsStore();
35
36
  const [busy, setBusy] = useState(false);
36
37
  const [note, setNote] = useState(null);
@@ -88,12 +89,12 @@ export function ConnectionExport({ actions, source }) {
88
89
  children: [
89
90
  /*#__PURE__*/ _jsx("p", {
90
91
  className: theme.field.hint,
91
- children: "One file holding this connection, its calls and its mappings — enough to rebuild the whole integration in another installation. Bring it in there with Import, on the connections screen."
92
+ children: labels.explain.exportBundleRebuildsIntegration
92
93
  }),
93
94
  /*#__PURE__*/ _jsx(Notice, {
94
95
  title: t.noSecretsTitle,
95
96
  tone: "info",
96
- children: "Tokens, passwords and keys are left out on purpose. Whatever this connection authenticates with has to be entered again wherever the file lands."
97
+ children: labels.explain.exportOmitsCredentials
97
98
  }),
98
99
  note ? /*#__PURE__*/ _jsx("p", {
99
100
  className: theme.field.hint,
@@ -153,12 +153,12 @@ export function CredentialFields({ actions, source, loading = false }) {
153
153
  title: labels.auth.writeOnlyTitle,
154
154
  tone: "info",
155
155
  children: /*#__PURE__*/ _jsx("p", {
156
- children: "A value typed here is stored and from then on only ever sent to the service. It is not returned to this screen, or to any other, no matter who asks — all that comes back is a masked hint. Leave a box empty to keep whatever is already stored."
156
+ children: labels.explain.credentialsWriteOnlyBody
157
157
  })
158
158
  }),
159
159
  slots.length === 0 ? /*#__PURE__*/ _jsx("p", {
160
160
  className: theme.list.empty,
161
- children: "This connection is set to no authentication, so there is no secret to store. Pick an auth type above to see the fields it needs."
161
+ children: labels.explain.credentialsNoAuthNothingToStore
162
162
  }) : null,
163
163
  slots.map((slot)=>/*#__PURE__*/ _jsx(CredentialSlotRow, {
164
164
  disabled: busy,
@@ -19,13 +19,13 @@ export function CredentialSlotRow({ slot, source, value, maskedHint, disabled, o
19
19
  const t = useLabels().errors;
20
20
  const labels = useLabels();
21
21
  const id = `credential-${slot}`;
22
- const label = slotLabel(slot, source);
22
+ const label = slotLabel(slot, source, labels.ui);
23
23
  const stored = maskedHint !== null && maskedHint.length > 0;
24
24
  return /*#__PURE__*/ _jsxs("div", {
25
25
  className: "flex flex-col gap-2",
26
26
  children: [
27
27
  /*#__PURE__*/ _jsx(Field, {
28
- hint: slotHint(slot, source),
28
+ hint: slotHint(slot, source, labels.ui),
29
29
  htmlFor: id,
30
30
  label: label,
31
31
  children: /*#__PURE__*/ _jsx("input", {
@@ -165,7 +165,7 @@ export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloa
165
165
  className: theme.button.ghost,
166
166
  onClick: list.reload,
167
167
  type: "button",
168
- children: "Try again"
168
+ children: labels.ui.tryAgain
169
169
  })
170
170
  ]
171
171
  }) : null,
@@ -132,7 +132,7 @@ export function EndpointSample({ endpoint, actions }) {
132
132
  }),
133
133
  /*#__PURE__*/ _jsx("p", {
134
134
  className: theme.field.hint,
135
- children: "Used for this one request only. During a run each parameter takes its value from the binding configured for it."
135
+ children: labels.endpoints.samplePathValuesHint
136
136
  }),
137
137
  /*#__PURE__*/ _jsx("div", {
138
138
  className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
@@ -192,14 +192,14 @@ export function EndpointSample({ endpoint, actions }) {
192
192
  }) : null,
193
193
  sample.recordCount > 0 ? /*#__PURE__*/ _jsx("p", {
194
194
  className: theme.field.hint,
195
- children: `This is one request. The source answered it with ${sample.recordCount} record(s)${sample.recordCount > sample.sample.length ? `, of which the first ${sample.sample.length} are printed below` : ''} — not the whole table. If the endpoint pages, this is one page of it. Preview on the Run step walks every page and reports the real total.`
195
+ children: `${labels.explain.sampleOneRequestLead}${sample.recordCount}${labels.explain.sampleOneRequestRecords}${sample.recordCount > sample.sample.length ? `${labels.explain.sampleFirstPrintedLead}${sample.sample.length}${labels.explain.sampleFirstPrintedTail}` : ''}${labels.explain.sampleOneRequestTail}`
196
196
  }) : null,
197
197
  sample.recordCount === 0 ? /*#__PURE__*/ _jsxs(Notice, {
198
198
  title: labels.endpoints.sampleNoRecords,
199
199
  tone: "warning",
200
200
  children: [
201
201
  /*#__PURE__*/ _jsx("p", {
202
- children: "These are the top-level keys of the body the source returned. Set the response root path to whichever holds the list."
202
+ children: labels.explain.sampleBodyKeysAreTopLevel
203
203
  }),
204
204
  sample.bodyKeys.length > 0 ? /*#__PURE__*/ _jsx("ul", {
205
205
  className: "mt-2 flex flex-wrap gap-1.5",
@@ -211,7 +211,7 @@ export function EndpointSample({ endpoint, actions }) {
211
211
  }, key))
212
212
  }) : /*#__PURE__*/ _jsx("p", {
213
213
  className: "mt-2",
214
- children: "The body has no top-level keys at all: it is empty, or it is a bare value rather than an object."
214
+ children: labels.explain.sampleBodyHasNoTopLevelKeys
215
215
  }),
216
216
  sample.bodyPreview ? /*#__PURE__*/ _jsx("div", {
217
217
  className: "mt-2",
@@ -255,7 +255,7 @@ export function EndpointSample({ endpoint, actions }) {
255
255
  }) : null,
256
256
  !store.sampling && !sample ? /*#__PURE__*/ _jsx("p", {
257
257
  className: theme.field.hint,
258
- children: "Nothing has been sampled yet. One request is sent, read-only, and the shape it comes back with is what the field mapping is built from."
258
+ children: labels.endpoints.sampleNothingYet
259
259
  }) : null
260
260
  ]
261
261
  })
@@ -23,7 +23,8 @@ import { Field, Notice, Section } from './Primitives';
23
23
  /** Columns that plausibly hold a password, so the guess is a good one. */ const PASSWORD_LIKE = /password|passwd|sifre|şifre|parola/i;
24
24
  /** Columns that plausibly point at the account row. */ const LINK_LIKE = /^(user_?id|account_?id|login_?id)$/i;
25
25
  export function MappingAccountFields({ value, targetFields, targets, disabled, onChange }) {
26
- const t = useLabels().account;
26
+ const labels = useLabels();
27
+ const t = labels.account;
27
28
  const uid = useId();
28
29
  const accountEntity = targets.find((target)=>target.entity === value?.entity);
29
30
  const accountFields = accountEntity?.fields ?? [];
@@ -96,7 +97,7 @@ export function MappingAccountFields({ value, targetFields, targets, disabled, o
96
97
  })
97
98
  }),
98
99
  /*#__PURE__*/ _jsx(Field, {
99
- hint: "The column on the account that identifies it.",
100
+ hint: labels.explain.accountMatchColumnIdentifies,
100
101
  htmlFor: `${uid}-match-column`,
101
102
  label: t.identifiedBy,
102
103
  children: /*#__PURE__*/ _jsxs("select", {
@@ -120,7 +121,7 @@ export function MappingAccountFields({ value, targetFields, targets, disabled, o
120
121
  })
121
122
  }),
122
123
  /*#__PURE__*/ _jsx(Field, {
123
- hint: "Where the run's shared password is written.",
124
+ hint: labels.explain.accountPasswordColumnReceives,
124
125
  htmlFor: `${uid}-password`,
125
126
  label: t.passwordInto,
126
127
  children: /*#__PURE__*/ _jsxs("select", {
@@ -151,7 +152,7 @@ export function MappingAccountFields({ value, targetFields, targets, disabled, o
151
152
  })
152
153
  }),
153
154
  /*#__PURE__*/ _jsx(Field, {
154
- hint: "The imported record's column carrying that same value.",
155
+ hint: labels.explain.accountMatchFromCarriesValue,
155
156
  htmlFor: `${uid}-match-from`,
156
157
  label: t.matchedFrom,
157
158
  children: /*#__PURE__*/ _jsxs("select", {
@@ -175,7 +176,7 @@ export function MappingAccountFields({ value, targetFields, targets, disabled, o
175
176
  })
176
177
  }),
177
178
  /*#__PURE__*/ _jsx(Field, {
178
- hint: "Used only when the column above came back EMPTY, so nobody is left without a login. Name the record's own columns in braces — {firstName}.{sicilNo}@acme.com. Leave it blank to go on skipping those records.",
179
+ hint: labels.explain.accountFallbackWhenIdentifierEmpty,
179
180
  htmlFor: `${uid}-email-format`,
180
181
  label: t.identifierFallback,
181
182
  children: /*#__PURE__*/ _jsx("input", {
@@ -190,7 +191,7 @@ export function MappingAccountFields({ value, targetFields, targets, disabled, o
190
191
  })
191
192
  }),
192
193
  /*#__PURE__*/ _jsx(Field, {
193
- hint: "Filled with the new account's id, so the record and its login know each other.",
194
+ hint: labels.explain.accountIdColumnLinksBack,
194
195
  htmlFor: `${uid}-id-column`,
195
196
  label: t.accountIdInto,
196
197
  children: /*#__PURE__*/ _jsxs("select", {
@@ -239,13 +240,13 @@ export function MappingAccountFields({ value, targetFields, targets, disabled, o
239
240
  ]
240
241
  }),
241
242
  type: "button",
242
- children: "Add a membership"
243
+ children: labels.ui.addMembership
243
244
  })
244
245
  ]
245
246
  }),
246
247
  (value.joins ?? []).length === 0 ? /*#__PURE__*/ _jsx("p", {
247
248
  className: theme.field.hint,
248
- children: "Nothing. An account with no role can sign in and do nothing — every screen it reaches refuses it. A role usually lives in its own table, joined to the account, which is what this adds."
249
+ children: labels.explain.accountNoMembershipsYet
249
250
  }) : null,
250
251
  (value.joins ?? []).map((join, joinIndex)=>{
251
252
  const joinEntity = targets.find((target)=>target.entity === join.entity);
@@ -348,12 +349,12 @@ export function MappingAccountFields({ value, targetFields, targets, disabled, o
348
349
  /*#__PURE__*/ _jsx(Notice, {
349
350
  title: t.sharedPasswordTitle,
350
351
  tone: "info",
351
- children: "Every account this run creates is given the same opening password — asked for once, when the import is started, and stored nowhere. People change it themselves afterwards. A record whose value already has an account is linked to that one instead, so running this import again does not create a second."
352
+ children: labels.explain.accountSharedPasswordPerRun
352
353
  })
353
354
  ]
354
355
  }) : /*#__PURE__*/ _jsx("p", {
355
356
  className: theme.list.empty,
356
- children: "Off. Records are imported without a login — useful for a directory, not for people who have to sign in."
357
+ children: labels.explain.accountCreationOff
357
358
  })
358
359
  });
359
360
  }
@@ -287,7 +287,7 @@ export function MappingEditor({ mapping, targets, targetsLoading, endpoints, fre
287
287
  disabled: busy,
288
288
  onClick: ()=>onRunImport(draft),
289
289
  type: "button",
290
- children: "Preview and import"
290
+ children: labels.ui.previewAndImport
291
291
  }) : null,
292
292
  /*#__PURE__*/ _jsx("button", {
293
293
  className: theme.button.primary,
@@ -6,6 +6,7 @@ import { useIntegrationsStore } from '../store';
6
6
  import { useLabels } from '../text/context';
7
7
  import { integrationsPageTheme } from '../theme';
8
8
  import { Badge, Field, Notice } from './Primitives';
9
+ import { formatMoment } from './runFormat';
9
10
  import { ListSkeleton } from './Skeletons';
10
11
  import { useInfiniteList } from './useInfiniteList';
11
12
  /**
@@ -22,10 +23,18 @@ const WRITE_MODE_TONE = {
22
23
  append: 'neutral',
23
24
  replace: 'danger'
24
25
  };
25
- function describeLastRun(value) {
26
- if (!value) return 'Never run';
26
+ /**
27
+ * When this mapping last ran, in a form that cannot tear on hydration.
28
+ *
29
+ * It used to be `toLocaleString()`, which reads the SERVER's locale and zone on
30
+ * the server and the BROWSER's on the client — two different strings for the
31
+ * same row, and React replacing one with the other after mount. It also meant a
32
+ * Turkish installation was shown 8/2/2026, which in Turkey reads as the 8th of
33
+ * February. `formatMoment` is UTC, fixed-width and labelled as such.
34
+ */ function describeLastRun(value, labels) {
35
+ if (!value) return labels.neverRun;
27
36
  const when = new Date(value);
28
- return Number.isNaN(when.getTime()) ? 'Never run' : `Last run ${when.toLocaleString()}`;
37
+ return Number.isNaN(when.getTime()) ? labels.neverRun : labels.lastRun(formatMoment(value));
29
38
  }
30
39
  export function MappingList({ actions, sourceId, selectedId, reloadToken, onSelect, onRun }) {
31
40
  const t = useLabels().errors;
@@ -150,14 +159,9 @@ export function MappingList({ actions, sourceId, selectedId, reloadToken, onSele
150
159
  children: store.endpointsTotal === 0 ? t.noEndpointsForMapping : labels.mappings.empty
151
160
  }) : /*#__PURE__*/ _jsxs(_Fragment, {
152
161
  children: [
153
- /*#__PURE__*/ _jsxs("p", {
162
+ /*#__PURE__*/ _jsx("p", {
154
163
  className: theme.list.itemMeta,
155
- children: [
156
- list.items.length,
157
- " of ",
158
- list.total,
159
- " shown"
160
- ]
164
+ children: labels.ui.shownOfTotal(list.items.length, list.total)
161
165
  }),
162
166
  /*#__PURE__*/ _jsx("ul", {
163
167
  className: theme.list.wrapper,
@@ -188,7 +192,7 @@ export function MappingList({ actions, sourceId, selectedId, reloadToken, onSele
188
192
  children: [
189
193
  /*#__PURE__*/ _jsx(Badge, {
190
194
  tone: WRITE_MODE_TONE[mapping.writeMode],
191
- children: mapping.writeMode
195
+ children: labels.writeModeFull[mapping.writeMode]
192
196
  }),
193
197
  mapping.enabled === false ? /*#__PURE__*/ _jsx(Badge, {
194
198
  tone: "neutral",
@@ -196,19 +200,19 @@ export function MappingList({ actions, sourceId, selectedId, reloadToken, onSele
196
200
  }) : null,
197
201
  /*#__PURE__*/ _jsx("span", {
198
202
  className: theme.list.itemMeta,
199
- children: describeLastRun(mapping.lastRunAt)
203
+ children: describeLastRun(mapping.lastRunAt, labels.ui)
200
204
  })
201
205
  ]
202
206
  })
203
207
  ]
204
208
  }),
205
209
  onRun ? /*#__PURE__*/ _jsx("button", {
206
- "aria-label": `Import ${mapping.name}`,
210
+ "aria-label": labels.ui.runImportFor(mapping.name),
207
211
  className: cn(theme.button.secondary, 'self-center whitespace-nowrap'),
208
212
  onClick: ()=>onRun(mapping),
209
- title: `Preview and import ${mapping.name}`,
213
+ title: labels.ui.previewAndImportFor(mapping.name),
210
214
  type: "button",
211
- children: "Import"
215
+ children: labels.ui.runImport
212
216
  }) : null
213
217
  ]
214
218
  }, mapping.id))
@@ -46,7 +46,8 @@ import { Field, Notice, Section } from './Primitives';
46
46
  }
47
47
  ];
48
48
  export function MappingScheduleFields({ enabled, cron, timezone, needsPassword, disabled, onChange }) {
49
- const t = useLabels().schedule;
49
+ const labels = useLabels();
50
+ const t = labels.schedule;
50
51
  const presets = presetsOf(t.presets);
51
52
  const uid = useId();
52
53
  const parsed = cron.trim() === '' ? null : parseCron(cron);
@@ -102,7 +103,7 @@ export function MappingScheduleFields({ enabled, cron, timezone, needsPassword,
102
103
  })
103
104
  }),
104
105
  /*#__PURE__*/ _jsx(Field, {
105
- hint: "The company's own clock, so 02:00 stays 02:00 across a daylight-saving change.",
106
+ hint: labels.explain.scheduleTimezoneIsCompanyClock,
106
107
  htmlFor: `${uid}-timezone`,
107
108
  label: t.timezone,
108
109
  children: /*#__PURE__*/ _jsx("input", {
@@ -133,16 +134,16 @@ export function MappingScheduleFields({ enabled, cron, timezone, needsPassword,
133
134
  needsPassword ? /*#__PURE__*/ _jsx(Notice, {
134
135
  title: t.cannotRunTitle,
135
136
  tone: "warning",
136
- children: "It creates a login for every new record, and a login needs a password — which there is nobody to type at that hour. Either start this import by hand, or turn the login off and give people their accounts another way."
137
+ children: labels.explain.scheduleCannotRunUnattended
137
138
  }) : null,
138
139
  /*#__PURE__*/ _jsx("p", {
139
140
  className: theme.field.hint,
140
- children: "Every server running this app checks the schedule, and exactly one of them takes the import — the same lock that stops two people running it at once."
141
+ children: labels.explain.scheduleOneServerTakesTheRun
141
142
  })
142
143
  ]
143
144
  }) : /*#__PURE__*/ _jsx("p", {
144
145
  className: theme.list.empty,
145
- children: "Off. This import runs when somebody asks for it, and at no other time."
146
+ children: labels.explain.scheduleOffUntilAskedFor
146
147
  })
147
148
  });
148
149
  }
@@ -70,7 +70,7 @@ export function MappingWriteModeFields({ idPrefix, writeMode, missingRecordPolic
70
70
  tone: "danger",
71
71
  children: [
72
72
  /*#__PURE__*/ _jsx("p", {
73
- children: "Every existing row is dropped and written again, so row ids change. Foreign keys, saved links, exports, report filters and anything else holding one of those ids breaks, and no amount of re-running repairs it."
73
+ children: labels.explain.writeModeReplaceRebuildsTable
74
74
  }),
75
75
  /*#__PURE__*/ _jsx("p", {
76
76
  className: "mt-1",
@@ -98,7 +98,7 @@ export function MappingWriteModeFields({ idPrefix, writeMode, missingRecordPolic
98
98
  missingRecordPolicy === 'delete' ? /*#__PURE__*/ _jsx(Notice, {
99
99
  title: labels.mappings.deleteWarnTitle,
100
100
  tone: "warning",
101
- children: "A source that answers with a short page — a filter left on, a timeout, an expired token honoured with an empty list — looks exactly like records that went away."
101
+ children: labels.explain.writeModeDeleteTrustsShortPage
102
102
  }) : null,
103
103
  missingRecordPolicy === 'deactivate' ? /*#__PURE__*/ _jsxs("div", {
104
104
  className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
@@ -178,7 +178,7 @@ export function PaginationFields({ idPrefix, value, disabled, onChange }) {
178
178
  /*#__PURE__*/ _jsx(Notice, {
179
179
  title: labels.endpoints.paginationTitle,
180
180
  tone: "warning",
181
- children: "Nothing here is detected for you. Pick the wrong style and the import still succeeds — it reads the first page and never asks for the rest. In a destructive write mode the records it never saw count as missing, so the run deletes or deactivates them."
181
+ children: labels.explain.paginationWrongStyleSilentlySucceeds
182
182
  }),
183
183
  /*#__PURE__*/ _jsx(Field, {
184
184
  hint: labels.endpoints.paginationHint,
@@ -50,7 +50,7 @@ import { Notice } from './Primitives';
50
50
  children: /*#__PURE__*/ _jsx("h4", {
51
51
  className: theme.card.title,
52
52
  id: `${groupId}-title`,
53
- children: "Parameter values"
53
+ children: labels.ui.parameterValues
54
54
  })
55
55
  }),
56
56
  /*#__PURE__*/ _jsxs("div", {
@@ -24,7 +24,7 @@ export function RunConfirmStep({ lines, counted, working, onConfirm, onDismiss }
24
24
  }),
25
25
  counted ? null : /*#__PURE__*/ _jsx("p", {
26
26
  className: "mt-2",
27
- children: "These counts are unknown because nothing was previewed. Run a preview first if you want to know how many rows this removes before it removes them."
27
+ children: labels.explain.runConfirmCountsUnknown
28
28
  }),
29
29
  /*#__PURE__*/ _jsxs("div", {
30
30
  className: "mt-3 flex flex-wrap items-center gap-2",
@@ -41,7 +41,7 @@ export function RunConfirmStep({ lines, counted, working, onConfirm, onDismiss }
41
41
  disabled: working,
42
42
  onClick: onDismiss,
43
43
  type: "button",
44
- children: "Keep them, do not run"
44
+ children: labels.explain.runConfirmKeepThem
45
45
  })
46
46
  ]
47
47
  })
@@ -112,13 +112,13 @@ export function RunHistory({ actions, mappingId, refreshKey, onRefresh }) {
112
112
  className: theme.button.ghost,
113
113
  onClick: list.reload,
114
114
  type: "button",
115
- children: "Try again"
115
+ children: labels.ui.tryAgain
116
116
  })
117
117
  ]
118
118
  }) : null,
119
119
  !firstPagePending && list.items.length === 0 && !list.error ? /*#__PURE__*/ _jsx("p", {
120
120
  className: theme.list.empty,
121
- children: "Nothing has run yet. A preview writes nothing; the first entry appears here once an import is started."
121
+ children: labels.explain.runHistoryNothingRunYet
122
122
  }) : null,
123
123
  list.items.length > 0 ? /*#__PURE__*/ _jsx("ul", {
124
124
  className: theme.list.wrapper,
@@ -16,7 +16,8 @@ import { countersLine, errorText, formatDuration, formatMoment, RUN_STATUS_TONE,
16
16
  * a button that quietly disappears reads as a bug rather than as an expiry.
17
17
  */ const theme = integrationsPageTheme;
18
18
  export function RunHistoryRow({ run, actions, onReverted }) {
19
- const t = useLabels().errors;
19
+ const labels = useLabels();
20
+ const t = labels.errors;
20
21
  const store = useIntegrationsStore();
21
22
  const [reverting, setReverting] = useState(false);
22
23
  const [conflicts, setConflicts] = useState(null);
@@ -72,23 +73,23 @@ export function RunHistoryRow({ run, actions, onReverted }) {
72
73
  children: [
73
74
  /*#__PURE__*/ _jsx(Badge, {
74
75
  tone: RUN_STATUS_TONE[run.status],
75
- children: run.status
76
+ children: labels.runStatusFull[run.status]
76
77
  }),
77
78
  /*#__PURE__*/ _jsx("span", {
78
79
  className: theme.card.title,
79
- children: run.targetEntity ?? 'unknown target'
80
+ children: run.targetEntity ?? labels.badges.unknownTarget
80
81
  }),
81
82
  run.writeMode ? /*#__PURE__*/ _jsx(Badge, {
82
83
  tone: "neutral",
83
- children: run.writeMode
84
+ children: labels.writeModeFull[run.writeMode] ?? run.writeMode
84
85
  }) : null,
85
86
  run.trigger ? /*#__PURE__*/ _jsx(Badge, {
86
87
  tone: "neutral",
87
- children: run.trigger
88
+ children: labels.triggerFull[run.trigger] ?? run.trigger
88
89
  }) : null,
89
90
  mismatch ? /*#__PURE__*/ _jsx(Badge, {
90
91
  tone: "warning",
91
- children: `read ${run.totalFetched} of ${reportedTotal}`
92
+ children: labels.badges.readOf(run.totalFetched ?? 0, reportedTotal ?? 0)
92
93
  }) : null
93
94
  ]
94
95
  }),
@@ -279,7 +279,7 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
279
279
  !(store.planning || plan || activeRunId) ? /*#__PURE__*/ _jsx(Notice, {
280
280
  tone: "info",
281
281
  title: labels.runs.previewExplainTitle,
282
- children: "A preview reads the source without writing anything, and reports how many rows would be created, updated, skipped, and how many rows in the target the source no longer returns."
282
+ children: labels.explain.runPreviewWritesNothing
283
283
  }) : null
284
284
  ]
285
285
  })
@@ -26,6 +26,7 @@ import { Field } from './Primitives';
26
26
  * somebody leaves in place. Editable — an install with a policy types its own.
27
27
  */ export const DEFAULT_SHARED_PASSWORD = 'ChanGeME2026!';
28
28
  export function RunPasswordField({ column, value, disabled, onChange }) {
29
+ const labels = useLabels();
29
30
  const t = useLabels().account;
30
31
  const uid = useId();
31
32
  const [shown, setShown] = useState(false);
@@ -53,7 +54,7 @@ export function RunPasswordField({ column, value, disabled, onChange }) {
53
54
  disabled: disabled,
54
55
  onClick: ()=>setShown((current)=>!current),
55
56
  type: "button",
56
- children: shown ? 'Hide' : 'Show'
57
+ children: shown ? labels.ui.hideSecret : labels.ui.showSecret
57
58
  })
58
59
  ]
59
60
  })
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useLabels } from '../text/context';
3
4
  import { integrationsPageTheme } from '../theme';
4
5
  import { Notice } from './Primitives';
5
6
  /**
@@ -36,6 +37,7 @@ import { Notice } from './Primitives';
36
37
  ].sort((a, b)=>b.records - a.records);
37
38
  }
38
39
  export function RunPlanReferences({ plan, busy, onCreateAndRun, onSkipAndRun }) {
40
+ const labels = useLabels();
39
41
  const missing = missingReferenceValues(plan);
40
42
  const planned = plan.referenceInserts ?? [];
41
43
  if (missing.length === 0 && planned.length === 0) return null;
@@ -43,15 +45,11 @@ export function RunPlanReferences({ plan, busy, onCreateAndRun, onSkipAndRun })
43
45
  className: "flex flex-col gap-3",
44
46
  children: [
45
47
  missing.length > 0 ? /*#__PURE__*/ _jsxs(Notice, {
46
- title: `${missing.length} value(s) this import refers to are not here yet`,
48
+ title: labels.explain.missingReferencesTitle(missing.length),
47
49
  tone: "warning",
48
50
  children: [
49
- /*#__PURE__*/ _jsxs("p", {
50
- children: [
51
- "As it stands the records that carry them are left out — ",
52
- plan.toSkip,
53
- " record(s) in total. Either add the values first, or import without those records."
54
- ]
51
+ /*#__PURE__*/ _jsx("p", {
52
+ children: labels.explain.missingReferencesRecordsLeftOut(plan.toSkip)
55
53
  }),
56
54
  /*#__PURE__*/ _jsx("ul", {
57
55
  className: "mt-2 flex flex-col gap-1",
@@ -61,7 +59,7 @@ export function RunPlanReferences({ plan, busy, onCreateAndRun, onSkipAndRun })
61
59
  /*#__PURE__*/ _jsx("strong", {
62
60
  children: entry.value
63
61
  }),
64
- ` — missing from ${entry.entity}.${entry.entityColumn}, needed by ${entry.records} record(s)`
62
+ labels.explain.missingReferenceRow(`${entry.entity}.${entry.entityColumn}`, entry.records)
65
63
  ]
66
64
  }, entry.key))
67
65
  }),
@@ -73,25 +71,25 @@ export function RunPlanReferences({ plan, busy, onCreateAndRun, onSkipAndRun })
73
71
  disabled: busy,
74
72
  onClick: onCreateAndRun,
75
73
  type: "button",
76
- children: `Create these ${missing.length} value(s) and import everything`
74
+ children: labels.explain.missingReferencesCreateAndRun(missing.length)
77
75
  }),
78
76
  /*#__PURE__*/ _jsx("button", {
79
77
  className: theme.button.secondary,
80
78
  disabled: busy,
81
79
  onClick: onSkipAndRun,
82
80
  type: "button",
83
- children: "Import without those records"
81
+ children: labels.explain.missingReferencesRunWithoutThem
84
82
  })
85
83
  ]
86
84
  })
87
85
  ]
88
86
  }) : null,
89
87
  planned.length > 0 ? /*#__PURE__*/ _jsxs(Notice, {
90
- title: `${planned.length} value(s) will be added first`,
88
+ title: labels.explain.plannedReferencesTitle(planned.length),
91
89
  tone: "info",
92
90
  children: [
93
91
  /*#__PURE__*/ _jsx("p", {
94
- children: "These are created in the same import, before the records that point at them, so nothing is written pointing at nothing. They carry only the value itself — rename them afterwards."
92
+ children: labels.explain.plannedReferencesCreatedBefore
95
93
  }),
96
94
  /*#__PURE__*/ _jsx("ul", {
97
95
  className: "mt-2 flex flex-col gap-1",
@@ -112,7 +110,7 @@ export function RunPlanReferences({ plan, busy, onCreateAndRun, onSkipAndRun })
112
110
  disabled: busy,
113
111
  onClick: onSkipAndRun,
114
112
  type: "button",
115
- children: "Do not create them — leave those records out"
113
+ children: labels.explain.plannedReferencesLeaveRecordsOut
116
114
  })
117
115
  })
118
116
  ]
@@ -81,7 +81,7 @@ export function RunPlanSuggestions({ plan, mapping, onEditMapping }) {
81
81
  className: `${theme.button.secondary} mt-3`,
82
82
  onClick: onEditMapping,
83
83
  type: "button",
84
- children: "Open the mapping"
84
+ children: labels.ui.openTheMapping
85
85
  }) : null
86
86
  ]
87
87
  }) : null,
@@ -115,7 +115,7 @@ export function RunPlanSuggestions({ plan, mapping, onEditMapping }) {
115
115
  className: `${theme.button.secondary} mt-3`,
116
116
  onClick: onEditMapping,
117
117
  type: "button",
118
- children: "Open the mapping"
118
+ children: labels.ui.openTheMapping
119
119
  }) : null
120
120
  ]
121
121
  }, entry.field);
@@ -35,22 +35,22 @@ export function RunPlanSummary({ plan }) {
35
35
  }),
36
36
  /*#__PURE__*/ _jsx(Badge, {
37
37
  tone: "info",
38
- children: plan.writeMode
38
+ children: labels.writeModeFull[plan.writeMode]
39
39
  }),
40
40
  /*#__PURE__*/ _jsx(Badge, {
41
41
  tone: plan.dedupConfigured ? 'neutral' : 'warning',
42
- children: plan.dedupConfigured ? 'dedup key set' : 'no dedup key'
42
+ children: plan.dedupConfigured ? labels.badges.dedupSet : labels.badges.dedupMissing
43
43
  }),
44
44
  /*#__PURE__*/ _jsx(Badge, {
45
45
  tone: "neutral",
46
- children: `missing rows: ${plan.missingPolicy}`
46
+ children: labels.badges.missingRows(labels.missingPolicyFull[plan.missingPolicy])
47
47
  })
48
48
  ]
49
49
  }),
50
50
  countMismatch ? /*#__PURE__*/ _jsx(Notice, {
51
51
  tone: "danger",
52
52
  title: labels.runExtras.countMismatchTitle,
53
- children: `The source reports ${plan.reportedTotal} record(s); ${plan.totalFetched} were actually read. Check the endpoint's pagination profile before running: a wrong profile reads one page, and every record it never saw counts as missing.`
53
+ children: labels.explain.runPlanCountMismatchBody(plan.reportedTotal, plan.totalFetched)
54
54
  }) : null,
55
55
  /*#__PURE__*/ _jsxs("div", {
56
56
  className: theme.stat.grid,
@@ -83,9 +83,9 @@ export function RunPlanSummary({ plan }) {
83
83
  ]
84
84
  }),
85
85
  plan.accountsToCreate > 0 ? /*#__PURE__*/ _jsx(Notice, {
86
- title: `${plan.accountsToCreate} login(s) will be created, with the password typed above`,
86
+ title: labels.explain.runPlanAccountsToCreateTitle(plan.accountsToCreate),
87
87
  tone: "info",
88
- children: "Each new record gets an account in the same import — one password for all of them, which people change themselves afterwards. Anyone whose value already has an account is linked to it instead."
88
+ children: labels.explain.runPlanAccountsSharePassword
89
89
  }) : null,
90
90
  plan.warnings.map((warning, index)=>/*#__PURE__*/ _jsx(Notice, {
91
91
  title: warning,
@@ -10,6 +10,7 @@ import { integrationsPageTheme } from '../theme';
10
10
  * SECOND, separate decision — it never rides along on the first click.
11
11
  */ const theme = integrationsPageTheme;
12
12
  export function RunRevertConflicts({ conflicts, message, working, onOverwrite, onDismiss }) {
13
+ const labels = useLabels();
13
14
  const t = useLabels().errors;
14
15
  return /*#__PURE__*/ _jsxs("section", {
15
16
  className: theme.notice.danger,
@@ -56,7 +57,7 @@ export function RunRevertConflicts({ conflicts, message, working, onOverwrite, o
56
57
  disabled: working,
57
58
  onClick: onDismiss,
58
59
  type: "button",
59
- children: "Leave those rows alone"
60
+ children: labels.ui.leaveRowsAlone
60
61
  })
61
62
  ]
62
63
  })
@@ -37,7 +37,7 @@ export function RunsTab({ actions, onSubscribeProgress }) {
37
37
  className: `${theme.button.secondary} mt-3`,
38
38
  onClick: ()=>store.setTab('mappings'),
39
39
  type: "button",
40
- children: "Go to the mappings"
40
+ children: labels.ui.goToMappings
41
41
  })
42
42
  ]
43
43
  }),
@@ -30,5 +30,18 @@ export declare function hintColumnsFor(hints: Partial<Record<CredentialSlot, str
30
30
  export declare function hintFromResult(result: Record<string, string | null>, slot: CredentialSlot): string | null | undefined;
31
31
  /** Which secrets the chosen auth type actually reads. */
32
32
  export declare function slotsForSource(source: IntegrationSource): CredentialSlot[];
33
- export declare function slotLabel(slot: CredentialSlot, source: IntegrationSource): string;
34
- export declare function slotHint(slot: CredentialSlot, source: IntegrationSource): string;
33
+ /** The words come from the installation; which one applies is decided here. */
34
+ export type SlotWords = {
35
+ slotClientSecret: string;
36
+ slotPassword: string;
37
+ slotApiKey: string;
38
+ slotBasic: string;
39
+ slotBearer: string;
40
+ slotHintClientSecret: string;
41
+ slotHintPassword: string;
42
+ slotHintApiKey: string;
43
+ slotHintBasic: string;
44
+ slotHintBearer: string;
45
+ };
46
+ export declare function slotLabel(slot: CredentialSlot, source: IntegrationSource, words: SlotWords): string;
47
+ export declare function slotHint(slot: CredentialSlot, source: IntegrationSource, words: SlotWords): string;
@@ -83,17 +83,17 @@
83
83
  'token'
84
84
  ];
85
85
  }
86
- export function slotLabel(slot, source) {
87
- if (slot === 'oauthClientSecret') return 'Client secret';
88
- if (slot === 'oauthPassword') return 'Password';
89
- if (source.authType === 'api_key') return 'API key';
90
- if (source.authType === 'basic') return 'Basic credential';
91
- return 'Bearer token';
86
+ export function slotLabel(slot, source, words) {
87
+ if (slot === 'oauthClientSecret') return words.slotClientSecret;
88
+ if (slot === 'oauthPassword') return words.slotPassword;
89
+ if (source.authType === 'api_key') return words.slotApiKey;
90
+ if (source.authType === 'basic') return words.slotBasic;
91
+ return words.slotBearer;
92
92
  }
93
- export function slotHint(slot, source) {
94
- if (slot === 'oauthClientSecret') return 'Sent to the token endpoint with the client ID.';
95
- if (slot === 'oauthPassword') return 'The password of the user named above.';
96
- if (source.authType === 'api_key') return 'Sent in the header or query parameter named in the auth section.';
97
- if (source.authType === 'basic') return 'The user name and password pair, as the service expects.';
98
- return 'Sent on every request through this connection.';
93
+ export function slotHint(slot, source, words) {
94
+ if (slot === 'oauthClientSecret') return words.slotHintClientSecret;
95
+ if (slot === 'oauthPassword') return words.slotHintPassword;
96
+ if (source.authType === 'api_key') return words.slotHintApiKey;
97
+ if (source.authType === 'basic') return words.slotHintBasic;
98
+ return words.slotHintBearer;
99
99
  }
@@ -319,6 +319,33 @@ export type IntegrationsLabels = {
319
319
  deactivate: string;
320
320
  delete: string;
321
321
  };
322
+ /**
323
+ * The words on the badges.
324
+ *
325
+ * A run said "succeeded" and a mapping said "upsert" — the value out of the
326
+ * column, printed as-is. They are the first thing read on both screens, and
327
+ * they were the last thing left in English.
328
+ */
329
+ runStatusFull: {
330
+ queued: string;
331
+ running: string;
332
+ succeeded: string;
333
+ failed: string;
334
+ cancelled: string;
335
+ reverted: string;
336
+ };
337
+ triggerFull: {
338
+ manual: string;
339
+ schedule: string;
340
+ api: string;
341
+ };
342
+ badges: {
343
+ unknownTarget: string;
344
+ dedupSet: string;
345
+ dedupMissing: string;
346
+ missingRows: (policy: string) => string;
347
+ readOf: (fetched: string | number, reported: string | number) => string;
348
+ };
322
349
  missingPolicyHint: {
323
350
  ignore: string;
324
351
  deactivate: string;
@@ -527,6 +554,93 @@ export type IntegrationsLabels = {
527
554
  dismiss: string;
528
555
  remove: string;
529
556
  };
557
+ /**
558
+ * The sentences that explain a consequence rather than name a control.
559
+ *
560
+ * They used to be written into the components, which meant a Turkish
561
+ * installation read English paragraphs in the middle of a Turkish screen —
562
+ * and these are exactly the paragraphs somebody reads before doing something
563
+ * irreversible. The short labels were always translatable; the reasoning was
564
+ * not, which is backwards.
565
+ */
566
+ /**
567
+ * The last of the words that were typed into the components.
568
+ *
569
+ * A screen half in Turkish reads worse than one entirely in English: the
570
+ * reader stops trusting that the two halves mean the same thing. These are
571
+ * short, which is exactly why they survived three passes.
572
+ */
573
+ ui: {
574
+ tryAgain: string;
575
+ neverRun: string;
576
+ lastRun: (moment: string) => string;
577
+ shownOfTotal: (shown: string | number, total: string | number) => string;
578
+ runImport: string;
579
+ runImportFor: (name: string) => string;
580
+ previewAndImportFor: (name: string) => string;
581
+ parameterValues: string;
582
+ openTheMapping: string;
583
+ leaveRowsAlone: string;
584
+ goToMappings: string;
585
+ addMembership: string;
586
+ previewAndImport: string;
587
+ cancel: string;
588
+ showSecret: string;
589
+ hideSecret: string;
590
+ slotClientSecret: string;
591
+ slotPassword: string;
592
+ slotApiKey: string;
593
+ slotBasic: string;
594
+ slotBearer: string;
595
+ slotHintClientSecret: string;
596
+ slotHintPassword: string;
597
+ slotHintApiKey: string;
598
+ slotHintBasic: string;
599
+ slotHintBearer: string;
600
+ };
601
+ explain: {
602
+ exportBundleRebuildsIntegration: string;
603
+ exportOmitsCredentials: string;
604
+ credentialsWriteOnlyBody: string;
605
+ credentialsNoAuthNothingToStore: string;
606
+ sampleOneRequestLead: string;
607
+ sampleOneRequestRecords: string;
608
+ sampleFirstPrintedLead: string;
609
+ sampleFirstPrintedTail: string;
610
+ sampleOneRequestTail: string;
611
+ sampleBodyKeysAreTopLevel: string;
612
+ sampleBodyHasNoTopLevelKeys: string;
613
+ accountMatchColumnIdentifies: string;
614
+ accountPasswordColumnReceives: string;
615
+ accountMatchFromCarriesValue: string;
616
+ accountFallbackWhenIdentifierEmpty: string;
617
+ accountIdColumnLinksBack: string;
618
+ accountNoMembershipsYet: string;
619
+ accountSharedPasswordPerRun: string;
620
+ accountCreationOff: string;
621
+ scheduleTimezoneIsCompanyClock: string;
622
+ scheduleCannotRunUnattended: string;
623
+ scheduleOneServerTakesTheRun: string;
624
+ scheduleOffUntilAskedFor: string;
625
+ writeModeReplaceRebuildsTable: string;
626
+ writeModeDeleteTrustsShortPage: string;
627
+ paginationWrongStyleSilentlySucceeds: string;
628
+ runConfirmCountsUnknown: string;
629
+ runConfirmKeepThem: string;
630
+ runHistoryNothingRunYet: string;
631
+ runPreviewWritesNothing: string;
632
+ missingReferencesTitle: (count: string | number) => string;
633
+ missingReferencesRecordsLeftOut: (count: string | number) => string;
634
+ missingReferenceRow: (target: string | number, count: string | number) => string;
635
+ missingReferencesCreateAndRun: (count: string | number) => string;
636
+ missingReferencesRunWithoutThem: string;
637
+ plannedReferencesTitle: (count: string | number) => string;
638
+ plannedReferencesCreatedBefore: string;
639
+ plannedReferencesLeaveRecordsOut: string;
640
+ runPlanCountMismatchBody: (reportedTotal: string | number | null, totalFetched: string | number) => string;
641
+ runPlanAccountsToCreateTitle: (accountsToCreate: string | number) => string;
642
+ runPlanAccountsSharePassword: string;
643
+ };
530
644
  };
531
645
  /** What every app gets before it says otherwise. */
532
646
  export declare const defaultIntegrationsLabels: IntegrationsLabels;
@@ -298,6 +298,26 @@
298
298
  append: 'Nothing is matched, so running this import twice stores every record twice.',
299
299
  replace: 'The table is emptied and refilled on every run.'
300
300
  },
301
+ runStatusFull: {
302
+ queued: 'queued',
303
+ running: 'running',
304
+ succeeded: 'succeeded',
305
+ failed: 'failed',
306
+ cancelled: 'cancelled',
307
+ reverted: 'reverted'
308
+ },
309
+ triggerFull: {
310
+ manual: 'manual',
311
+ schedule: 'scheduled',
312
+ api: 'API'
313
+ },
314
+ badges: {
315
+ unknownTarget: 'unknown target',
316
+ dedupSet: 'dedup key set',
317
+ dedupMissing: 'no dedup key',
318
+ missingRows: (policy)=>`missing rows: ${policy}`,
319
+ readOf: (fetched, reported)=>`read ${fetched} of ${reported}`
320
+ },
301
321
  missingPolicyFull: {
302
322
  ignore: 'Ignore — leave them exactly as they are',
303
323
  deactivate: 'Deactivate — mark them with a column value',
@@ -488,6 +508,77 @@
488
508
  somethingWentWrong: 'Something went wrong',
489
509
  dismiss: 'Dismiss error',
490
510
  remove: 'Remove'
511
+ },
512
+ ui: {
513
+ tryAgain: 'Try again',
514
+ neverRun: 'Never run',
515
+ lastRun: (moment)=>`Last run ${moment}`,
516
+ shownOfTotal: (shown, total)=>`${shown} of ${total} shown`,
517
+ runImport: 'Import',
518
+ runImportFor: (name)=>`Import ${name}`,
519
+ previewAndImportFor: (name)=>`Preview and import ${name}`,
520
+ parameterValues: 'Parameter values',
521
+ openTheMapping: 'Open the mapping',
522
+ leaveRowsAlone: 'Leave those rows alone',
523
+ goToMappings: 'Go to the mappings',
524
+ addMembership: 'Add a membership',
525
+ previewAndImport: 'Preview and import',
526
+ cancel: 'Cancel',
527
+ showSecret: 'Show',
528
+ hideSecret: 'Hide',
529
+ slotClientSecret: 'Client secret',
530
+ slotPassword: 'Password',
531
+ slotApiKey: 'API key',
532
+ slotBasic: 'Basic credential',
533
+ slotBearer: 'Bearer token',
534
+ slotHintClientSecret: 'Sent to the token endpoint with the client ID.',
535
+ slotHintPassword: 'The password of the user named above.',
536
+ slotHintApiKey: 'Sent in the header or query parameter named in the auth section.',
537
+ slotHintBasic: 'The user name and password pair, as the service expects.',
538
+ slotHintBearer: 'Sent on every request through this connection.'
539
+ },
540
+ explain: {
541
+ exportBundleRebuildsIntegration: 'One file holding this connection, its calls and its mappings — enough to rebuild the whole integration in another installation. Bring it in there with Import, on the connections screen.',
542
+ exportOmitsCredentials: 'Tokens, passwords and keys are left out on purpose. Whatever this connection authenticates with has to be entered again wherever the file lands.',
543
+ credentialsWriteOnlyBody: 'A value typed here is stored and from then on only ever sent to the service. It is not returned to this screen, or to any other, no matter who asks — all that comes back is a masked hint. Leave a box empty to keep whatever is already stored.',
544
+ credentialsNoAuthNothingToStore: 'This connection is set to no authentication, so there is no secret to store. Pick an auth type above to see the fields it needs.',
545
+ sampleOneRequestLead: 'This is one request. The source answered it with ',
546
+ sampleOneRequestRecords: ' record(s)',
547
+ sampleFirstPrintedLead: ', of which the first ',
548
+ sampleFirstPrintedTail: ' are printed below',
549
+ sampleOneRequestTail: ' — not the whole table. If the endpoint pages, this is one page of it. Preview on the Run step walks every page and reports the real total.',
550
+ sampleBodyKeysAreTopLevel: 'These are the top-level keys of the body the source returned. Set the response root path to whichever holds the list.',
551
+ sampleBodyHasNoTopLevelKeys: 'The body has no top-level keys at all: it is empty, or it is a bare value rather than an object.',
552
+ accountMatchColumnIdentifies: 'The column on the account that identifies it.',
553
+ accountPasswordColumnReceives: "Where the run's shared password is written.",
554
+ accountMatchFromCarriesValue: "The imported record's column carrying that same value.",
555
+ accountFallbackWhenIdentifierEmpty: "Used only when the column above came back EMPTY, so nobody is left without a login. Name the record's own columns in braces — {firstName}.{sicilNo}@acme.com. Leave it blank to go on skipping those records.",
556
+ accountIdColumnLinksBack: "Filled with the new account's id, so the record and its login know each other.",
557
+ accountNoMembershipsYet: 'Nothing. An account with no role can sign in and do nothing — every screen it reaches refuses it. A role usually lives in its own table, joined to the account, which is what this adds.',
558
+ accountSharedPasswordPerRun: 'Every account this run creates is given the same opening password — asked for once, when the import is started, and stored nowhere. People change it themselves afterwards. A record whose value already has an account is linked to that one instead, so running this import again does not create a second.',
559
+ accountCreationOff: 'Off. Records are imported without a login — useful for a directory, not for people who have to sign in.',
560
+ scheduleTimezoneIsCompanyClock: "The company's own clock, so 02:00 stays 02:00 across a daylight-saving change.",
561
+ scheduleCannotRunUnattended: 'It creates a login for every new record, and a login needs a password — which there is nobody to type at that hour. Either start this import by hand, or turn the login off and give people their accounts another way.',
562
+ scheduleOneServerTakesTheRun: 'Every server running this app checks the schedule, and exactly one of them takes the import — the same lock that stops two people running it at once.',
563
+ scheduleOffUntilAskedFor: 'Off. This import runs when somebody asks for it, and at no other time.',
564
+ writeModeReplaceRebuildsTable: 'Every existing row is dropped and written again, so row ids change. Foreign keys, saved links, exports, report filters and anything else holding one of those ids breaks, and no amount of re-running repairs it.',
565
+ writeModeDeleteTrustsShortPage: 'A source that answers with a short page — a filter left on, a timeout, an expired token honoured with an empty list — looks exactly like records that went away.',
566
+ paginationWrongStyleSilentlySucceeds: 'Nothing here is detected for you. Pick the wrong style and the import still succeeds — it reads the first page and never asks for the rest. In a destructive write mode the records it never saw count as missing, so the run deletes or deactivates them.',
567
+ runConfirmCountsUnknown: 'These counts are unknown because nothing was previewed. Run a preview first if you want to know how many rows this removes before it removes them.',
568
+ runConfirmKeepThem: 'Keep them, do not run',
569
+ runHistoryNothingRunYet: 'Nothing has run yet. A preview writes nothing; the first entry appears here once an import is started.',
570
+ runPreviewWritesNothing: 'A preview reads the source without writing anything, and reports how many rows would be created, updated, skipped, and how many rows in the target the source no longer returns.',
571
+ missingReferencesTitle: (count)=>`$${count} value(s) this import refers to are not here yet`,
572
+ missingReferencesRecordsLeftOut: (count)=>`As it stands the records that carry them are left out — $${count} record(s) in total. Either add the values first, or import without those records.`,
573
+ missingReferenceRow: (target, count)=>` — missing from $${target}, needed by $${count} record(s)`,
574
+ missingReferencesCreateAndRun: (count)=>`Create these $${count} value(s) and import everything`,
575
+ missingReferencesRunWithoutThem: 'Import without those records',
576
+ plannedReferencesTitle: (count)=>`$${count} value(s) will be added first`,
577
+ plannedReferencesCreatedBefore: 'These are created in the same import, before the records that point at them, so nothing is written pointing at nothing. They carry only the value itself — rename them afterwards.',
578
+ plannedReferencesLeaveRecordsOut: 'Do not create them — leave those records out',
579
+ runPlanCountMismatchBody: (reportedTotal, totalFetched)=>`The source reports ${reportedTotal} record(s); ${totalFetched} were actually read. Check the endpoint's pagination profile before running: a wrong profile reads one page, and every record it never saw counts as missing.`,
580
+ runPlanAccountsToCreateTitle: (accountsToCreate)=>`${accountsToCreate} login(s) will be created, with the password typed above`,
581
+ runPlanAccountsSharePassword: 'Each new record gets an account in the same import — one password for all of them, which people change themselves afterwards. Anyone whose value already has an account is linked to it instead.'
491
582
  }
492
583
  };
493
584
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nucleus-core-ts",
3
- "version": "0.9.911",
3
+ "version": "0.9.913",
4
4
  "description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
5
5
  "author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
6
6
  "license": "SEE LICENSE IN LICENSE",