@firecms/collection_editor 3.1.0 → 3.2.0-canary.44dc65b

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 (63) hide show
  1. package/dist/index.es.js +6887 -3709
  2. package/dist/index.es.js.map +1 -1
  3. package/dist/index.umd.js +6885 -3707
  4. package/dist/index.umd.js.map +1 -1
  5. package/dist/locales/de.d.ts +118 -0
  6. package/dist/locales/en.d.ts +118 -0
  7. package/dist/locales/es.d.ts +118 -0
  8. package/dist/locales/fr.d.ts +118 -0
  9. package/dist/locales/hi.d.ts +118 -0
  10. package/dist/locales/it.d.ts +118 -0
  11. package/dist/locales/pt.d.ts +118 -0
  12. package/package.json +8 -8
  13. package/src/locales/de.ts +123 -0
  14. package/src/locales/en.ts +143 -0
  15. package/src/locales/es.ts +123 -0
  16. package/src/locales/fr.ts +123 -0
  17. package/src/locales/hi.ts +123 -0
  18. package/src/locales/it.ts +123 -0
  19. package/src/locales/pt.ts +123 -0
  20. package/src/ui/EditorCollectionAction.tsx +3 -3
  21. package/src/ui/EditorEntityAction.tsx +3 -2
  22. package/src/ui/NewCollectionButton.tsx +3 -1
  23. package/src/ui/NewCollectionCard.tsx +7 -4
  24. package/src/ui/PropertyAddColumnComponent.tsx +3 -2
  25. package/src/ui/collection_editor/AICollectionGeneratorPopover.tsx +11 -10
  26. package/src/ui/collection_editor/CollectionDetailsForm.tsx +26 -24
  27. package/src/ui/collection_editor/CollectionEditorDialog.tsx +42 -38
  28. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +19 -18
  29. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +12 -10
  30. package/src/ui/collection_editor/DisplaySettingsForm.tsx +19 -17
  31. package/src/ui/collection_editor/EntityActionsEditTab.tsx +11 -12
  32. package/src/ui/collection_editor/EntityActionsSelectDialog.tsx +5 -6
  33. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +5 -5
  34. package/src/ui/collection_editor/EnumForm.tsx +6 -2
  35. package/src/ui/collection_editor/ExtendSettingsForm.tsx +8 -7
  36. package/src/ui/collection_editor/GeneralSettingsForm.tsx +36 -38
  37. package/src/ui/collection_editor/GetCodeDialog.tsx +13 -12
  38. package/src/ui/collection_editor/KanbanConfigSection.tsx +11 -9
  39. package/src/ui/collection_editor/LayoutModeSwitch.tsx +7 -4
  40. package/src/ui/collection_editor/PropertyEditView.tsx +74 -65
  41. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +16 -19
  42. package/src/ui/collection_editor/ViewModeSwitch.tsx +8 -6
  43. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +6 -3
  44. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +5 -2
  45. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +3 -1
  46. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +6 -4
  47. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +20 -18
  48. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +5 -4
  49. package/src/ui/collection_editor/properties/MapPropertyField.tsx +8 -7
  50. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +22 -23
  51. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +3 -1
  52. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +5 -4
  53. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +46 -51
  54. package/src/ui/collection_editor/properties/StringPropertyField.tsx +3 -1
  55. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +12 -10
  56. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +7 -4
  57. package/src/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +8 -3
  58. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +5 -2
  59. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +7 -5
  60. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +10 -7
  61. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +11 -9
  62. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +4 -1
  63. package/src/useCollectionEditorPlugin.tsx +22 -6
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from "react";
2
- import { EntityCollection, useNavigationController, useSnackbarController, AIIcon } from "@firecms/core";
2
+ import { EntityCollection, useNavigationController, useSnackbarController, AIIcon, useTranslation } from "@firecms/core";
3
3
  import {
4
4
  Button,
5
5
  CircularProgress,
@@ -69,6 +69,7 @@ export function AICollectionGeneratorPopover({
69
69
  const [loading, setLoading] = useState(false);
70
70
  const [error, setError] = useState<string | null>(null);
71
71
 
72
+ const { t } = useTranslation();
72
73
  const navigation = useNavigationController();
73
74
  const snackbarController = useSnackbarController();
74
75
 
@@ -155,13 +156,13 @@ export function AICollectionGeneratorPopover({
155
156
  : <AIIcon size="small" />
156
157
  }
157
158
  >
158
- AI Assist
159
+ {t("ai_assist")}
159
160
  </Button>
160
161
  ) : (
161
162
  <IconButton
162
163
  size={size}
163
164
  disabled={loading}
164
- aria-label="AI Assist"
165
+ aria-label={t("ai_assist")}
165
166
  >
166
167
  {loading
167
168
  ? <CircularProgress size="smallest" />
@@ -185,14 +186,14 @@ export function AICollectionGeneratorPopover({
185
186
  <div className="flex items-center gap-2">
186
187
  <AIIcon size="small" />
187
188
  <Typography variant="subtitle2">
188
- {existingCollection ? "Modify Collection with AI" : "Generate Collection with AI"}
189
+ {existingCollection ? t("modify_collection_with_ai") : t("generate_collection_with_ai")}
189
190
  </Typography>
190
191
  </div>
191
192
 
192
193
  <Typography variant="caption" color="secondary">
193
194
  {existingCollection
194
- ? "Describe the changes you want to make to this collection."
195
- : "Describe the collection you want to create."
195
+ ? t("describe_changes_to_make")
196
+ : t("describe_collection_to_create")
196
197
  }
197
198
  </Typography>
198
199
 
@@ -205,8 +206,8 @@ export function AICollectionGeneratorPopover({
205
206
  onChange={(e) => setPrompt(e.target.value)}
206
207
  onKeyDown={handleKeyDown}
207
208
  placeholder={existingCollection
208
- ? "e.g., Add a thumbnail image field with storage, make price required..."
209
- : "e.g., Create a products collection with name, price, description, and category..."
209
+ ? t("ai_placeholder_modify")
210
+ : t("ai_placeholder_create")
210
211
  }
211
212
  disabled={loading}
212
213
  />
@@ -224,7 +225,7 @@ export function AICollectionGeneratorPopover({
224
225
  onClick={() => setMenuOpen(false)}
225
226
  disabled={loading}
226
227
  >
227
- Cancel
228
+ {t("cancel")}
228
229
  </Button>
229
230
  <Button
230
231
  variant="filled"
@@ -233,7 +234,7 @@ export function AICollectionGeneratorPopover({
233
234
  disabled={!prompt.trim() || loading}
234
235
  startIcon={loading ? <CircularProgress size="smallest" /> : undefined}
235
236
  >
236
- {loading ? "Generating..." : "Generate"}
237
+ {loading ? t("generating") : t("generate_with_ai")}
237
238
  </Button>
238
239
  </div>
239
240
  </div>
@@ -1,5 +1,5 @@
1
1
  import React, { useMemo, useState } from "react";
2
- import { EntityCollection, FieldCaption, getFieldConfig, IconForView, Property, PropertyConfigBadge, resolveCollection, SearchIconsView, singular, toSnakeCase, unslugify, useAuthController, useCustomizationController } from "@firecms/core";
2
+ import { EntityCollection, FieldCaption, getFieldConfig, IconForView, Property, PropertyConfigBadge, resolveCollection, SearchIconsView, singular, toSnakeCase, unslugify, useAuthController, useCustomizationController, useTranslation } from "@firecms/core";
3
3
  import {
4
4
  BooleanSwitchWithLabel,
5
5
  Chip,
@@ -61,6 +61,8 @@ export function CollectionDetailsForm({
61
61
  const [iconDialogOpen, setIconDialogOpen] = useState(false);
62
62
  const [orderPropertyDialogOpen, setOrderPropertyDialogOpen] = useState(false);
63
63
 
64
+ const { t } = useTranslation();
65
+
64
66
  const authController = useAuthController();
65
67
  const customizationController = useCustomizationController();
66
68
 
@@ -137,12 +139,12 @@ export function CollectionDetailsForm({
137
139
  <div
138
140
  className="flex flex-row gap-2 py-2 pt-3 items-center">
139
141
  <Typography variant={!isNewCollection ? "h5" : "h4"} className={"flex-grow"}>
140
- {isNewCollection ? "New collection" : `${values?.name} collection`}
142
+ {isNewCollection ? t("new_collection") : t("collection_with_name", { name: values?.name || "" })}
141
143
  </Typography>
142
144
  <DefaultDatabaseField databaseId={values.databaseId}
143
145
  onDatabaseIdUpdate={updateDatabaseId} />
144
146
 
145
- <Tooltip title={"Change icon"}
147
+ <Tooltip title={t("change_icon")}
146
148
  asChild={true}>
147
149
  <IconButton
148
150
  shape={"square"}
@@ -154,7 +156,7 @@ export function CollectionDetailsForm({
154
156
 
155
157
  {parentCollection && <Chip colorScheme={"tealDarker"}>
156
158
  <Typography variant={"caption"}>
157
- This is a subcollection of <b>{parentCollection.name}</b>
159
+ {t("is_subcollection_of")} <b>{parentCollection.name}</b>
158
160
  </Typography>
159
161
  </Chip>}
160
162
 
@@ -165,26 +167,26 @@ export function CollectionDetailsForm({
165
167
  <TextField
166
168
  value={values.name ?? ""}
167
169
  onChange={(e: any) => updateName(e.target.value)}
168
- label={"Name"}
170
+ label={t("name")}
169
171
  autoFocus={true}
170
172
  required
171
173
  error={showErrors && Boolean(errors.name)} />
172
174
  <FieldCaption error={touched.name && Boolean(errors.name)}>
173
- {touched.name && Boolean(errors.name) ? errors.name : "Name of this collection, usually a plural name (e.g. Products)"}
175
+ {touched.name && Boolean(errors.name) ? errors.name : t("collection_name_description")}
174
176
  </FieldCaption>
175
177
  </div>
176
178
 
177
179
  <div className={cls("col-span-12 ")}>
178
180
  <Field name={"path"}
179
181
  as={DebouncedTextField}
180
- label={"Path"}
182
+ label={t("path")}
181
183
  required
182
184
  error={showErrors && Boolean(errors.path)} />
183
185
 
184
186
  <FieldCaption error={touched.path && Boolean(errors.path)}>
185
187
  {touched.path && Boolean(errors.path)
186
188
  ? errors.path
187
- : isSubcollection ? "Relative path to the parent (no need to include the parent path)" : "Path that this collection is stored in, in the database"}
189
+ : isSubcollection ? t("relative_path_to_parent") : t("path_in_database")}
188
190
  </FieldCaption>
189
191
 
190
192
  </div>
@@ -247,7 +249,7 @@ export function CollectionDetailsForm({
247
249
  <Select
248
250
  key={`order-select-${numberProperties.length}`}
249
251
  name="orderProperty"
250
- label="Order Property"
252
+ label={t("order_property")}
251
253
  size={"large"}
252
254
  fullWidth={true}
253
255
  position={"item-aligned"}
@@ -259,10 +261,10 @@ export function CollectionDetailsForm({
259
261
  }}
260
262
  renderValue={(value) => {
261
263
  if (orderPropertyMissing) {
262
- return <span className="text-red-500">{value} (not found)</span>;
264
+ return <span className="text-red-500">{value} ({t("not_found_suffix")})</span>;
263
265
  }
264
266
  const prop = numberProperties.find(p => p.key === value);
265
- if (!prop) return "Select a property";
267
+ if (!prop) return t("select_a_property");
266
268
  const fieldConfig = getFieldConfig(prop.property, customizationController.propertyConfigs);
267
269
  return (
268
270
  <div className="flex items-center gap-2">
@@ -292,7 +294,7 @@ export function CollectionDetailsForm({
292
294
  <div>
293
295
  <div>{prop.label}</div>
294
296
  <Typography variant="caption" color="secondary">
295
- {fieldConfig?.name || "Number"}
297
+ {fieldConfig?.name || t("number")}
296
298
  </Typography>
297
299
  </div>
298
300
  </div>
@@ -302,10 +304,10 @@ export function CollectionDetailsForm({
302
304
  </Select>
303
305
  <FieldCaption error={orderPropertyMissing}>
304
306
  {orderPropertyMissing
305
- ? `Property "${values.orderProperty}" does not exist or is not a number property. Please select a valid property or clear the selection.`
307
+ ? t("order_property_not_found", { property: values.orderProperty ?? "" })
306
308
  : numberProperties.length === 0
307
- ? "No number properties found. Add a number property to enable ordering."
308
- : "Select a number property to persist the order of items"
309
+ ? t("no_number_properties")
310
+ : t("order_property_description")
309
311
  }
310
312
  </FieldCaption>
311
313
  </>
@@ -323,7 +325,7 @@ export function CollectionDetailsForm({
323
325
  : "__order";
324
326
  const dialogPropertyName = orderPropertyMissing && values.orderProperty
325
327
  ? unslugify(values.orderProperty)
326
- : "Order";
328
+ : t("order_label");
327
329
 
328
330
  if (!showCreateButton) return null;
329
331
 
@@ -334,7 +336,7 @@ export function CollectionDetailsForm({
334
336
  className="ml-3.5 text-sm text-primary hover:text-primary-dark mt-2"
335
337
  onClick={() => setOrderPropertyDialogOpen(true)}
336
338
  >
337
- + Create "{dialogPropertyKey}" property
339
+ {t("create_property", { property: dialogPropertyKey })}
338
340
  </button>
339
341
  <PropertyFormDialog
340
342
  open={orderPropertyDialogOpen}
@@ -376,16 +378,14 @@ export function CollectionDetailsForm({
376
378
  position={"start"}
377
379
  size={"large"}
378
380
  allowIndeterminate={true}
379
- label={<span className="flex items-center gap-2"><HistoryIcon size={"smallest"} />{values.history === null || values.history === undefined ? "Document history revisions enabled if enabled globally" : (
380
- values.history ? "Document history revisions ENABLED" : "Document history revisions NOT enabled"
381
+ label={<span className="flex items-center gap-2"><HistoryIcon size={"smallest"} />{values.history === null || values.history === undefined ? t("doc_history_global") : (
382
+ values.history ? t("doc_history_enabled") : t("doc_history_not_enabled")
381
383
  )}</span>}
382
384
  onValueChange={(v) => setFieldValue("history", v)}
383
385
  value={values.history === undefined ? null : values.history}
384
386
  />
385
387
  <FieldCaption>
386
- When enabled, each document in this collection will have a history of changes.
387
- This is useful for auditing purposes. The data is stored in a subcollection of the document
388
- in your database, called <b>__history</b>.
388
+ {t("doc_history_description")}
389
389
  </FieldCaption>
390
390
  </div>
391
391
 
@@ -424,7 +424,9 @@ function DefaultDatabaseField({
424
424
  onDatabaseIdUpdate
425
425
  }: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
426
426
 
427
- return <Tooltip title={"Database ID"}
427
+ const { t } = useTranslation();
428
+
429
+ return <Tooltip title={t("database_id")}
428
430
  side={"top"}
429
431
  align={"start"}>
430
432
  <TextField size={"small"}
@@ -432,6 +434,6 @@ function DefaultDatabaseField({
432
434
  inputClassName={"text-end"}
433
435
  value={databaseId ?? ""}
434
436
  onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
435
- placeholder={"(default)"}></TextField>
437
+ placeholder={t("default_text")}></TextField>
436
438
  </Tooltip>
437
439
  }
@@ -22,7 +22,8 @@ import {
22
22
  useCustomizationController,
23
23
  useNavigationController,
24
24
  User,
25
- useSnackbarController
25
+ useSnackbarController,
26
+ useTranslation,
26
27
  } from "@firecms/core";
27
28
  import {
28
29
  ArrowBackIcon,
@@ -113,6 +114,8 @@ export interface CollectionEditorDialogProps {
113
114
  }
114
115
 
115
116
  export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
117
+ const { t } = useTranslation();
118
+
116
119
 
117
120
  const open = props.open;
118
121
 
@@ -143,7 +146,7 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
143
146
  maxWidth={"7xl"}
144
147
  onOpenChange={(open) => !open ? handleCancel() : undefined}
145
148
  >
146
- <DialogTitle hidden>Collection editor</DialogTitle>
149
+ <DialogTitle hidden>{t("collection_editor")}</DialogTitle>
147
150
  <AIModifiedPathsProvider>
148
151
  {open && <CollectionEditor {...props}
149
152
  handleCancel={handleCancel}
@@ -153,7 +156,7 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
153
156
  open={unsavedChangesDialogOpen}
154
157
  handleOk={() => props.handleClose(undefined)}
155
158
  handleCancel={() => setUnsavedChangesDialogOpen(false)}
156
- body={"There are unsaved changes in this collection"} />
159
+ body={t("unsaved_changes_in_collection")} />
157
160
  </AIModifiedPathsProvider>
158
161
  </Dialog>
159
162
  );
@@ -306,10 +309,11 @@ function CollectionEditorInternal<M extends Record<string, any>>({
306
309
  collection: PersistedCollection<M> | undefined,
307
310
  setCollection: (collection: PersistedCollection<M>) => void,
308
311
  propertyConfigs: Record<string, PropertyConfig<any>>,
309
- groups: string[],
312
+ groups: (string | null)[],
310
313
  }
311
314
  ) {
312
315
 
316
+ const { t } = useTranslation();
313
317
  const importConfig = useImportConfig();
314
318
  const navigation = useNavigationController();
315
319
  const snackbarController = useSnackbarController();
@@ -342,7 +346,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
342
346
  console.error(e);
343
347
  snackbarController.open({
344
348
  type: "error",
345
- message: "Error persisting collection: " + (e.message ?? "Details in the console")
349
+ message: t("error_persisting_collection", { error: e.message ?? t("details_in_console") })
346
350
  });
347
351
  return false;
348
352
  });
@@ -423,7 +427,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
423
427
  console.error(e);
424
428
  snackbarController.open({
425
429
  type: "error",
426
- message: "Error inferring collection: " + (e.message ?? "Details in the console")
430
+ message: t("error_inferring_collection", { error: e.message ?? t("details_in_console") })
427
431
  });
428
432
  return newCollection;
429
433
  }
@@ -486,7 +490,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
486
490
  } catch (e: any) {
487
491
  snackbarController.open({
488
492
  type: "error",
489
- message: "Error persisting collection: " + (e.message ?? "Details in the console")
493
+ message: t("error_persisting_collection", { error: e.message ?? t("details_in_console") })
490
494
  });
491
495
  console.error(e);
492
496
  formexController.resetForm({ values: newCollectionState });
@@ -510,11 +514,11 @@ function CollectionEditorInternal<M extends Record<string, any>>({
510
514
  errors = { ...errors, ...propertyErrorsRef.current };
511
515
  }
512
516
  if (currentView === "general") {
513
- const pathError = validatePath(col.path, isNewCollection, existingPaths, col.id);
517
+ const pathError = validatePath(t, col.path, isNewCollection, existingPaths, col.id);
514
518
  if (pathError) {
515
519
  errors.path = pathError;
516
520
  }
517
- const idError = validateId(col.id, isNewCollection, existingPaths, existingIds);
521
+ const idError = validateId(t, col.id, isNewCollection, existingPaths, existingIds);
518
522
  if (idError) {
519
523
  errors.id = idError;
520
524
  }
@@ -539,7 +543,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
539
543
 
540
544
  const path = values.path;
541
545
  const updatedFullPath = fullPath?.includes("/") ? fullPath?.split("/").slice(0, -1).join("/") + "/" + path : path; // TODO: this path is wrong
542
- const pathError = validatePath(path, isNewCollection, existingPaths, values.id);
546
+ const pathError = validatePath(t, path, isNewCollection, existingPaths, values.id);
543
547
 
544
548
  const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
545
549
  const resolvedPath = !pathError ? navigation.resolveIdsFrom(updatedFullPath) : undefined;
@@ -601,7 +605,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
601
605
  setDeleteRequested(false);
602
606
  handleCancel();
603
607
  snackbarController.open({
604
- message: "Collection deleted",
608
+ message: t("collection_deleted"),
605
609
  type: "success"
606
610
  });
607
611
  });
@@ -641,16 +645,16 @@ function CollectionEditorInternal<M extends Record<string, any>>({
641
645
  <Tabs value={currentView}
642
646
  onValueChange={(v) => setCurrentView(v as EditorView)}>
643
647
  <Tab value={"general"}>
644
- General
648
+ {t("tab_general")}
645
649
  </Tab>
646
650
  <Tab value={"display"}>
647
- Display
651
+ {t("tab_display")}
648
652
  </Tab>
649
653
  <Tab value={"properties"}>
650
- Properties
654
+ {t("tab_properties")}
651
655
  </Tab>
652
656
  <Tab value={"extend"}>
653
- Extend
657
+ {t("tab_extend")}
654
658
  </Tab>
655
659
  </Tabs>
656
660
  </div>}
@@ -695,7 +699,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
695
699
  onImportSuccess={async (importedCollection) => {
696
700
  snackbarController.open({
697
701
  type: "info",
698
- message: "Data imported successfully"
702
+ message: t("data_imported_successfully_msg")
699
703
  });
700
704
  await saveCollection(values);
701
705
  handleClose(importedCollection);
@@ -763,7 +767,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
763
767
  importConfig.setInUse(false);
764
768
  return setCurrentView("welcome");
765
769
  }}>
766
- Back
770
+ {t("back")}
767
771
  </Button>}
768
772
 
769
773
  {isNewCollection && includeTemplates && currentView === "import_data_preview" &&
@@ -772,14 +776,14 @@ function CollectionEditorInternal<M extends Record<string, any>>({
772
776
  onClick={() => {
773
777
  setCurrentView("import_data_mapping");
774
778
  }}>
775
- Back
779
+ {t("back")}
776
780
  </Button>}
777
781
 
778
782
  {isNewCollection && includeTemplates && currentView === "general" &&
779
783
  <Button variant={"text"}
780
784
  type="button"
781
785
  onClick={() => setCurrentView("welcome")}>
782
- Back
786
+ {t("back")}
783
787
  </Button>}
784
788
 
785
789
  {isNewCollection && currentView === "properties" && <Button variant={"text"}
@@ -787,7 +791,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
787
791
  color={"neutral"}
788
792
  onClick={() => setCurrentView("general")}>
789
793
  <ArrowBackIcon />
790
- Back
794
+ {t("back")}
791
795
  </Button>}
792
796
 
793
797
  <Button variant={"text"}
@@ -795,12 +799,12 @@ function CollectionEditorInternal<M extends Record<string, any>>({
795
799
  onClick={() => {
796
800
  handleCancel();
797
801
  }}>
798
- Cancel
802
+ {t("cancel")}
799
803
  </Button>
800
804
 
801
805
  {currentView === "welcome" &&
802
806
  <Button variant={"text"} onClick={() => onWelcomeScreenContinue()}>
803
- Continue from scratch
807
+ {t("continue_from_scratch")}
804
808
  </Button>}
805
809
 
806
810
  {isNewCollection && currentView === "import_data_mapping" &&
@@ -809,7 +813,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
809
813
  color="primary"
810
814
  onClick={onImportMappingComplete}
811
815
  >
812
- Next
816
+ {t("next")}
813
817
  </Button>}
814
818
 
815
819
  {isNewCollection && currentView === "import_data_preview" &&
@@ -820,7 +824,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
820
824
  setNextMode();
821
825
  }}
822
826
  >
823
- Next
827
+ {t("next")}
824
828
  </Button>}
825
829
 
826
830
  {isNewCollection && (currentView === "general" || currentView === "properties") &&
@@ -834,8 +838,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
834
838
  ? <CheckIcon />
835
839
  : undefined}
836
840
  >
837
- {currentView === "general" && "Next"}
838
- {currentView === "properties" && "Create collection"}
841
+ {currentView === "general" && t("next")}
842
+ {currentView === "properties" && t("create_collection")}
839
843
  </LoadingButton>}
840
844
 
841
845
  {!isNewCollection && <LoadingButton
@@ -844,7 +848,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
844
848
  type="submit"
845
849
  loading={isSubmitting}
846
850
  >
847
- Update collection
851
+ {t("update_collection")}
848
852
  </LoadingButton>}
849
853
 
850
854
  </DialogActions>
@@ -857,10 +861,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
857
861
  open={deleteRequested}
858
862
  onAccept={deleteCollection}
859
863
  onCancel={() => setDeleteRequested(false)}
860
- title={<>Delete the stored config?</>}
861
- body={<> This will <b>not
862
- delete any data</b>, only
863
- the stored config, and reset to the code state.</>} />
864
+ title={<>{t("delete_stored_config")}</>}
865
+ body={<>{t("delete_stored_config_body")}</>} />
864
866
 
865
867
  </DialogContent>
866
868
 
@@ -915,30 +917,32 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
915
917
 
916
918
  }
917
919
 
918
- const validatePath = (value: string, isNewCollection: boolean, existingPaths: string[], idValue?: string) => {
920
+ type TranslateFn = (key: string, params?: Record<string, string>) => string;
921
+
922
+ const validatePath = (t: TranslateFn, value: string, isNewCollection: boolean, existingPaths: string[], idValue?: string) => {
919
923
  let error;
920
924
  if (!value) {
921
- error = "You must specify a path in the database for this collection";
925
+ error = t("must_specify_path");
922
926
  }
923
927
  // if (isNewCollection && existingIds?.includes(value.trim().toLowerCase()))
924
928
  // error = "There is already a collection which uses this path as an id";
925
929
  if (isNewCollection && existingPaths?.includes(value.trim().toLowerCase()) && !idValue)
926
- error = "There is already a collection with the specified path. If you want to have multiple collections referring to the same database path, make sure the have different ids";
930
+ error = t("collection_path_already_exists");
927
931
 
928
932
  const subpaths = removeInitialAndTrailingSlashes(value).split("/");
929
933
  if (subpaths.length % 2 === 0) {
930
- error = `Collection paths must have an odd number of segments: ${value}`;
934
+ error = t("collection_path_odd_segments", { path: value });
931
935
  }
932
936
  return error;
933
937
  };
934
938
 
935
- const validateId = (value: string, isNewCollection: boolean, existingPaths: string[], existingIds: string[]) => {
939
+ const validateId = (t: TranslateFn, value: string, isNewCollection: boolean, existingPaths: string[], existingIds: string[]) => {
936
940
  if (!value) return undefined;
937
941
  let error;
938
942
  if (isNewCollection && existingPaths?.includes(value.trim().toLowerCase()))
939
- error = "There is already a collection that uses this value as a path";
943
+ error = t("collection_uses_path_as_id");
940
944
  if (isNewCollection && existingIds?.includes(value.trim().toLowerCase()))
941
- error = "There is already a collection which uses this id";
945
+ error = t("collection_uses_this_id");
942
946
  // if (error) {
943
947
  // setAdvancedPanelExpanded(true);
944
948
  // }
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from "react";
2
- import { AIIcon, EntityCollection, prettifyIdentifier, } from "@firecms/core";
2
+ import { AIIcon, EntityCollection, prettifyIdentifier, useTranslation } from "@firecms/core";
3
3
  import { Button, Card, Chip, cls, CodeIcon, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
4
4
  import { CollectionJsonImportDialog } from "./CollectionJsonImportDialog";
5
5
 
@@ -30,6 +30,7 @@ export function CollectionEditorWelcomeView({
30
30
  }) {
31
31
 
32
32
  const { pathSuggestions } = useCollectionEditorController();
33
+ const { t } = useTranslation();
33
34
 
34
35
  const filteredSuggestions = (pathSuggestions ?? []).filter(s => !(existingCollectionPaths ?? []).find(c => c.trim().toLowerCase() === s.trim().toLowerCase()));
35
36
 
@@ -49,13 +50,13 @@ export function CollectionEditorWelcomeView({
49
50
  <div
50
51
  className="flex flex-row py-2 pt-3 items-center">
51
52
  <Typography variant={"h4"} className={"flex-grow"}>
52
- New collection
53
+ {t("new_collection")}
53
54
  </Typography>
54
55
  </div>
55
56
 
56
57
  {parentCollection && <Chip colorScheme={"tealDarker"}>
57
58
  <Typography variant={"caption"}>
58
- This is a subcollection of <b>{parentCollection.name}</b>
59
+ {t("this_is_subcollection_of")} <b>{parentCollection.name}</b>
59
60
  </Typography>
60
61
  </Chip>}
61
62
 
@@ -63,7 +64,7 @@ export function CollectionEditorWelcomeView({
63
64
 
64
65
  <Typography variant={"caption"}
65
66
  color={"secondary"}>
66
- Use one of the existing paths in your database:
67
+ {t("use_existing_paths_database")}
67
68
  </Typography>
68
69
  <div className={"flex flex-wrap gap-x-2 gap-y-1 items-center my-2 min-h-7"}>
69
70
 
@@ -92,7 +93,7 @@ export function CollectionEditorWelcomeView({
92
93
  <Typography variant={"caption"}
93
94
  color={"secondary"}
94
95
  className={"mb-2"}>
95
- Describe your collection to AI:
96
+ {t("describe_collection_ai")}
96
97
  </Typography>
97
98
 
98
99
  <AICollectionGeneratorPopover
@@ -107,7 +108,7 @@ export function CollectionEditorWelcomeView({
107
108
  variant="outlined"
108
109
  startIcon={<AIIcon size="small" />}
109
110
  >
110
- Generate with AI
111
+ {t("generate_with_ai")}
111
112
  </Button>
112
113
  }
113
114
  />
@@ -118,7 +119,7 @@ export function CollectionEditorWelcomeView({
118
119
  <Typography variant={"caption"}
119
120
  color={"secondary"}
120
121
  className={"mb-2"}>
121
- Create from JSON configuration:
122
+ {t("create_from_json_config")}
122
123
  </Typography>
123
124
 
124
125
  <Button
@@ -126,7 +127,7 @@ export function CollectionEditorWelcomeView({
126
127
  onClick={() => setJsonImportOpen(true)}
127
128
  startIcon={<CodeIcon size="small" />}
128
129
  >
129
- Paste JSON Configuration
130
+ {t("paste_json_config")}
130
131
  </Button>
131
132
 
132
133
  <CollectionJsonImportDialog
@@ -149,7 +150,7 @@ export function CollectionEditorWelcomeView({
149
150
  <Typography variant={"caption"}
150
151
  color={"secondary"}
151
152
  className={"mb-2"}>
152
- Create a collection from a file (csv, json, xls, xslx...)
153
+ {t("create_collection_from_file_formats")}
153
154
  </Typography>
154
155
 
155
156
  <ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)} />
@@ -159,34 +160,34 @@ export function CollectionEditorWelcomeView({
159
160
  <div className={"my-2"}>
160
161
  <Typography variant={"caption"}
161
162
  color={"secondary"}>
162
- Select a template:
163
+ {t("select_template")}
163
164
  </Typography>
164
165
 
165
166
  <div className={"flex gap-2"}>
166
- <TemplateButton title={"Products"}
167
- subtitle={"A collection of products with images, prices and stock"}
167
+ <TemplateButton title={t("products")}
168
+ subtitle={t("collection_products_subtitle")}
168
169
  icon={<Icon size={"small"}
169
170
  iconKey={productsCollectionTemplate.icon! as string} />}
170
171
  onClick={() => {
171
172
  setValues(productsCollectionTemplate);
172
173
  onContinue();
173
174
  }} />
174
- <TemplateButton title={"Users"}
175
- subtitle={"A collection of users with emails, names and roles"}
175
+ <TemplateButton title={t("users")}
176
+ subtitle={t("collection_users_subtitle")}
176
177
  icon={<Icon size={"small"} iconKey={usersCollectionTemplate.icon! as string} />}
177
178
  onClick={() => {
178
179
  setValues(usersCollectionTemplate);
179
180
  onContinue();
180
181
  }} />
181
- <TemplateButton title={"Blog posts"}
182
- subtitle={"A collection of blog posts with images, authors and complex content"}
182
+ <TemplateButton title={t("blog_posts")}
183
+ subtitle={t("collection_blog_posts_subtitle")}
183
184
  icon={<Icon size={"small"} iconKey={blogCollectionTemplate.icon! as string} />}
184
185
  onClick={() => {
185
186
  setValues(blogCollectionTemplate);
186
187
  onContinue();
187
188
  }} />
188
- <TemplateButton title={"Pages"}
189
- subtitle={"A collection of pages with images, authors and complex content"}
189
+ <TemplateButton title={t("pages")}
190
+ subtitle={t("collection_pages_subtitle")}
190
191
  icon={<Icon size={"small"} iconKey={pagesCollectionTemplate.icon! as string} />}
191
192
  onClick={() => {
192
193
  setValues(pagesCollectionTemplate);