@emdash-cms/admin 0.1.1 → 0.3.0

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.
package/dist/index.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import { c as fetchManifest, i as fetchPlugins, l as parseApiResponse, n as enablePlugin, o as API_BASE, r as fetchPlugin, s as apiFetch, t as disablePlugin, u as throwResponseError } from "./plugins-XhZqfegd.js";
2
+ import { i as SUPPORTED_LOCALE_CODES, n as DEFAULT_LOCALE, o as resolveLocale, r as SUPPORTED_LOCALES, t as useLocale } from "./useLocale-CXsoFCFt.js";
3
+ import "./locales/index.js";
2
4
  import { Badge, Button, Checkbox, CommandPalette, Dialog, Input, InputArea, Label, LinkButton, Loader, Popover, Select, Sidebar as KumoSidebar, Switch, Tabs, Toast, Toasty, Tooltip, buttonVariants, useSidebar } from "@cloudflare/kumo";
5
+ import { i18n } from "@lingui/core";
6
+ import { I18nProvider, Trans, useLingui } from "@lingui/react";
3
7
  import { QueryClient, QueryClientProvider, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
8
  import { Link, Link as Link$1, Outlet, RouterProvider, createRootRouteWithContext, createRoute, createRouter, useLocation, useNavigate, useNavigate as useNavigate$1, useParams, useParams as useParams$1, useSearch } from "@tanstack/react-router";
5
9
  import * as React from "react";
@@ -200,9 +204,11 @@ function getMutationError(error) {
200
204
  /** Inline error banner for use inside dialogs. */
201
205
  function DialogError({ message, className }) {
202
206
  if (!message) return null;
207
+ const lines = message.split("\n");
203
208
  return /* @__PURE__ */ jsx("div", {
209
+ role: "alert",
204
210
  className: cn("rounded-md bg-kumo-danger/10 p-3 text-sm text-kumo-danger", className),
205
- children: message
211
+ children: lines.map((line, i) => /* @__PURE__ */ jsx("div", { children: line }, i))
206
212
  });
207
213
  }
208
214
 
@@ -2033,44 +2039,19 @@ async function executeWpPluginImport(url, token, config) {
2033
2039
  /**
2034
2040
  * API token management client functions
2035
2041
  */
2036
- /** Available scopes for API tokens */
2037
- const API_TOKEN_SCOPES = [
2038
- {
2039
- value: "content:read",
2040
- label: "Content Read",
2041
- description: "Read content entries"
2042
- },
2043
- {
2044
- value: "content:write",
2045
- label: "Content Write",
2046
- description: "Create, update, delete content"
2047
- },
2048
- {
2049
- value: "media:read",
2050
- label: "Media Read",
2051
- description: "Read media files"
2052
- },
2053
- {
2054
- value: "media:write",
2055
- label: "Media Write",
2056
- description: "Upload and delete media"
2057
- },
2058
- {
2059
- value: "schema:read",
2060
- label: "Schema Read",
2061
- description: "Read collection schemas"
2062
- },
2063
- {
2064
- value: "schema:write",
2065
- label: "Schema Write",
2066
- description: "Modify collection schemas"
2067
- },
2068
- {
2069
- value: "admin",
2070
- label: "Admin",
2071
- description: "Full admin access"
2072
- }
2073
- ];
2042
+ /**
2043
+ * Scope strings for personal API tokens (wire + UI iteration order).
2044
+ * Human-readable copy lives in `ApiTokenSettings` (`SCOPE_UI` + Lingui).
2045
+ */
2046
+ const API_TOKEN_SCOPES = {
2047
+ ContentRead: "content:read",
2048
+ ContentWrite: "content:write",
2049
+ MediaRead: "media:read",
2050
+ MediaWrite: "media:write",
2051
+ SchemaRead: "schema:read",
2052
+ SchemaWrite: "schema:write",
2053
+ Admin: "admin"
2054
+ };
2074
2055
  /**
2075
2056
  * Fetch all API tokens for the current user
2076
2057
  */
@@ -3284,7 +3265,7 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
3284
3265
  open,
3285
3266
  onOpenChange: handleClose,
3286
3267
  children: /* @__PURE__ */ jsxs(Dialog, {
3287
- className: "p-6 max-w-4xl max-h-[80vh] flex flex-col",
3268
+ className: "p-6 max-w-4xl max-h-[80vh] flex flex-col overflow-hidden",
3288
3269
  size: "xl",
3289
3270
  children: [
3290
3271
  /* @__PURE__ */ jsxs("div", {
@@ -3412,7 +3393,7 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
3412
3393
  className: "mb-3"
3413
3394
  }),
3414
3395
  /* @__PURE__ */ jsx("div", {
3415
- className: "flex-1 overflow-y-auto min-h-[300px]",
3396
+ className: "flex-1 overflow-y-auto min-h-0",
3416
3397
  children: isLoading ? /* @__PURE__ */ jsx("div", {
3417
3398
  className: "flex items-center justify-center h-full",
3418
3399
  children: /* @__PURE__ */ jsx(Loader, {})
@@ -4366,7 +4347,10 @@ var index_default = Suggestion;
4366
4347
  const blockTransforms = [
4367
4348
  {
4368
4349
  id: "paragraph",
4369
- label: "Paragraph",
4350
+ label: {
4351
+ id: "bkQRMh",
4352
+ message: "Paragraph"
4353
+ },
4370
4354
  icon: Paragraph,
4371
4355
  transform: (editor) => {
4372
4356
  editor.chain().focus().setNode("paragraph").run();
@@ -4374,7 +4358,10 @@ const blockTransforms = [
4374
4358
  },
4375
4359
  {
4376
4360
  id: "heading1",
4377
- label: "Heading 1",
4361
+ label: {
4362
+ id: "lXKZGw",
4363
+ message: "Heading 1"
4364
+ },
4378
4365
  icon: TextHOne,
4379
4366
  transform: (editor) => {
4380
4367
  editor.chain().focus().setNode("heading", { level: 1 }).run();
@@ -4382,7 +4369,10 @@ const blockTransforms = [
4382
4369
  },
4383
4370
  {
4384
4371
  id: "heading2",
4385
- label: "Heading 2",
4372
+ label: {
4373
+ id: "El7NbA",
4374
+ message: "Heading 2"
4375
+ },
4386
4376
  icon: TextHTwo,
4387
4377
  transform: (editor) => {
4388
4378
  editor.chain().focus().setNode("heading", { level: 2 }).run();
@@ -4390,7 +4380,10 @@ const blockTransforms = [
4390
4380
  },
4391
4381
  {
4392
4382
  id: "heading3",
4393
- label: "Heading 3",
4383
+ label: {
4384
+ id: "SFN6dN",
4385
+ message: "Heading 3"
4386
+ },
4394
4387
  icon: TextHThree,
4395
4388
  transform: (editor) => {
4396
4389
  editor.chain().focus().setNode("heading", { level: 3 }).run();
@@ -4398,7 +4391,10 @@ const blockTransforms = [
4398
4391
  },
4399
4392
  {
4400
4393
  id: "blockquote",
4401
- label: "Quote",
4394
+ label: {
4395
+ id: "ZhhOwV",
4396
+ message: "Quote"
4397
+ },
4402
4398
  icon: Quotes,
4403
4399
  transform: (editor) => {
4404
4400
  editor.chain().focus().toggleBlockquote().run();
@@ -4406,7 +4402,10 @@ const blockTransforms = [
4406
4402
  },
4407
4403
  {
4408
4404
  id: "codeBlock",
4409
- label: "Code Block",
4405
+ label: {
4406
+ id: "N2eHWq",
4407
+ message: "Code Block"
4408
+ },
4410
4409
  icon: Code,
4411
4410
  transform: (editor) => {
4412
4411
  editor.chain().focus().toggleCodeBlock().run();
@@ -4414,7 +4413,10 @@ const blockTransforms = [
4414
4413
  },
4415
4414
  {
4416
4415
  id: "bulletList",
4417
- label: "Bullet List",
4416
+ label: {
4417
+ id: "s1c0ja",
4418
+ message: "Bullet List"
4419
+ },
4418
4420
  icon: List,
4419
4421
  transform: (editor) => {
4420
4422
  editor.chain().focus().toggleBulletList().run();
@@ -4422,7 +4424,10 @@ const blockTransforms = [
4422
4424
  },
4423
4425
  {
4424
4426
  id: "orderedList",
4425
- label: "Numbered List",
4427
+ label: {
4428
+ id: "upFPtm",
4429
+ message: "Numbered List"
4430
+ },
4426
4431
  icon: ListNumbers,
4427
4432
  transform: (editor) => {
4428
4433
  editor.chain().focus().toggleOrderedList().run();
@@ -4433,6 +4438,7 @@ const blockTransforms = [
4433
4438
  * Block Menu - floating menu for block-level actions
4434
4439
  */
4435
4440
  function BlockMenu({ editor, anchorElement, isOpen, onClose }) {
4441
+ const { _: _t } = useLingui();
4436
4442
  const [showTransforms, setShowTransforms] = React.useState(false);
4437
4443
  const menuRef = React.useRef(null);
4438
4444
  const stableOnClose = useStableCallback(onClose);
@@ -4529,7 +4535,7 @@ function BlockMenu({ editor, anchorElement, isOpen, onClose }) {
4529
4535
  type: "button",
4530
4536
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm hover:bg-kumo-tint text-left",
4531
4537
  onClick: () => handleTransform(transform),
4532
- children: [/* @__PURE__ */ jsx(transform.icon, { className: "h-4 w-4 text-kumo-subtle" }), /* @__PURE__ */ jsx("span", { children: transform.label })]
4538
+ children: [/* @__PURE__ */ jsx(transform.icon, { className: "h-4 w-4 text-kumo-subtle" }), /* @__PURE__ */ jsx("span", { children: _t(transform.label) })]
4533
4539
  }, transform.id))
4534
4540
  ]
4535
4541
  }) : /* @__PURE__ */ jsxs("div", {
@@ -5774,8 +5780,14 @@ function convertPTMarks(marks, markDefs) {
5774
5780
  const defaultSlashCommands = [
5775
5781
  {
5776
5782
  id: "heading1",
5777
- title: "Heading 1",
5778
- description: "Large section heading",
5783
+ title: {
5784
+ id: "lXKZGw",
5785
+ message: "Heading 1"
5786
+ },
5787
+ description: {
5788
+ id: "59o/ag",
5789
+ message: "Large section heading"
5790
+ },
5779
5791
  icon: TextHOne,
5780
5792
  aliases: ["h1", "title"],
5781
5793
  command: ({ editor, range }) => {
@@ -5784,8 +5796,14 @@ const defaultSlashCommands = [
5784
5796
  },
5785
5797
  {
5786
5798
  id: "heading2",
5787
- title: "Heading 2",
5788
- description: "Medium section heading",
5799
+ title: {
5800
+ id: "El7NbA",
5801
+ message: "Heading 2"
5802
+ },
5803
+ description: {
5804
+ id: "IAcjOi",
5805
+ message: "Medium section heading"
5806
+ },
5789
5807
  icon: TextHTwo,
5790
5808
  aliases: ["h2", "subtitle"],
5791
5809
  command: ({ editor, range }) => {
@@ -5794,8 +5812,14 @@ const defaultSlashCommands = [
5794
5812
  },
5795
5813
  {
5796
5814
  id: "heading3",
5797
- title: "Heading 3",
5798
- description: "Small section heading",
5815
+ title: {
5816
+ id: "SFN6dN",
5817
+ message: "Heading 3"
5818
+ },
5819
+ description: {
5820
+ id: "Ik7fz5",
5821
+ message: "Small section heading"
5822
+ },
5799
5823
  icon: TextHThree,
5800
5824
  aliases: ["h3"],
5801
5825
  command: ({ editor, range }) => {
@@ -5804,8 +5828,14 @@ const defaultSlashCommands = [
5804
5828
  },
5805
5829
  {
5806
5830
  id: "bulletList",
5807
- title: "Bullet List",
5808
- description: "Create a bullet list",
5831
+ title: {
5832
+ id: "s1c0ja",
5833
+ message: "Bullet List"
5834
+ },
5835
+ description: {
5836
+ id: "md7u2X",
5837
+ message: "Create a bullet list"
5838
+ },
5809
5839
  icon: List,
5810
5840
  aliases: ["ul", "unordered"],
5811
5841
  command: ({ editor, range }) => {
@@ -5814,8 +5844,14 @@ const defaultSlashCommands = [
5814
5844
  },
5815
5845
  {
5816
5846
  id: "numberedList",
5817
- title: "Numbered List",
5818
- description: "Create a numbered list",
5847
+ title: {
5848
+ id: "upFPtm",
5849
+ message: "Numbered List"
5850
+ },
5851
+ description: {
5852
+ id: "fh33rM",
5853
+ message: "Create a numbered list"
5854
+ },
5819
5855
  icon: ListNumbers,
5820
5856
  aliases: ["ol", "ordered"],
5821
5857
  command: ({ editor, range }) => {
@@ -5824,8 +5860,14 @@ const defaultSlashCommands = [
5824
5860
  },
5825
5861
  {
5826
5862
  id: "quote",
5827
- title: "Quote",
5828
- description: "Insert a blockquote",
5863
+ title: {
5864
+ id: "ZhhOwV",
5865
+ message: "Quote"
5866
+ },
5867
+ description: {
5868
+ id: "l+2nGM",
5869
+ message: "Insert a blockquote"
5870
+ },
5829
5871
  icon: Quotes,
5830
5872
  aliases: ["blockquote", "cite"],
5831
5873
  command: ({ editor, range }) => {
@@ -5834,8 +5876,14 @@ const defaultSlashCommands = [
5834
5876
  },
5835
5877
  {
5836
5878
  id: "codeBlock",
5837
- title: "Code Block",
5838
- description: "Insert a code block",
5879
+ title: {
5880
+ id: "N2eHWq",
5881
+ message: "Code Block"
5882
+ },
5883
+ description: {
5884
+ id: "12gVka",
5885
+ message: "Insert a code block"
5886
+ },
5839
5887
  icon: CodeBlock,
5840
5888
  aliases: [
5841
5889
  "code",
@@ -5848,8 +5896,14 @@ const defaultSlashCommands = [
5848
5896
  },
5849
5897
  {
5850
5898
  id: "divider",
5851
- title: "Divider",
5852
- description: "Insert a horizontal rule",
5899
+ title: {
5900
+ id: "R8AthW",
5901
+ message: "Divider"
5902
+ },
5903
+ description: {
5904
+ id: "9Jd2n5",
5905
+ message: "Insert a horizontal rule"
5906
+ },
5853
5907
  icon: Minus,
5854
5908
  aliases: [
5855
5909
  "hr",
@@ -5958,6 +6012,7 @@ function createSlashCommandsExtension(options) {
5958
6012
  * Slash command menu component using Floating UI
5959
6013
  */
5960
6014
  function SlashCommandMenu({ state, onCommand, onClose: _onClose, setSelectedIndex }) {
6015
+ const { _: _t } = useLingui();
5961
6016
  const containerRef = React.useRef(null);
5962
6017
  const { refs, floatingStyles } = useFloating({
5963
6018
  open: state.isOpen,
@@ -5992,7 +6047,10 @@ function SlashCommandMenu({ state, onCommand, onClose: _onClose, setSelectedInde
5992
6047
  className: "z-[100] rounded-lg border bg-kumo-overlay p-1 shadow-lg min-w-[220px] max-h-[300px] overflow-y-auto",
5993
6048
  children: state.items.length === 0 ? /* @__PURE__ */ jsx("p", {
5994
6049
  className: "text-sm text-kumo-subtle px-3 py-2",
5995
- children: "No results"
6050
+ children: _t({
6051
+ id: "Ev2r9A",
6052
+ message: "No results"
6053
+ })
5996
6054
  }) : state.items.map((item, index) => /* @__PURE__ */ jsxs("button", {
5997
6055
  type: "button",
5998
6056
  "data-index": index,
@@ -6003,10 +6061,10 @@ function SlashCommandMenu({ state, onCommand, onClose: _onClose, setSelectedInde
6003
6061
  className: "flex flex-col",
6004
6062
  children: [/* @__PURE__ */ jsx("span", {
6005
6063
  className: "font-medium",
6006
- children: item.title
6064
+ children: typeof item.title === "string" ? item.title : _t(item.title)
6007
6065
  }), /* @__PURE__ */ jsx("span", {
6008
6066
  className: "text-xs text-kumo-subtle",
6009
- children: item.description
6067
+ children: typeof item.description === "string" ? item.description : _t(item.description)
6010
6068
  })]
6011
6069
  })]
6012
6070
  }, item.id))
@@ -6267,6 +6325,7 @@ function EditorFooter({ editor }) {
6267
6325
  * Portable Text Editor Component
6268
6326
  */
6269
6327
  function PortableTextEditor({ value, onChange, placeholder = "Start writing...", className, editable = true, "aria-labelledby": ariaLabelledby, pluginBlocks = [], focusMode: controlledFocusMode, onFocusModeChange, onEditorReady, minimal = false, onBlockSidebarOpen, onBlockSidebarClose }) {
6328
+ const { _: _t2 } = useLingui();
6270
6329
  const onChangeRef = React.useRef(onChange);
6271
6330
  React.useEffect(() => {
6272
6331
  onChangeRef.current = onChange;
@@ -6298,8 +6357,14 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
6298
6357
  const cmds = [...defaultSlashCommands];
6299
6358
  cmds.push({
6300
6359
  id: "image",
6301
- title: "Image",
6302
- description: "Insert an image",
6360
+ title: {
6361
+ id: "hG89Ed",
6362
+ message: "Image"
6363
+ },
6364
+ description: {
6365
+ id: "3Lcj6W",
6366
+ message: "Insert an image"
6367
+ },
6303
6368
  icon: Image$1,
6304
6369
  aliases: [
6305
6370
  "img",
@@ -6307,7 +6372,10 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
6307
6372
  "picture",
6308
6373
  "url"
6309
6374
  ],
6310
- category: "Media",
6375
+ category: {
6376
+ id: "xYilR2",
6377
+ message: "Media"
6378
+ },
6311
6379
  command: ({ editor, range }) => {
6312
6380
  editor.chain().focus().deleteRange(range).run();
6313
6381
  setMediaPickerOpen(true);
@@ -6315,15 +6383,24 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
6315
6383
  });
6316
6384
  cmds.push({
6317
6385
  id: "section",
6318
- title: "Section",
6319
- description: "Insert a reusable section",
6386
+ title: {
6387
+ id: "BhZ5PL",
6388
+ message: "Section"
6389
+ },
6390
+ description: {
6391
+ id: "Pkleyv",
6392
+ message: "Insert a reusable section"
6393
+ },
6320
6394
  icon: Stack,
6321
6395
  aliases: [
6322
6396
  "pattern",
6323
6397
  "block",
6324
6398
  "template"
6325
6399
  ],
6326
- category: "Content",
6400
+ category: {
6401
+ id: "4b3oEV",
6402
+ message: "Content"
6403
+ },
6327
6404
  command: ({ editor, range }) => {
6328
6405
  editor.chain().focus().deleteRange(range).run();
6329
6406
  setSectionPickerOpen(true);
@@ -6332,25 +6409,36 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
6332
6409
  for (const block of pluginBlocks) cmds.push({
6333
6410
  id: `plugin-${block.pluginId}-${block.type}`,
6334
6411
  title: block.label,
6335
- description: block.description || `Embed a ${block.label.toLowerCase()}`,
6412
+ description: block.description ?? _t2({
6413
+ id: "g0zqOw",
6414
+ message: "Embed a {0}",
6415
+ values: { 0: block.label }
6416
+ }),
6336
6417
  icon: resolveIcon(block.icon),
6337
6418
  aliases: [block.type],
6338
- category: "Embeds",
6419
+ category: {
6420
+ id: "aTofd0",
6421
+ message: "Embeds"
6422
+ },
6339
6423
  command: ({ editor, range }) => {
6340
6424
  editor.chain().focus().deleteRange(range).run();
6341
6425
  setPluginBlockModal(block);
6342
6426
  }
6343
6427
  });
6344
6428
  return cmds;
6345
- }, [pluginBlocks]);
6429
+ }, [pluginBlocks, _t2]);
6346
6430
  const filterCommandsRef = React.useRef((_q) => []);
6347
6431
  filterCommandsRef.current = (query) => {
6348
6432
  if (!query) return slashCommands;
6349
6433
  const searchText = query.toLowerCase();
6350
6434
  const titleMatches = [];
6351
6435
  const otherMatches = [];
6352
- for (const item of slashCommands) if (item.title.toLowerCase().includes(searchText)) titleMatches.push(item);
6353
- else if (item.description.toLowerCase().includes(searchText) || item.aliases?.some((alias) => alias.toLowerCase().includes(searchText))) otherMatches.push(item);
6436
+ for (const item of slashCommands) {
6437
+ const titleStr = typeof item.title === "string" ? item.title : _t2(item.title);
6438
+ const descStr = typeof item.description === "string" ? item.description : _t2(item.description);
6439
+ if (titleStr.toLowerCase().includes(searchText)) titleMatches.push(item);
6440
+ else if (descStr.toLowerCase().includes(searchText) || item.aliases?.some((alias) => alias.toLowerCase().includes(searchText))) otherMatches.push(item);
6441
+ }
6354
6442
  return [...titleMatches, ...otherMatches];
6355
6443
  };
6356
6444
  const initialContent = React.useMemo(() => portableTextToProsemirror(value || []), []);
@@ -8112,43 +8200,40 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
8112
8200
  isSaving: isSaving || false
8113
8201
  }),
8114
8202
  !isNew && /* @__PURE__ */ jsxs(Fragment, { children: [
8115
- supportsDrafts && hasPendingChanges && onDiscardDraft && /* @__PURE__ */ jsxs(Dialog.Root, {
8116
- disablePointerDismissal: true,
8117
- children: [/* @__PURE__ */ jsx(Dialog.Trigger, { render: (p) => /* @__PURE__ */ jsx(Button, {
8118
- ...p,
8119
- type: "button",
8120
- variant: "outline",
8121
- size: "sm",
8122
- icon: /* @__PURE__ */ jsx(X, {}),
8123
- children: "Discard changes"
8124
- }) }), /* @__PURE__ */ jsxs(Dialog, {
8125
- className: "p-6",
8126
- size: "sm",
8127
- children: [
8128
- /* @__PURE__ */ jsx(Dialog.Title, {
8129
- className: "text-lg font-semibold",
8130
- children: "Discard draft changes?"
8131
- }),
8132
- /* @__PURE__ */ jsx(Dialog.Description, {
8133
- className: "text-kumo-subtle",
8134
- children: "This will revert to the published version. Your draft changes will be lost."
8135
- }),
8136
- /* @__PURE__ */ jsxs("div", {
8137
- className: "mt-6 flex justify-end gap-2",
8138
- children: [/* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
8139
- ...p,
8140
- variant: "secondary",
8141
- children: "Cancel"
8142
- }) }), /* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
8143
- ...p,
8144
- variant: "destructive",
8145
- onClick: onDiscardDraft,
8146
- children: "Discard changes"
8147
- }) })]
8148
- })
8149
- ]
8150
- })]
8151
- }),
8203
+ supportsDrafts && hasPendingChanges && onDiscardDraft && /* @__PURE__ */ jsxs(Dialog.Root, { children: [/* @__PURE__ */ jsx(Dialog.Trigger, { render: (p) => /* @__PURE__ */ jsx(Button, {
8204
+ ...p,
8205
+ type: "button",
8206
+ variant: "outline",
8207
+ size: "sm",
8208
+ icon: /* @__PURE__ */ jsx(X, {}),
8209
+ children: "Discard changes"
8210
+ }) }), /* @__PURE__ */ jsxs(Dialog, {
8211
+ className: "p-6",
8212
+ size: "sm",
8213
+ children: [
8214
+ /* @__PURE__ */ jsx(Dialog.Title, {
8215
+ className: "text-lg font-semibold",
8216
+ children: "Discard draft changes?"
8217
+ }),
8218
+ /* @__PURE__ */ jsx(Dialog.Description, {
8219
+ className: "text-kumo-subtle",
8220
+ children: "This will revert to the published version. Your draft changes will be lost."
8221
+ }),
8222
+ /* @__PURE__ */ jsxs("div", {
8223
+ className: "mt-6 flex justify-end gap-2",
8224
+ children: [/* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
8225
+ ...p,
8226
+ variant: "secondary",
8227
+ children: "Cancel"
8228
+ }) }), /* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
8229
+ ...p,
8230
+ variant: "destructive",
8231
+ onClick: onDiscardDraft,
8232
+ children: "Discard changes"
8233
+ }) })]
8234
+ })
8235
+ ]
8236
+ })] }),
8152
8237
  isLive ? /* @__PURE__ */ jsx(Fragment, { children: hasPendingChanges ? /* @__PURE__ */ jsx(Button, {
8153
8238
  type: "button",
8154
8239
  variant: "primary",
@@ -8246,6 +8331,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
8246
8331
  children: supportsDrafts ? /* @__PURE__ */ jsxs(Fragment, { children: [
8247
8332
  isLive && /* @__PURE__ */ jsx(Badge, {
8248
8333
  variant: "primary",
8334
+ className: "text-white",
8249
8335
  children: "Published"
8250
8336
  }),
8251
8337
  hasPendingChanges && /* @__PURE__ */ jsx(Badge, {
@@ -8538,27 +8624,31 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
8538
8624
  required: field.required
8539
8625
  });
8540
8626
  case "boolean": return /* @__PURE__ */ jsx(Switch, {
8627
+ id,
8541
8628
  label,
8542
8629
  checked: typeof value === "boolean" ? value : false,
8543
8630
  onCheckedChange: handleChange
8544
8631
  });
8545
8632
  case "portableText": {
8546
8633
  const labelId = `${id}-label`;
8547
- return /* @__PURE__ */ jsxs("div", { children: [!minimal && /* @__PURE__ */ jsx("span", {
8548
- id: labelId,
8549
- className: cn("text-sm font-medium leading-none text-kumo-default", labelClass),
8550
- children: label
8551
- }), /* @__PURE__ */ jsx(PortableTextEditor, {
8552
- value: Array.isArray(value) ? value : [],
8553
- onChange: handleChange,
8554
- placeholder: `Enter ${label.toLowerCase()}...`,
8555
- "aria-labelledby": labelId,
8556
- pluginBlocks,
8557
- onEditorReady,
8558
- minimal,
8559
- onBlockSidebarOpen,
8560
- onBlockSidebarClose
8561
- })] });
8634
+ return /* @__PURE__ */ jsxs("div", {
8635
+ id,
8636
+ children: [!minimal && /* @__PURE__ */ jsx("span", {
8637
+ id: labelId,
8638
+ className: cn("text-sm font-medium leading-none text-kumo-default", labelClass),
8639
+ children: label
8640
+ }), /* @__PURE__ */ jsx(PortableTextEditor, {
8641
+ value: Array.isArray(value) ? value : [],
8642
+ onChange: handleChange,
8643
+ placeholder: `Enter ${label.toLowerCase()}...`,
8644
+ "aria-labelledby": labelId,
8645
+ pluginBlocks,
8646
+ onEditorReady,
8647
+ minimal,
8648
+ onBlockSidebarOpen,
8649
+ onBlockSidebarClose
8650
+ })]
8651
+ });
8562
8652
  }
8563
8653
  case "richText": return /* @__PURE__ */ jsx(InputArea, {
8564
8654
  label,
@@ -8572,6 +8662,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
8572
8662
  const selectItems = {};
8573
8663
  for (const opt of field.options ?? []) selectItems[opt.value] = opt.label;
8574
8664
  return /* @__PURE__ */ jsx(Select, {
8665
+ id,
8575
8666
  label,
8576
8667
  value: typeof value === "string" ? value : "",
8577
8668
  onValueChange: (v) => handleChange(v ?? ""),
@@ -8610,6 +8701,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
8610
8701
  required: field.required
8611
8702
  });
8612
8703
  case "image": return /* @__PURE__ */ jsx(ImageFieldRenderer, {
8704
+ id,
8613
8705
  label,
8614
8706
  description: name === "featured_image" ? "Used as the main visual for this post on listing pages and at the top of the post" : void 0,
8615
8707
  value: value != null && typeof value === "object" ? value : void 0,
@@ -8639,7 +8731,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
8639
8731
  });
8640
8732
  }
8641
8733
  }
8642
- function ImageFieldRenderer({ label, description, value, onChange, required }) {
8734
+ function ImageFieldRenderer({ id, label, description, value, onChange, required }) {
8643
8735
  const [pickerOpen, setPickerOpen] = React.useState(false);
8644
8736
  const displayUrl = typeof value === "string" ? value : value?.previewUrl || value?.src || (value && (!value.provider || value.provider === "local") ? `/_emdash/api/media/file/${typeof value.meta?.storageKey === "string" ? value.meta.storageKey : value.id}` : void 0);
8645
8737
  const handleSelect = (item) => {
@@ -8660,58 +8752,61 @@ function ImageFieldRenderer({ label, description, value, onChange, required }) {
8660
8752
  const handleRemove = () => {
8661
8753
  onChange(void 0);
8662
8754
  };
8663
- return /* @__PURE__ */ jsxs("div", { children: [
8664
- /* @__PURE__ */ jsx(Label, { children: label }),
8665
- displayUrl ? /* @__PURE__ */ jsxs("div", {
8666
- className: "mt-2 relative group",
8667
- children: [/* @__PURE__ */ jsx("img", {
8668
- src: displayUrl,
8669
- alt: "",
8670
- className: "max-h-48 rounded-lg border object-cover"
8671
- }), /* @__PURE__ */ jsxs("div", {
8672
- className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1",
8673
- children: [/* @__PURE__ */ jsx(Button, {
8674
- type: "button",
8675
- size: "sm",
8676
- variant: "secondary",
8677
- onClick: () => setPickerOpen(true),
8678
- children: "Change"
8679
- }), /* @__PURE__ */ jsx(Button, {
8680
- type: "button",
8681
- shape: "square",
8682
- variant: "destructive",
8683
- className: "h-8 w-8",
8684
- onClick: handleRemove,
8685
- "aria-label": "Remove image",
8686
- children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
8755
+ return /* @__PURE__ */ jsxs("div", {
8756
+ id,
8757
+ children: [
8758
+ /* @__PURE__ */ jsx(Label, { children: label }),
8759
+ displayUrl ? /* @__PURE__ */ jsxs("div", {
8760
+ className: "mt-2 relative group",
8761
+ children: [/* @__PURE__ */ jsx("img", {
8762
+ src: displayUrl,
8763
+ alt: "",
8764
+ className: "max-h-48 rounded-lg border object-cover"
8765
+ }), /* @__PURE__ */ jsxs("div", {
8766
+ className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1",
8767
+ children: [/* @__PURE__ */ jsx(Button, {
8768
+ type: "button",
8769
+ size: "sm",
8770
+ variant: "secondary",
8771
+ onClick: () => setPickerOpen(true),
8772
+ children: "Change"
8773
+ }), /* @__PURE__ */ jsx(Button, {
8774
+ type: "button",
8775
+ shape: "square",
8776
+ variant: "destructive",
8777
+ className: "h-8 w-8",
8778
+ onClick: handleRemove,
8779
+ "aria-label": "Remove image",
8780
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
8781
+ })]
8687
8782
  })]
8688
- })]
8689
- }) : /* @__PURE__ */ jsx(Button, {
8690
- type: "button",
8691
- variant: "outline",
8692
- className: "mt-2 w-full h-32 border-dashed",
8693
- onClick: () => setPickerOpen(true),
8694
- children: /* @__PURE__ */ jsxs("div", {
8695
- className: "flex flex-col items-center gap-2 text-kumo-subtle",
8696
- children: [/* @__PURE__ */ jsx(Image$1, { className: "h-8 w-8" }), /* @__PURE__ */ jsx("span", { children: "Select image" })]
8783
+ }) : /* @__PURE__ */ jsx(Button, {
8784
+ type: "button",
8785
+ variant: "outline",
8786
+ className: "mt-2 w-full h-32 border-dashed",
8787
+ onClick: () => setPickerOpen(true),
8788
+ children: /* @__PURE__ */ jsxs("div", {
8789
+ className: "flex flex-col items-center gap-2 text-kumo-subtle",
8790
+ children: [/* @__PURE__ */ jsx(Image$1, { className: "h-8 w-8" }), /* @__PURE__ */ jsx("span", { children: "Select image" })]
8791
+ })
8792
+ }),
8793
+ /* @__PURE__ */ jsx(MediaPickerModal, {
8794
+ open: pickerOpen,
8795
+ onOpenChange: setPickerOpen,
8796
+ onSelect: handleSelect,
8797
+ mimeTypeFilter: "image/",
8798
+ title: `Select ${label}`
8799
+ }),
8800
+ description && /* @__PURE__ */ jsx("p", {
8801
+ className: "text-xs text-kumo-subtle mt-1",
8802
+ children: description
8803
+ }),
8804
+ required && !displayUrl && /* @__PURE__ */ jsx("p", {
8805
+ className: "text-sm text-kumo-danger mt-1",
8806
+ children: "This field is required"
8697
8807
  })
8698
- }),
8699
- /* @__PURE__ */ jsx(MediaPickerModal, {
8700
- open: pickerOpen,
8701
- onOpenChange: setPickerOpen,
8702
- onSelect: handleSelect,
8703
- mimeTypeFilter: "image/",
8704
- title: `Select ${label}`
8705
- }),
8706
- description && /* @__PURE__ */ jsx("p", {
8707
- className: "text-xs text-kumo-subtle mt-1",
8708
- children: description
8709
- }),
8710
- required && !displayUrl && /* @__PURE__ */ jsx("p", {
8711
- className: "text-sm text-kumo-danger mt-1",
8712
- children: "This field is required"
8713
- })
8714
- ] });
8808
+ ]
8809
+ });
8715
8810
  }
8716
8811
  function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuickEdit }) {
8717
8812
  const [selectedBylineId, setSelectedBylineId] = React.useState("");
@@ -8891,7 +8986,10 @@ function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuic
8891
8986
  children: [/* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
8892
8987
  ...p,
8893
8988
  variant: "secondary",
8894
- onClick: resetQuickCreate,
8989
+ onClick: (e) => {
8990
+ resetQuickCreate();
8991
+ p.onClick?.(e);
8992
+ },
8895
8993
  children: "Cancel"
8896
8994
  }) }), /* @__PURE__ */ jsx(Button, {
8897
8995
  type: "button",
@@ -9035,6 +9133,7 @@ function AuthorSelector({ authorId, users, onChange }) {
9035
9133
  * Only renders when i18n is configured (manifest.i18n is present).
9036
9134
  */
9037
9135
  function LocaleSwitcher({ locales, defaultLocale, value, onChange, showAll = false, className, size = "md" }) {
9136
+ const { _: _t } = useLingui();
9038
9137
  return /* @__PURE__ */ jsxs("div", {
9039
9138
  className: cn("flex items-center gap-1.5", className),
9040
9139
  children: [/* @__PURE__ */ jsx(GlobeSimple, {
@@ -9043,14 +9142,23 @@ function LocaleSwitcher({ locales, defaultLocale, value, onChange, showAll = fal
9043
9142
  }), /* @__PURE__ */ jsxs("select", {
9044
9143
  value,
9045
9144
  onChange: (e) => onChange(e.target.value),
9046
- "aria-label": "Locale",
9145
+ "aria-label": _t({
9146
+ id: "8NbHF7",
9147
+ message: "Locale"
9148
+ }),
9047
9149
  className: cn("rounded-md border bg-transparent font-medium transition-colors", "focus:ring-kumo-ring focus:outline-none focus:ring-2 focus:ring-offset-1", "hover:bg-kumo-tint/50 cursor-pointer", size === "sm" ? "px-1.5 py-0.5 text-xs" : "px-2 py-1 text-sm"),
9048
9150
  children: [showAll && /* @__PURE__ */ jsx("option", {
9049
9151
  value: "",
9050
- children: "All locales"
9152
+ children: _t({
9153
+ id: "JjOi48",
9154
+ message: "All locales"
9155
+ })
9051
9156
  }), locales.map((locale) => /* @__PURE__ */ jsxs("option", {
9052
9157
  value: locale,
9053
- children: [locale.toUpperCase(), locale === defaultLocale ? " (default)" : ""]
9158
+ children: [locale.toUpperCase(), locale === defaultLocale ? _t({
9159
+ id: "FozKV6",
9160
+ message: " (default)"
9161
+ }) : ""]
9054
9162
  }, locale))]
9055
9163
  })]
9056
9164
  });
@@ -9082,6 +9190,15 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
9082
9190
  }, [items, searchQuery]);
9083
9191
  const totalPages = Math.max(1, Math.ceil(filteredItems.length / PAGE_SIZE));
9084
9192
  const paginatedItems = filteredItems.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
9193
+ React.useEffect(() => {
9194
+ if (page >= totalPages - 1 && hasMore && onLoadMore && !searchQuery) onLoadMore();
9195
+ }, [
9196
+ page,
9197
+ totalPages,
9198
+ hasMore,
9199
+ onLoadMore,
9200
+ searchQuery
9201
+ ]);
9085
9202
  return /* @__PURE__ */ jsxs("div", {
9086
9203
  className: "space-y-4",
9087
9204
  children: [
@@ -9102,6 +9219,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
9102
9219
  }), /* @__PURE__ */ jsxs(Link$1, {
9103
9220
  to: "/content/$collection/new",
9104
9221
  params: { collection },
9222
+ search: { locale: activeLocale },
9105
9223
  className: buttonVariants(),
9106
9224
  children: [/* @__PURE__ */ jsx(Plus, {
9107
9225
  className: "mr-2 h-4 w-4",
@@ -9192,6 +9310,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
9192
9310
  /* @__PURE__ */ jsx(Link$1, {
9193
9311
  to: "/content/$collection/new",
9194
9312
  params: { collection },
9313
+ search: { locale: activeLocale },
9195
9314
  className: "text-kumo-brand underline",
9196
9315
  children: "Create your first one"
9197
9316
  })
@@ -9220,6 +9339,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
9220
9339
  className: "text-sm text-kumo-subtle",
9221
9340
  children: [
9222
9341
  filteredItems.length,
9342
+ hasMore && !searchQuery ? "+" : "",
9223
9343
  " ",
9224
9344
  filteredItems.length === 1 ? "item" : "items",
9225
9345
  searchQuery && ` matching "${searchQuery}"`
@@ -10092,71 +10212,128 @@ const SLUG_LEADING_TRAILING_PATTERN = /^_|_$/g;
10092
10212
  const SUPPORT_OPTIONS = [
10093
10213
  {
10094
10214
  value: "drafts",
10095
- label: "Drafts",
10096
- description: "Save content as draft before publishing"
10215
+ label: {
10216
+ id: "M8kJqa",
10217
+ message: "Drafts"
10218
+ },
10219
+ description: {
10220
+ id: "ZpP0HR",
10221
+ message: "Save content as draft before publishing"
10222
+ }
10097
10223
  },
10098
10224
  {
10099
10225
  value: "revisions",
10100
- label: "Revisions",
10101
- description: "Track content history"
10226
+ label: {
10227
+ id: "ZdWn/b",
10228
+ message: "Revisions"
10229
+ },
10230
+ description: {
10231
+ id: "9kGBt9",
10232
+ message: "Track content history"
10233
+ }
10102
10234
  },
10103
10235
  {
10104
10236
  value: "preview",
10105
- label: "Preview",
10106
- description: "Preview content before publishing"
10237
+ label: {
10238
+ id: "rdUucN",
10239
+ message: "Preview"
10240
+ },
10241
+ description: {
10242
+ id: "KRB02E",
10243
+ message: "Preview content before publishing"
10244
+ }
10107
10245
  },
10108
10246
  {
10109
10247
  value: "search",
10110
- label: "Search",
10111
- description: "Enable full-text search on this collection"
10248
+ label: {
10249
+ id: "A1taO8",
10250
+ message: "Search"
10251
+ },
10252
+ description: {
10253
+ id: "T7NoTE",
10254
+ message: "Enable full-text search on this collection"
10255
+ }
10112
10256
  }
10113
10257
  ];
10114
- /**
10115
- * System fields that exist on every collection
10116
- * These are created automatically and cannot be modified
10117
- */
10118
10258
  const SYSTEM_FIELDS = [
10119
10259
  {
10120
10260
  slug: "id",
10121
- label: "ID",
10261
+ label: {
10262
+ id: "S0kLOH",
10263
+ message: "ID"
10264
+ },
10122
10265
  type: "text",
10123
- description: "Unique identifier (ULID)"
10266
+ description: {
10267
+ id: "PUk2oG",
10268
+ message: "Unique identifier (ULID)"
10269
+ }
10124
10270
  },
10125
10271
  {
10126
10272
  slug: "slug",
10127
- label: "Slug",
10273
+ label: {
10274
+ id: "L85WcV",
10275
+ message: "Slug"
10276
+ },
10128
10277
  type: "text",
10129
- description: "URL-friendly identifier"
10278
+ description: {
10279
+ id: "f0WSdD",
10280
+ message: "URL-friendly identifier"
10281
+ }
10130
10282
  },
10131
10283
  {
10132
10284
  slug: "status",
10133
- label: "Status",
10285
+ label: {
10286
+ id: "uAQUqI",
10287
+ message: "Status"
10288
+ },
10134
10289
  type: "text",
10135
- description: "draft, published, or archived"
10290
+ description: {
10291
+ id: "khtYSH",
10292
+ message: "draft, published, or archived"
10293
+ }
10136
10294
  },
10137
10295
  {
10138
10296
  slug: "created_at",
10139
- label: "Created At",
10297
+ label: {
10298
+ id: "88kg0+",
10299
+ message: "Created At"
10300
+ },
10140
10301
  type: "datetime",
10141
- description: "When the entry was created"
10302
+ description: {
10303
+ id: "SMcuRW",
10304
+ message: "When the entry was created"
10305
+ }
10142
10306
  },
10143
10307
  {
10144
10308
  slug: "updated_at",
10145
- label: "Updated At",
10309
+ label: {
10310
+ id: "Llcakz",
10311
+ message: "Updated At"
10312
+ },
10146
10313
  type: "datetime",
10147
- description: "When the entry was last modified"
10314
+ description: {
10315
+ id: "46AzZK",
10316
+ message: "When the entry was last modified"
10317
+ }
10148
10318
  },
10149
10319
  {
10150
10320
  slug: "published_at",
10151
- label: "Published At",
10321
+ label: {
10322
+ id: "6QwXHP",
10323
+ message: "Published At"
10324
+ },
10152
10325
  type: "datetime",
10153
- description: "When the entry was published"
10326
+ description: {
10327
+ id: "MRpwV3",
10328
+ message: "When the entry was published"
10329
+ }
10154
10330
  }
10155
10331
  ];
10156
10332
  /**
10157
10333
  * Content Type editor for creating/editing collections
10158
10334
  */
10159
10335
  function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, onUpdateField, onDeleteField, onReorderFields }) {
10336
+ const { _: _t } = useLingui();
10160
10337
  useNavigate$1();
10161
10338
  const [slug, setSlug] = React.useState(collection?.slug ?? "");
10162
10339
  const [label, setLabel] = React.useState(collection?.label ?? "");
@@ -10379,10 +10556,10 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
10379
10556
  disabled: isFromCode
10380
10557
  }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
10381
10558
  className: "text-sm font-medium",
10382
- children: option.label
10559
+ children: _t(option.label)
10383
10560
  }), /* @__PURE__ */ jsx("p", {
10384
10561
  className: "text-xs text-kumo-subtle",
10385
- children: option.description
10562
+ children: _t(option.description)
10386
10563
  })] })]
10387
10564
  }, option.value))]
10388
10565
  }),
@@ -10662,6 +10839,7 @@ function FieldRow({ field, isFromCode, onEdit, onDelete }) {
10662
10839
  });
10663
10840
  }
10664
10841
  function SystemFieldRow({ field }) {
10842
+ const { _: _t2 } = useLingui();
10665
10843
  return /* @__PURE__ */ jsxs("div", {
10666
10844
  className: "flex items-center px-4 py-2 opacity-75",
10667
10845
  children: [
@@ -10674,7 +10852,7 @@ function SystemFieldRow({ field }) {
10674
10852
  children: [
10675
10853
  /* @__PURE__ */ jsx("span", {
10676
10854
  className: "font-medium text-sm",
10677
- children: field.label
10855
+ children: _t2(field.label)
10678
10856
  }),
10679
10857
  /* @__PURE__ */ jsx("code", {
10680
10858
  className: "text-xs bg-kumo-tint px-1.5 py-0.5 rounded text-kumo-subtle",
@@ -10687,7 +10865,7 @@ function SystemFieldRow({ field }) {
10687
10865
  ]
10688
10866
  }), /* @__PURE__ */ jsx("p", {
10689
10867
  className: "text-xs text-kumo-subtle mt-0.5",
10690
- children: field.description
10868
+ children: _t2(field.description)
10691
10869
  })]
10692
10870
  })
10693
10871
  ]
@@ -12076,6 +12254,7 @@ const OAUTH_PROVIDERS = [{
12076
12254
  icon: /* @__PURE__ */ jsx(GoogleIcon, { className: "h-5 w-5" })
12077
12255
  }];
12078
12256
  function MagicLinkForm({ onBack }) {
12257
+ const { _: _t } = useLingui();
12079
12258
  const [email, setEmail] = React.useState("");
12080
12259
  const [isLoading, setIsLoading] = React.useState(false);
12081
12260
  const [error, setError] = React.useState(null);
@@ -12092,11 +12271,17 @@ function MagicLinkForm({ onBack }) {
12092
12271
  });
12093
12272
  if (!response.ok) {
12094
12273
  const body = await response.json().catch(() => ({}));
12095
- throw new Error(body?.error?.message || "Failed to send magic link");
12274
+ throw new Error(body?.error?.message || _t({
12275
+ id: "dsPiA2",
12276
+ message: "Failed to send magic link"
12277
+ }));
12096
12278
  }
12097
12279
  setSent(true);
12098
12280
  } catch (err) {
12099
- setError(err instanceof Error ? err.message : "Failed to send magic link");
12281
+ setError(err instanceof Error ? err.message : _t({
12282
+ id: "dsPiA2",
12283
+ message: "Failed to send magic link"
12284
+ }));
12100
12285
  } finally {
12101
12286
  setIsLoading(false);
12102
12287
  }
@@ -12121,30 +12306,40 @@ function MagicLinkForm({ onBack }) {
12121
12306
  }),
12122
12307
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", {
12123
12308
  className: "text-xl font-semibold",
12124
- children: "Check your email"
12125
- }), /* @__PURE__ */ jsxs("p", {
12309
+ children: _t({
12310
+ id: "v4fiSg",
12311
+ message: "Check your email"
12312
+ })
12313
+ }), /* @__PURE__ */ jsx("p", {
12126
12314
  className: "text-kumo-subtle mt-2",
12127
- children: [
12128
- "If an account exists for ",
12129
- /* @__PURE__ */ jsx("span", {
12130
- className: "font-medium text-kumo-default",
12131
- children: email
12132
- }),
12133
- ", we've sent a sign-in link."
12134
- ]
12315
+ children: /* @__PURE__ */ jsx(Trans, {
12316
+ id: "HvU4EW",
12317
+ message: "If an account exists for <0>{email}</0>, we've sent a sign-in link.",
12318
+ values: { email },
12319
+ components: { 0: /* @__PURE__ */ jsx("span", { className: "font-medium text-kumo-default" }) }
12320
+ })
12135
12321
  })] }),
12136
12322
  /* @__PURE__ */ jsxs("div", {
12137
12323
  className: "text-sm text-kumo-subtle",
12138
- children: [/* @__PURE__ */ jsx("p", { children: "Click the link in the email to sign in." }), /* @__PURE__ */ jsx("p", {
12324
+ children: [/* @__PURE__ */ jsx("p", { children: _t({
12325
+ id: "tfQcxX",
12326
+ message: "Click the link in the email to sign in."
12327
+ }) }), /* @__PURE__ */ jsx("p", {
12139
12328
  className: "mt-2",
12140
- children: "The link will expire in 15 minutes."
12329
+ children: _t({
12330
+ id: "Cw9Xfg",
12331
+ message: "The link will expire in 15 minutes."
12332
+ })
12141
12333
  })]
12142
12334
  }),
12143
12335
  /* @__PURE__ */ jsx(Button, {
12144
12336
  variant: "outline",
12145
12337
  onClick: onBack,
12146
12338
  className: "mt-4 w-full justify-center",
12147
- children: "Back to login"
12339
+ children: _t({
12340
+ id: "VCoEm+",
12341
+ message: "Back to login"
12342
+ })
12148
12343
  })
12149
12344
  ]
12150
12345
  });
@@ -12153,7 +12348,10 @@ function MagicLinkForm({ onBack }) {
12153
12348
  className: "space-y-4",
12154
12349
  children: [
12155
12350
  /* @__PURE__ */ jsx(Input, {
12156
- label: "Email address",
12351
+ label: _t({
12352
+ id: "ATGYL1",
12353
+ message: "Email address"
12354
+ }),
12157
12355
  type: "email",
12158
12356
  value: email,
12159
12357
  onChange: (e) => setEmail(e.target.value),
@@ -12174,14 +12372,23 @@ function MagicLinkForm({ onBack }) {
12174
12372
  variant: "primary",
12175
12373
  loading: isLoading,
12176
12374
  disabled: !email,
12177
- children: isLoading ? "Sending..." : "Send magic link"
12375
+ children: isLoading ? _t({
12376
+ id: "IoAuJG",
12377
+ message: "Sending..."
12378
+ }) : _t({
12379
+ id: "+Ni3Gv",
12380
+ message: "Send magic link"
12381
+ })
12178
12382
  }),
12179
12383
  /* @__PURE__ */ jsx(Button, {
12180
12384
  type: "button",
12181
12385
  variant: "ghost",
12182
12386
  className: "w-full justify-center",
12183
12387
  onClick: onBack,
12184
- children: "Back to login"
12388
+ children: _t({
12389
+ id: "VCoEm+",
12390
+ message: "Back to login"
12391
+ })
12185
12392
  })
12186
12393
  ]
12187
12394
  });
@@ -12191,6 +12398,8 @@ function handleOAuthClick(providerId) {
12191
12398
  }
12192
12399
  function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12193
12400
  const safeRedirectUrl = sanitizeRedirectUrl(redirectUrl);
12401
+ const { _: _t2 } = useLingui();
12402
+ const { locale, setLocale } = useLocale();
12194
12403
  const [method, setMethod] = React.useState("passkey");
12195
12404
  const [urlError, setUrlError] = React.useState(null);
12196
12405
  const { data: manifest, isLoading: manifestLoading } = useQuery({
@@ -12205,7 +12414,11 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12205
12414
  const error = params.get("error");
12206
12415
  const message = params.get("message");
12207
12416
  if (error) {
12208
- setUrlError(message || `Authentication error: ${error}`);
12417
+ setUrlError(message || _t2({
12418
+ id: "Xeb2Gt",
12419
+ message: "Authentication error: {error}",
12420
+ values: { error }
12421
+ }));
12209
12422
  window.history.replaceState({}, "", window.location.pathname);
12210
12423
  }
12211
12424
  }, []);
@@ -12228,7 +12441,13 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12228
12441
  className: "text-center mb-8",
12229
12442
  children: [/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-2" }), /* @__PURE__ */ jsxs("h1", {
12230
12443
  className: "text-2xl font-semibold text-kumo-default",
12231
- children: [method === "passkey" && "Sign in to your site", method === "magic-link" && "Sign in with email"]
12444
+ children: [method === "passkey" && _t2({
12445
+ id: "lE0wHD",
12446
+ message: "Sign in to your site"
12447
+ }), method === "magic-link" && _t2({
12448
+ id: "me9L29",
12449
+ message: "Sign in with email"
12450
+ })]
12232
12451
  })]
12233
12452
  }),
12234
12453
  urlError && /* @__PURE__ */ jsx("div", {
@@ -12244,7 +12463,10 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12244
12463
  optionsEndpoint: "/_emdash/api/auth/passkey/options",
12245
12464
  verifyEndpoint: "/_emdash/api/auth/passkey/verify",
12246
12465
  onSuccess: handleSuccess,
12247
- buttonText: "Sign in with Passkey"
12466
+ buttonText: _t2({
12467
+ id: "QjsOMP",
12468
+ message: "Sign in with Passkey"
12469
+ })
12248
12470
  }),
12249
12471
  /* @__PURE__ */ jsxs("div", {
12250
12472
  className: "relative",
@@ -12255,7 +12477,10 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12255
12477
  className: "relative flex justify-center text-xs uppercase",
12256
12478
  children: /* @__PURE__ */ jsx("span", {
12257
12479
  className: "bg-kumo-base px-2 text-kumo-subtle",
12258
- children: "Or continue with"
12480
+ children: _t2({
12481
+ id: "zW+FpA",
12482
+ message: "Or continue with"
12483
+ })
12259
12484
  })
12260
12485
  })]
12261
12486
  }),
@@ -12277,26 +12502,42 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12277
12502
  className: "w-full justify-center",
12278
12503
  type: "button",
12279
12504
  onClick: () => setMethod("magic-link"),
12280
- children: "Sign in with email link"
12505
+ children: _t2({
12506
+ id: "RxerEl",
12507
+ message: "Sign in with email link"
12508
+ })
12281
12509
  })
12282
12510
  ]
12283
12511
  }), method === "magic-link" && /* @__PURE__ */ jsx(MagicLinkForm, { onBack: () => setMethod("passkey") })]
12284
12512
  }),
12285
12513
  /* @__PURE__ */ jsx("p", {
12286
12514
  className: "text-center mt-6 text-sm text-kumo-subtle",
12287
- children: method === "passkey" ? "Use your registered passkey to sign in securely." : "We'll send you a link to sign in without a password."
12515
+ children: method === "passkey" ? _t2({
12516
+ id: "+ET/av",
12517
+ message: "Use your registered passkey to sign in securely."
12518
+ }) : _t2({
12519
+ id: "pv+wH3",
12520
+ message: "We'll send you a link to sign in without a password."
12521
+ })
12288
12522
  }),
12289
- manifest?.signupEnabled && /* @__PURE__ */ jsxs("p", {
12523
+ manifest?.signupEnabled && /* @__PURE__ */ jsx("p", {
12290
12524
  className: "text-center mt-4 text-sm text-kumo-subtle",
12291
- children: [
12292
- "Don't have an account?",
12293
- " ",
12294
- /* @__PURE__ */ jsx(Link$1, {
12525
+ children: /* @__PURE__ */ jsx(Trans, {
12526
+ id: "352VU2",
12527
+ message: "Don't have an account? <0>Sign up</0>",
12528
+ components: { 0: /* @__PURE__ */ jsx(Link$1, {
12295
12529
  to: "/signup",
12296
- className: "text-kumo-brand hover:underline font-medium",
12297
- children: "Sign up"
12298
- })
12299
- ]
12530
+ className: "text-kumo-brand hover:underline font-medium"
12531
+ }) }
12532
+ })
12533
+ }),
12534
+ SUPPORTED_LOCALES.length > 1 && /* @__PURE__ */ jsx("div", {
12535
+ className: "mt-6 flex justify-center gap-2 text-xs text-kumo-subtle",
12536
+ children: SUPPORTED_LOCALES.map((l, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [i > 0 && /* @__PURE__ */ jsx("span", { children: "·" }), /* @__PURE__ */ jsx("button", {
12537
+ onClick: () => setLocale(l.code),
12538
+ className: l.code === locale ? "font-medium text-kumo-default" : "hover:text-kumo-default transition-colors",
12539
+ children: l.label
12540
+ })] }, l.code))
12300
12541
  })
12301
12542
  ]
12302
12543
  })
@@ -14471,7 +14712,7 @@ function ContentPickerModal({ open, onOpenChange, onSelect }) {
14471
14712
  open,
14472
14713
  onOpenChange,
14473
14714
  children: /* @__PURE__ */ jsxs(Dialog, {
14474
- className: "p-6 w-2xl h-[80vh] flex flex-col",
14715
+ className: "p-6 max-w-2xl h-[80vh] flex flex-col",
14475
14716
  size: "lg",
14476
14717
  children: [
14477
14718
  /* @__PURE__ */ jsxs("div", {
@@ -14821,9 +15062,11 @@ function MenuEditor() {
14821
15062
  /* @__PURE__ */ jsx(Input, {
14822
15063
  label: "URL",
14823
15064
  name: "url",
14824
- type: "url",
15065
+ type: "text",
14825
15066
  required: true,
14826
- placeholder: "https://example.com"
15067
+ pattern: "(https?://.+|/.*)",
15068
+ title: "Enter a URL (https://…) or a relative path (/…)",
15069
+ placeholder: "https://example.com or /about"
14827
15070
  }),
14828
15071
  /* @__PURE__ */ jsxs(Select, {
14829
15072
  label: "Target",
@@ -14987,8 +15230,10 @@ function MenuEditor() {
14987
15230
  editingItem.type === "custom" && /* @__PURE__ */ jsx(Input, {
14988
15231
  label: "URL",
14989
15232
  name: "url",
14990
- type: "url",
15233
+ type: "text",
14991
15234
  required: true,
15235
+ pattern: "(https?://.+|/.*)",
15236
+ title: "Enter a URL (https://…) or a relative path (/…)",
14992
15237
  defaultValue: editingItem.custom_url || ""
14993
15238
  }),
14994
15239
  /* @__PURE__ */ jsxs(Select, {
@@ -15517,6 +15762,7 @@ function Redirects() {
15517
15762
  setTab("redirects");
15518
15763
  }
15519
15764
  const redirects = redirectsQuery.data?.items ?? [];
15765
+ const loopRedirectIds = new Set(redirectsQuery.data?.loopRedirectIds ?? []);
15520
15766
  return /* @__PURE__ */ jsxs("div", {
15521
15767
  className: "space-y-6",
15522
15768
  children: [
@@ -15550,173 +15796,208 @@ function Redirects() {
15550
15796
  children: "404 Errors"
15551
15797
  })]
15552
15798
  }),
15553
- tab === "redirects" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
15554
- className: "flex items-center gap-4",
15555
- children: [
15556
- /* @__PURE__ */ jsxs("div", {
15557
- className: "relative flex-1 max-w-md",
15558
- children: [/* @__PURE__ */ jsx(MagnifyingGlass, {
15559
- className: "absolute left-3 top-1/2 -translate-y-1/2 text-kumo-subtle",
15560
- size: 16
15561
- }), /* @__PURE__ */ jsx(Input, {
15562
- placeholder: "Search source or destination...",
15563
- className: "pl-10",
15564
- value: search,
15565
- onChange: (e) => setSearch(e.target.value)
15566
- })]
15567
- }),
15568
- /* @__PURE__ */ jsxs("select", {
15569
- value: filterEnabled,
15570
- onChange: (e) => setFilterEnabled(e.target.value),
15571
- className: "h-10 rounded-md border border-kumo-line bg-kumo-base px-3 text-sm",
15572
- children: [
15573
- /* @__PURE__ */ jsx("option", {
15574
- value: "all",
15575
- children: "All statuses"
15576
- }),
15577
- /* @__PURE__ */ jsx("option", {
15578
- value: "true",
15579
- children: "Enabled"
15580
- }),
15581
- /* @__PURE__ */ jsx("option", {
15582
- value: "false",
15583
- children: "Disabled"
15584
- })
15585
- ]
15586
- }),
15587
- /* @__PURE__ */ jsxs("select", {
15588
- value: filterAuto,
15589
- onChange: (e) => setFilterAuto(e.target.value),
15590
- className: "h-10 rounded-md border border-kumo-line bg-kumo-base px-3 text-sm",
15591
- children: [
15592
- /* @__PURE__ */ jsx("option", {
15593
- value: "all",
15594
- children: "All types"
15595
- }),
15596
- /* @__PURE__ */ jsx("option", {
15597
- value: "false",
15598
- children: "Manual"
15599
- }),
15600
- /* @__PURE__ */ jsx("option", {
15601
- value: "true",
15602
- children: "Auto (slug change)"
15603
- })
15604
- ]
15605
- })
15606
- ]
15607
- }), redirectsQuery.isLoading ? /* @__PURE__ */ jsx("div", {
15608
- className: "py-12 text-center text-kumo-subtle",
15609
- children: "Loading redirects..."
15610
- }) : redirects.length === 0 ? /* @__PURE__ */ jsxs("div", {
15611
- className: "py-12 text-center text-kumo-subtle",
15612
- children: [
15613
- /* @__PURE__ */ jsx(ArrowsLeftRight, {
15614
- size: 48,
15615
- className: "mx-auto mb-4 opacity-30"
15616
- }),
15617
- /* @__PURE__ */ jsx("p", {
15618
- className: "text-lg font-medium",
15619
- children: "No redirects yet"
15620
- }),
15621
- /* @__PURE__ */ jsx("p", {
15622
- className: "text-sm mt-1",
15623
- children: "Create redirect rules to manage URL changes."
15624
- })
15625
- ]
15626
- }) : /* @__PURE__ */ jsxs("div", {
15627
- className: "border rounded-lg",
15628
- children: [/* @__PURE__ */ jsxs("div", {
15629
- className: "flex items-center gap-4 py-2 px-4 border-b bg-kumo-tint/50 text-sm font-medium text-kumo-subtle",
15630
- children: [
15631
- /* @__PURE__ */ jsx("div", {
15632
- className: "flex-1",
15633
- children: "Source"
15634
- }),
15635
- /* @__PURE__ */ jsx("div", { className: "w-8 text-center" }),
15636
- /* @__PURE__ */ jsx("div", {
15637
- className: "flex-1",
15638
- children: "Destination"
15639
- }),
15640
- /* @__PURE__ */ jsx("div", {
15641
- className: "w-14 text-center",
15642
- children: "Code"
15643
- }),
15644
- /* @__PURE__ */ jsx("div", {
15645
- className: "w-16 text-right",
15646
- children: "Hits"
15647
- }),
15648
- /* @__PURE__ */ jsx("div", {
15649
- className: "w-20 text-center",
15650
- children: "Status"
15651
- }),
15652
- /* @__PURE__ */ jsx("div", { className: "w-20" })
15653
- ]
15654
- }), redirects.map((r) => /* @__PURE__ */ jsxs("div", {
15655
- className: cn("flex items-center gap-4 py-2 px-4 border-b last:border-0 text-sm", !r.enabled && "opacity-50"),
15799
+ tab === "redirects" && /* @__PURE__ */ jsxs(Fragment, { children: [
15800
+ /* @__PURE__ */ jsxs("div", {
15801
+ className: "flex items-center gap-4",
15656
15802
  children: [
15657
- /* @__PURE__ */ jsx("div", {
15658
- className: "flex-1 font-mono text-xs truncate",
15659
- title: r.source,
15660
- children: r.source
15661
- }),
15662
- /* @__PURE__ */ jsx("div", {
15663
- className: "w-8 text-center text-kumo-subtle",
15664
- children: /* @__PURE__ */ jsx(ArrowRight, { size: 14 })
15665
- }),
15666
- /* @__PURE__ */ jsx("div", {
15667
- className: "flex-1 font-mono text-xs truncate",
15668
- title: r.destination,
15669
- children: r.destination
15670
- }),
15671
- /* @__PURE__ */ jsx("div", {
15672
- className: "w-14 text-center",
15673
- children: /* @__PURE__ */ jsx(Badge, {
15674
- variant: "secondary",
15675
- children: r.type
15676
- })
15677
- }),
15678
- /* @__PURE__ */ jsx("div", {
15679
- className: "w-16 text-right tabular-nums text-kumo-subtle",
15680
- children: r.hits
15803
+ /* @__PURE__ */ jsxs("div", {
15804
+ className: "relative flex-1 max-w-md",
15805
+ children: [/* @__PURE__ */ jsx(MagnifyingGlass, {
15806
+ className: "absolute left-3 top-1/2 -translate-y-1/2 text-kumo-subtle",
15807
+ size: 16
15808
+ }), /* @__PURE__ */ jsx(Input, {
15809
+ placeholder: "Search source or destination...",
15810
+ className: "pl-10",
15811
+ value: search,
15812
+ onChange: (e) => setSearch(e.target.value)
15813
+ })]
15681
15814
  }),
15682
- /* @__PURE__ */ jsx("div", {
15683
- className: "w-20 text-center",
15684
- children: /* @__PURE__ */ jsx(Switch, {
15685
- checked: r.enabled,
15686
- onCheckedChange: (checked) => toggleMutation.mutate({
15687
- id: r.id,
15688
- enabled: checked
15815
+ /* @__PURE__ */ jsxs("select", {
15816
+ value: filterEnabled,
15817
+ onChange: (e) => setFilterEnabled(e.target.value),
15818
+ className: "h-10 rounded-md border border-kumo-line bg-kumo-base px-3 text-sm",
15819
+ children: [
15820
+ /* @__PURE__ */ jsx("option", {
15821
+ value: "all",
15822
+ children: "All statuses"
15689
15823
  }),
15690
- "aria-label": r.enabled ? "Disable redirect" : "Enable redirect"
15691
- })
15824
+ /* @__PURE__ */ jsx("option", {
15825
+ value: "true",
15826
+ children: "Enabled"
15827
+ }),
15828
+ /* @__PURE__ */ jsx("option", {
15829
+ value: "false",
15830
+ children: "Disabled"
15831
+ })
15832
+ ]
15692
15833
  }),
15693
- /* @__PURE__ */ jsxs("div", {
15694
- className: "w-20 flex items-center justify-end gap-1",
15834
+ /* @__PURE__ */ jsxs("select", {
15835
+ value: filterAuto,
15836
+ onChange: (e) => setFilterAuto(e.target.value),
15837
+ className: "h-10 rounded-md border border-kumo-line bg-kumo-base px-3 text-sm",
15695
15838
  children: [
15696
- r.auto && /* @__PURE__ */ jsx(Badge, {
15697
- variant: "outline",
15698
- className: "mr-1 text-xs",
15699
- children: "auto"
15839
+ /* @__PURE__ */ jsx("option", {
15840
+ value: "all",
15841
+ children: "All types"
15700
15842
  }),
15701
- /* @__PURE__ */ jsx("button", {
15702
- onClick: () => setEditRedirect(r),
15703
- className: "p-1 text-kumo-subtle hover:text-kumo-default",
15704
- title: "Edit redirect",
15705
- "aria-label": `Edit redirect ${r.source}`,
15706
- children: /* @__PURE__ */ jsx(PencilSimple, { size: 14 })
15843
+ /* @__PURE__ */ jsx("option", {
15844
+ value: "false",
15845
+ children: "Manual"
15707
15846
  }),
15708
- /* @__PURE__ */ jsx("button", {
15709
- onClick: () => setDeleteId(r.id),
15710
- className: "p-1 text-kumo-subtle hover:text-kumo-danger",
15711
- title: "Delete redirect",
15712
- "aria-label": `Delete redirect ${r.source}`,
15713
- children: /* @__PURE__ */ jsx(Trash, { size: 14 })
15847
+ /* @__PURE__ */ jsx("option", {
15848
+ value: "true",
15849
+ children: "Auto (slug change)"
15714
15850
  })
15715
15851
  ]
15716
15852
  })
15717
15853
  ]
15718
- }, r.id))]
15719
- })] }),
15854
+ }),
15855
+ loopRedirectIds.size > 0 && /* @__PURE__ */ jsxs("div", {
15856
+ role: "alert",
15857
+ className: "flex items-start gap-3 rounded-lg border border-kumo-warning/50 bg-kumo-warning-tint p-4",
15858
+ children: [/* @__PURE__ */ jsx(WarningCircle, {
15859
+ size: 20,
15860
+ className: "mt-0.5 shrink-0 text-kumo-warning",
15861
+ weight: "fill",
15862
+ "aria-hidden": "true"
15863
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
15864
+ className: "text-sm font-medium text-kumo-warning",
15865
+ children: "Redirect loop detected"
15866
+ }), /* @__PURE__ */ jsxs("p", {
15867
+ className: "mt-1 text-sm text-kumo-subtle",
15868
+ children: [
15869
+ loopRedirectIds.size,
15870
+ " ",
15871
+ loopRedirectIds.size === 1 ? "redirect is" : "redirects are",
15872
+ " part of a loop. Visitors hitting these paths will see an error."
15873
+ ]
15874
+ })] })]
15875
+ }),
15876
+ redirectsQuery.isLoading ? /* @__PURE__ */ jsx("div", {
15877
+ className: "py-12 text-center text-kumo-subtle",
15878
+ children: "Loading redirects..."
15879
+ }) : redirects.length === 0 ? /* @__PURE__ */ jsxs("div", {
15880
+ className: "py-12 text-center text-kumo-subtle",
15881
+ children: [
15882
+ /* @__PURE__ */ jsx(ArrowsLeftRight, {
15883
+ size: 48,
15884
+ className: "mx-auto mb-4 opacity-30"
15885
+ }),
15886
+ /* @__PURE__ */ jsx("p", {
15887
+ className: "text-lg font-medium",
15888
+ children: "No redirects yet"
15889
+ }),
15890
+ /* @__PURE__ */ jsx("p", {
15891
+ className: "text-sm mt-1",
15892
+ children: "Create redirect rules to manage URL changes."
15893
+ })
15894
+ ]
15895
+ }) : /* @__PURE__ */ jsxs("div", {
15896
+ className: "border rounded-lg",
15897
+ children: [/* @__PURE__ */ jsxs("div", {
15898
+ className: "flex items-center gap-4 py-2 px-4 border-b bg-kumo-tint/50 text-sm font-medium text-kumo-subtle",
15899
+ children: [
15900
+ /* @__PURE__ */ jsx("div", {
15901
+ className: "flex-1",
15902
+ children: "Source"
15903
+ }),
15904
+ /* @__PURE__ */ jsx("div", { className: "w-8 text-center" }),
15905
+ /* @__PURE__ */ jsx("div", {
15906
+ className: "flex-1",
15907
+ children: "Destination"
15908
+ }),
15909
+ /* @__PURE__ */ jsx("div", {
15910
+ className: "w-14 text-center",
15911
+ children: "Code"
15912
+ }),
15913
+ /* @__PURE__ */ jsx("div", {
15914
+ className: "w-16 text-right",
15915
+ children: "Hits"
15916
+ }),
15917
+ /* @__PURE__ */ jsx("div", {
15918
+ className: "w-20 text-center",
15919
+ children: "Status"
15920
+ }),
15921
+ /* @__PURE__ */ jsx("div", { className: "w-20" })
15922
+ ]
15923
+ }), redirects.map((r) => /* @__PURE__ */ jsxs("div", {
15924
+ className: cn("flex items-center gap-4 py-2 px-4 border-b last:border-0 text-sm", !r.enabled && "opacity-50"),
15925
+ children: [
15926
+ /* @__PURE__ */ jsx("div", {
15927
+ className: "flex-1 font-mono text-xs truncate",
15928
+ title: r.source,
15929
+ children: r.source
15930
+ }),
15931
+ /* @__PURE__ */ jsx("div", {
15932
+ className: "w-8 text-center text-kumo-subtle",
15933
+ children: /* @__PURE__ */ jsx(ArrowRight, { size: 14 })
15934
+ }),
15935
+ /* @__PURE__ */ jsx("div", {
15936
+ className: "flex-1 font-mono text-xs truncate",
15937
+ title: r.destination,
15938
+ children: r.destination
15939
+ }),
15940
+ /* @__PURE__ */ jsx("div", {
15941
+ className: "w-14 text-center",
15942
+ children: /* @__PURE__ */ jsx(Badge, {
15943
+ variant: "secondary",
15944
+ children: r.type
15945
+ })
15946
+ }),
15947
+ /* @__PURE__ */ jsx("div", {
15948
+ className: "w-16 text-right tabular-nums text-kumo-subtle",
15949
+ children: r.hits
15950
+ }),
15951
+ /* @__PURE__ */ jsx("div", {
15952
+ className: "w-20 text-center",
15953
+ children: /* @__PURE__ */ jsx(Switch, {
15954
+ checked: r.enabled,
15955
+ onCheckedChange: (checked) => toggleMutation.mutate({
15956
+ id: r.id,
15957
+ enabled: checked
15958
+ }),
15959
+ "aria-label": r.enabled ? "Disable redirect" : "Enable redirect"
15960
+ })
15961
+ }),
15962
+ /* @__PURE__ */ jsxs("div", {
15963
+ className: "w-20 flex items-center justify-end gap-1",
15964
+ children: [
15965
+ loopRedirectIds.has(r.id) && /* @__PURE__ */ jsx("span", {
15966
+ title: "Part of a redirect loop",
15967
+ className: "mr-1 inline-flex",
15968
+ children: /* @__PURE__ */ jsx(WarningCircle, {
15969
+ size: 14,
15970
+ weight: "fill",
15971
+ className: "text-kumo-warning",
15972
+ role: "img",
15973
+ "aria-label": "Part of a redirect loop"
15974
+ })
15975
+ }),
15976
+ r.auto && /* @__PURE__ */ jsx(Badge, {
15977
+ variant: "outline",
15978
+ className: "mr-1 text-xs",
15979
+ children: "auto"
15980
+ }),
15981
+ /* @__PURE__ */ jsx("button", {
15982
+ onClick: () => setEditRedirect(r),
15983
+ className: "p-1 text-kumo-subtle hover:text-kumo-default",
15984
+ title: "Edit redirect",
15985
+ "aria-label": `Edit redirect ${r.source}`,
15986
+ children: /* @__PURE__ */ jsx(PencilSimple, { size: 14 })
15987
+ }),
15988
+ /* @__PURE__ */ jsx("button", {
15989
+ onClick: () => setDeleteId(r.id),
15990
+ className: "p-1 text-kumo-subtle hover:text-kumo-danger",
15991
+ title: "Delete redirect",
15992
+ "aria-label": `Delete redirect ${r.source}`,
15993
+ children: /* @__PURE__ */ jsx(Trash, { size: 14 })
15994
+ })
15995
+ ]
15996
+ })
15997
+ ]
15998
+ }, r.id))]
15999
+ })
16000
+ ] }),
15720
16001
  tab === "404s" && /* @__PURE__ */ jsx(NotFoundPanel, {
15721
16002
  items: notFoundQuery.data ?? [],
15722
16003
  onCreateRedirect: handleCreateFrom404
@@ -16463,13 +16744,18 @@ function Settings() {
16463
16744
  queryKey: ["manifest"],
16464
16745
  queryFn: fetchManifest
16465
16746
  });
16747
+ const { _: _t } = useLingui();
16748
+ const { locale, setLocale } = useLocale();
16466
16749
  const showSecuritySettings = manifest?.authMode === "passkey";
16467
16750
  return /* @__PURE__ */ jsxs("div", {
16468
16751
  className: "space-y-6",
16469
16752
  children: [
16470
16753
  /* @__PURE__ */ jsx("h1", {
16471
16754
  className: "text-2xl font-bold",
16472
- children: "Settings"
16755
+ children: _t({
16756
+ id: "Tz0i8g",
16757
+ message: "Settings"
16758
+ })
16473
16759
  }),
16474
16760
  /* @__PURE__ */ jsxs("div", {
16475
16761
  className: "space-y-2",
@@ -16477,20 +16763,38 @@ function Settings() {
16477
16763
  /* @__PURE__ */ jsx(SettingsLink, {
16478
16764
  to: "/settings/general",
16479
16765
  icon: /* @__PURE__ */ jsx(Gear, { className: "h-5 w-5" }),
16480
- title: "General",
16481
- description: "Site identity, logo, favicon, and reading preferences"
16766
+ title: _t({
16767
+ id: "Weq9zb",
16768
+ message: "General"
16769
+ }),
16770
+ description: _t({
16771
+ id: "RR0ADZ",
16772
+ message: "Site identity, logo, favicon, and reading preferences"
16773
+ })
16482
16774
  }),
16483
16775
  /* @__PURE__ */ jsx(SettingsLink, {
16484
16776
  to: "/settings/social",
16485
16777
  icon: /* @__PURE__ */ jsx(ShareNetwork, { className: "h-5 w-5" }),
16486
- title: "Social Links",
16487
- description: "Social media profile links"
16778
+ title: _t({
16779
+ id: "d0rUsW",
16780
+ message: "Social Links"
16781
+ }),
16782
+ description: _t({
16783
+ id: "qS3mgX",
16784
+ message: "Social media profile links"
16785
+ })
16488
16786
  }),
16489
16787
  /* @__PURE__ */ jsx(SettingsLink, {
16490
16788
  to: "/settings/seo",
16491
16789
  icon: /* @__PURE__ */ jsx(MagnifyingGlass, { className: "h-5 w-5" }),
16492
- title: "SEO",
16493
- description: "Search engine optimization and verification"
16790
+ title: _t({
16791
+ id: "4Ml90q",
16792
+ message: "SEO"
16793
+ }),
16794
+ description: _t({
16795
+ id: "zY0S+v",
16796
+ message: "Search engine optimization and verification"
16797
+ })
16494
16798
  })
16495
16799
  ]
16496
16800
  }),
@@ -16499,13 +16803,25 @@ function Settings() {
16499
16803
  children: [/* @__PURE__ */ jsx(SettingsLink, {
16500
16804
  to: "/settings/security",
16501
16805
  icon: /* @__PURE__ */ jsx(Shield, { className: "h-5 w-5" }),
16502
- title: "Security",
16503
- description: "Manage your passkeys and authentication"
16806
+ title: _t({
16807
+ id: "a3LDKx",
16808
+ message: "Security"
16809
+ }),
16810
+ description: _t({
16811
+ id: "OfnTKV",
16812
+ message: "Manage your passkeys and authentication"
16813
+ })
16504
16814
  }), /* @__PURE__ */ jsx(SettingsLink, {
16505
16815
  to: "/settings/allowed-domains",
16506
16816
  icon: /* @__PURE__ */ jsx(Globe, { className: "h-5 w-5" }),
16507
- title: "Self-Signup Domains",
16508
- description: "Allow users from specific domains to sign up"
16817
+ title: _t({
16818
+ id: "Tllxyd",
16819
+ message: "Self-Signup Domains"
16820
+ }),
16821
+ description: _t({
16822
+ id: "DsZc8w",
16823
+ message: "Allow users from specific domains to sign up"
16824
+ })
16509
16825
  })]
16510
16826
  }),
16511
16827
  /* @__PURE__ */ jsxs("div", {
@@ -16513,49 +16829,217 @@ function Settings() {
16513
16829
  children: [/* @__PURE__ */ jsx(SettingsLink, {
16514
16830
  to: "/settings/api-tokens",
16515
16831
  icon: /* @__PURE__ */ jsx(Key, { className: "h-5 w-5" }),
16516
- title: "API Tokens",
16517
- description: "Create personal access tokens for programmatic API access"
16832
+ title: _t({
16833
+ id: "ZiooJI",
16834
+ message: "API Tokens"
16835
+ }),
16836
+ description: _t({
16837
+ id: "dhH+RW",
16838
+ message: "Create personal access tokens for programmatic API access"
16839
+ })
16518
16840
  }), /* @__PURE__ */ jsx(SettingsLink, {
16519
16841
  to: "/settings/email",
16520
16842
  icon: /* @__PURE__ */ jsx(Envelope, { className: "h-5 w-5" }),
16521
- title: "Email",
16522
- description: "View email provider status and send test emails"
16843
+ title: _t({
16844
+ id: "O3oNi5",
16845
+ message: "Email"
16846
+ }),
16847
+ description: _t({
16848
+ id: "/JPN+P",
16849
+ message: "View email provider status and send test emails"
16850
+ })
16523
16851
  })]
16852
+ }),
16853
+ SUPPORTED_LOCALES.length > 1 && /* @__PURE__ */ jsx("div", {
16854
+ className: "space-y-2",
16855
+ children: /* @__PURE__ */ jsxs("div", {
16856
+ className: "flex items-center justify-between p-4 rounded-lg border bg-kumo-base",
16857
+ children: [/* @__PURE__ */ jsxs("div", {
16858
+ className: "flex items-center gap-3",
16859
+ children: [/* @__PURE__ */ jsx("div", {
16860
+ className: "text-kumo-subtle",
16861
+ children: /* @__PURE__ */ jsx(GlobeSimple, { className: "h-5 w-5" })
16862
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
16863
+ className: "font-medium",
16864
+ children: _t({
16865
+ id: "vXIe7J",
16866
+ message: "Language"
16867
+ })
16868
+ }), /* @__PURE__ */ jsx("div", {
16869
+ className: "text-sm text-kumo-subtle",
16870
+ children: _t({
16871
+ id: "Y8S9QC",
16872
+ message: "Choose your preferred admin language"
16873
+ })
16874
+ })] })]
16875
+ }), /* @__PURE__ */ jsx("div", {
16876
+ className: "flex gap-1",
16877
+ children: SUPPORTED_LOCALES.map((l) => /* @__PURE__ */ jsx("button", {
16878
+ onClick: () => setLocale(l.code),
16879
+ className: cn("rounded-md px-3 py-1.5 text-sm transition-colors", l.code === locale ? "bg-kumo-brand/10 text-kumo-brand font-medium" : "hover:bg-kumo-tint"),
16880
+ children: l.label
16881
+ }, l.code))
16882
+ })]
16883
+ })
16524
16884
  })
16525
16885
  ]
16526
16886
  });
16527
16887
  }
16528
16888
 
16529
16889
  //#endregion
16530
- //#region src/components/settings/AllowedDomainsSettings.tsx
16890
+ //#region src/components/users/roleDefinitions.ts
16531
16891
  /**
16532
- * Allowed Domains Settings - Self-signup domain management
16533
- *
16534
- * Only available when using passkey auth. When external auth (e.g., Cloudflare Access)
16535
- * is configured, this page shows an informational message instead.
16892
+ * Canonical role levels for admin UI (badge colors, selects, labels).
16893
+ * Allowed Domains UI only offers default roles up to Editor (40), not Admin (50).
16536
16894
  */
16537
- const ROLES$1 = [
16895
+ const ROLE_ENTRIES = [
16538
16896
  {
16539
16897
  value: 10,
16540
- label: "Subscriber"
16898
+ color: "gray",
16899
+ label: {
16900
+ id: "M2vfqB",
16901
+ message: "Subscriber"
16902
+ },
16903
+ description: {
16904
+ id: "m7BmGK",
16905
+ message: "Can view content"
16906
+ }
16541
16907
  },
16542
16908
  {
16543
16909
  value: 20,
16544
- label: "Contributor"
16910
+ color: "blue",
16911
+ label: {
16912
+ id: "3L51iw",
16913
+ message: "Contributor"
16914
+ },
16915
+ description: {
16916
+ id: "WzyYFi",
16917
+ message: "Can create content"
16918
+ }
16545
16919
  },
16546
16920
  {
16547
16921
  value: 30,
16548
- label: "Author"
16922
+ color: "green",
16923
+ label: {
16924
+ id: "VbeIOx",
16925
+ message: "Author"
16926
+ },
16927
+ description: {
16928
+ id: "1onhKx",
16929
+ message: "Can publish own content"
16930
+ }
16549
16931
  },
16550
16932
  {
16551
16933
  value: 40,
16552
- label: "Editor"
16934
+ color: "purple",
16935
+ label: {
16936
+ id: "uBAxNB",
16937
+ message: "Editor"
16938
+ },
16939
+ description: {
16940
+ id: "ppZiw8",
16941
+ message: "Can manage all content"
16942
+ }
16943
+ },
16944
+ {
16945
+ value: 50,
16946
+ color: "red",
16947
+ label: {
16948
+ id: "U3pytU",
16949
+ message: "Admin"
16950
+ },
16951
+ description: {
16952
+ id: "scmRyR",
16953
+ message: "Full access"
16954
+ }
16553
16955
  }
16554
16956
  ];
16555
- function getRoleName(level) {
16556
- return ROLES$1.find((r) => r.value === level)?.label ?? "Unknown";
16957
+ const ROLE_CONFIG = Object.fromEntries(ROLE_ENTRIES.map((e) => [e.value, {
16958
+ label: e.label,
16959
+ description: e.description,
16960
+ color: e.color
16961
+ }]));
16962
+ function unknownRoleConfig(role) {
16963
+ return {
16964
+ label: {
16965
+ id: "h4POyA",
16966
+ message: "Role {role}",
16967
+ values: { role }
16968
+ },
16969
+ description: {
16970
+ id: "C52+JH",
16971
+ message: "Unknown role"
16972
+ },
16973
+ color: "gray"
16974
+ };
16557
16975
  }
16976
+ /** Badge / display config for a numeric role level */
16977
+ function getRoleConfig(role) {
16978
+ return ROLE_CONFIG[role] ?? unknownRoleConfig(role);
16979
+ }
16980
+
16981
+ //#endregion
16982
+ //#region src/components/users/useRolesConfig.ts
16983
+ const MSG_ROLE_UNKNOWN = {
16984
+ id: "Ef7StM",
16985
+ message: "Unknown"
16986
+ };
16987
+ /**
16988
+ * Shared resolved role strings + descriptor rows for selects (after `i18n` is active).
16989
+ */
16990
+ function useRolesConfig() {
16991
+ const { _: _t } = useLingui();
16992
+ const roles = React.useMemo(() => ROLE_ENTRIES.map(({ value, label, description }) => ({
16993
+ value,
16994
+ label: _t(label),
16995
+ description: _t(description)
16996
+ })), [_t]);
16997
+ const roleLabels = React.useMemo(() => Object.fromEntries(ROLE_ENTRIES.map((r) => [String(r.value), _t(r.label)])), [_t]);
16998
+ return {
16999
+ roleLabels,
17000
+ getRoleLabel: React.useCallback((level) => roleLabels[String(level)] ?? _t(MSG_ROLE_UNKNOWN), [roleLabels, _t]),
17001
+ roles
17002
+ };
17003
+ }
17004
+
17005
+ //#endregion
17006
+ //#region src/components/settings/useAllowedDomainsRolesConfig.ts
17007
+ /** Self-signup default role must not be Admin (API / product rule). */
17008
+ const MAX_SELF_SIGNUP_DEFAULT_ROLE = 40;
17009
+ /**
17010
+ * Role labels and selects for Allowed Domains (Subscriber–Editor only for defaults).
17011
+ * Built on {@link useRolesConfig}; keeps the filter + `Select` `items` shape in one place.
17012
+ */
17013
+ function useAllowedDomainsRolesConfig() {
17014
+ const { roleLabels, getRoleLabel, roles } = useRolesConfig();
17015
+ const signupRoles = React.useMemo(() => roles.filter((r) => r.value <= MAX_SELF_SIGNUP_DEFAULT_ROLE), [roles]);
17016
+ return {
17017
+ getRoleLabel,
17018
+ signupRoles,
17019
+ signupRoleItems: React.useMemo(() => {
17020
+ const entries = signupRoles.map((r) => {
17021
+ const label = roleLabels[String(r.value)];
17022
+ return [String(r.value), label ?? getRoleLabel(r.value)];
17023
+ });
17024
+ return Object.fromEntries(entries);
17025
+ }, [
17026
+ signupRoles,
17027
+ roleLabels,
17028
+ getRoleLabel
17029
+ ])
17030
+ };
17031
+ }
17032
+
17033
+ //#endregion
17034
+ //#region src/components/settings/AllowedDomainsSettings.tsx
17035
+ /**
17036
+ * Allowed Domains Settings - Self-signup domain management
17037
+ *
17038
+ * Only available when using passkey auth. When external auth (e.g., Cloudflare Access)
17039
+ * is configured, this page shows an informational message instead.
17040
+ */
16558
17041
  function AllowedDomainsSettings() {
17042
+ const { getRoleLabel, signupRoles, signupRoleItems } = useAllowedDomainsRolesConfig();
16559
17043
  const queryClient = useQueryClient();
16560
17044
  const [isAddingDomain, setIsAddingDomain] = React.useState(false);
16561
17045
  const [editingDomain, setEditingDomain] = React.useState(null);
@@ -16747,7 +17231,7 @@ function AllowedDomainsSettings() {
16747
17231
  children: domain.domain
16748
17232
  }), /* @__PURE__ */ jsxs("div", {
16749
17233
  className: "text-sm text-kumo-subtle",
16750
- children: ["Default role: ", getRoleName(domain.defaultRole)]
17234
+ children: ["Default role: ", getRoleLabel(domain.defaultRole)]
16751
17235
  })] })]
16752
17236
  }), /* @__PURE__ */ jsxs("div", {
16753
17237
  className: "flex items-center gap-2",
@@ -16808,8 +17292,8 @@ function AllowedDomainsSettings() {
16808
17292
  label: "Default Role",
16809
17293
  value: String(newRole),
16810
17294
  onValueChange: (v) => v !== null && setNewRole(Number(v)),
16811
- items: Object.fromEntries(ROLES$1.map((r) => [String(r.value), r.label])),
16812
- children: ROLES$1.map((role) => /* @__PURE__ */ jsx(Select.Option, {
17295
+ items: signupRoleItems,
17296
+ children: signupRoles.map((role) => /* @__PURE__ */ jsx(Select.Option, {
16813
17297
  value: String(role.value),
16814
17298
  children: role.label
16815
17299
  }, role.value))
@@ -16869,8 +17353,8 @@ function AllowedDomainsSettings() {
16869
17353
  label: "Default Role",
16870
17354
  value: String(editingDomain?.defaultRole ?? 30),
16871
17355
  onValueChange: (v) => v !== null && editingDomain && handleUpdateRole(editingDomain.domain, Number(v)),
16872
- items: Object.fromEntries(ROLES$1.map((r) => [String(r.value), r.label])),
16873
- children: ROLES$1.map((role) => /* @__PURE__ */ jsx(Select.Option, {
17356
+ items: signupRoleItems,
17357
+ children: signupRoles.map((role) => /* @__PURE__ */ jsx(Select.Option, {
16874
17358
  value: String(role.value),
16875
17359
  children: role.label
16876
17360
  }, role.value))
@@ -16929,25 +17413,121 @@ function AllowedDomainsSettings() {
16929
17413
  const EXPIRY_OPTIONS = [
16930
17414
  {
16931
17415
  value: "none",
16932
- label: "No expiry"
17416
+ label: {
17417
+ id: "JYZYJA",
17418
+ message: "No expiry"
17419
+ }
16933
17420
  },
16934
17421
  {
16935
17422
  value: "7d",
16936
- label: "7 days"
17423
+ label: {
17424
+ id: "rJe6vw",
17425
+ message: "7 days"
17426
+ }
16937
17427
  },
16938
17428
  {
16939
17429
  value: "30d",
16940
- label: "30 days"
17430
+ label: {
17431
+ id: "P9cEa2",
17432
+ message: "30 days"
17433
+ }
16941
17434
  },
16942
17435
  {
16943
17436
  value: "90d",
16944
- label: "90 days"
17437
+ label: {
17438
+ id: "h28hXf",
17439
+ message: "90 days"
17440
+ }
16945
17441
  },
16946
17442
  {
16947
17443
  value: "365d",
16948
- label: "1 year"
17444
+ label: {
17445
+ id: "+N7uug",
17446
+ message: "1 year"
17447
+ }
16949
17448
  }
16950
17449
  ];
17450
+ const API_TOKEN_SCOPE_VALUES = [
17451
+ {
17452
+ scope: API_TOKEN_SCOPES.ContentRead,
17453
+ label: {
17454
+ id: "DqsyRK",
17455
+ message: "Content Read"
17456
+ },
17457
+ description: {
17458
+ id: "Pho0BC",
17459
+ message: "Read content entries"
17460
+ }
17461
+ },
17462
+ {
17463
+ scope: API_TOKEN_SCOPES.ContentWrite,
17464
+ label: {
17465
+ id: "js1bld",
17466
+ message: "Content Write"
17467
+ },
17468
+ description: {
17469
+ id: "e48Hn6",
17470
+ message: "Create, update, delete content"
17471
+ }
17472
+ },
17473
+ {
17474
+ scope: API_TOKEN_SCOPES.MediaRead,
17475
+ label: {
17476
+ id: "TNFBJj",
17477
+ message: "Media Read"
17478
+ },
17479
+ description: {
17480
+ id: "IxcUJS",
17481
+ message: "Read media files"
17482
+ }
17483
+ },
17484
+ {
17485
+ scope: API_TOKEN_SCOPES.MediaWrite,
17486
+ label: {
17487
+ id: "nAGBQY",
17488
+ message: "Media Write"
17489
+ },
17490
+ description: {
17491
+ id: "SrZ1jh",
17492
+ message: "Upload and delete media"
17493
+ }
17494
+ },
17495
+ {
17496
+ scope: API_TOKEN_SCOPES.SchemaRead,
17497
+ label: {
17498
+ id: "19WSvR",
17499
+ message: "Schema Read"
17500
+ },
17501
+ description: {
17502
+ id: "nEEs8e",
17503
+ message: "Read collection schemas"
17504
+ }
17505
+ },
17506
+ {
17507
+ scope: API_TOKEN_SCOPES.SchemaWrite,
17508
+ label: {
17509
+ id: "n6+BBp",
17510
+ message: "Schema Write"
17511
+ },
17512
+ description: {
17513
+ id: "UgmJPq",
17514
+ message: "Modify collection schemas"
17515
+ }
17516
+ },
17517
+ {
17518
+ scope: API_TOKEN_SCOPES.Admin,
17519
+ label: {
17520
+ id: "U3pytU",
17521
+ message: "Admin"
17522
+ },
17523
+ description: {
17524
+ id: "5SaI4f",
17525
+ message: "Full admin access"
17526
+ }
17527
+ }
17528
+ ];
17529
+ /** Wire scopes shown on the create-token form (contract-tested vs `API_TOKEN_SCOPES` and `@emdash-cms/auth`). */
17530
+ const API_TOKEN_SCOPE_FORM_SCOPES = API_TOKEN_SCOPE_VALUES.map((row) => row.scope);
16951
17531
  function computeExpiryDate(option) {
16952
17532
  if (option === "none") return void 0;
16953
17533
  const days = parseInt(option, 10);
@@ -16957,6 +17537,7 @@ function computeExpiryDate(option) {
16957
17537
  return date.toISOString();
16958
17538
  }
16959
17539
  function ApiTokenSettings() {
17540
+ const { _: _t } = useLingui();
16960
17541
  const queryClient = useQueryClient();
16961
17542
  const [showCreateForm, setShowCreateForm] = React.useState(false);
16962
17543
  const [newToken, setNewToken] = React.useState(null);
@@ -16998,6 +17579,7 @@ function ApiTokenSettings() {
16998
17579
  copyTimeoutRef.current = setTimeout(setCopied, 2e3, false);
16999
17580
  } catch {}
17000
17581
  };
17582
+ const expirySelectItems = React.useMemo(() => Object.fromEntries(EXPIRY_OPTIONS.map((o) => [o.value, _t(o.label)])), [_t]);
17001
17583
  return /* @__PURE__ */ jsxs("div", {
17002
17584
  className: "space-y-6",
17003
17585
  children: [
@@ -17008,15 +17590,24 @@ function ApiTokenSettings() {
17008
17590
  children: /* @__PURE__ */ jsx(Button, {
17009
17591
  variant: "ghost",
17010
17592
  shape: "square",
17011
- "aria-label": "Back to settings",
17593
+ "aria-label": _t({
17594
+ id: "9aZHfH",
17595
+ message: "Back to settings"
17596
+ }),
17012
17597
  children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
17013
17598
  })
17014
17599
  }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
17015
17600
  className: "text-2xl font-bold",
17016
- children: "API Tokens"
17601
+ children: _t({
17602
+ id: "ZiooJI",
17603
+ message: "API Tokens"
17604
+ })
17017
17605
  }), /* @__PURE__ */ jsx("p", {
17018
17606
  className: "text-sm text-kumo-subtle",
17019
- children: "Create personal access tokens for programmatic API access"
17607
+ children: _t({
17608
+ id: "dhH+RW",
17609
+ message: "Create personal access tokens for programmatic API access"
17610
+ })
17020
17611
  })] })]
17021
17612
  }),
17022
17613
  newToken && /* @__PURE__ */ jsx("div", {
@@ -17028,13 +17619,20 @@ function ApiTokenSettings() {
17028
17619
  /* @__PURE__ */ jsxs("div", {
17029
17620
  className: "flex-1 min-w-0",
17030
17621
  children: [
17031
- /* @__PURE__ */ jsxs("p", {
17622
+ /* @__PURE__ */ jsx("p", {
17032
17623
  className: "font-medium text-green-800 dark:text-green-200",
17033
- children: ["Token created: ", newToken.info.name]
17624
+ children: _t({
17625
+ id: "eoLxnB",
17626
+ message: "Token created: {0}",
17627
+ values: { 0: newToken.info.name }
17628
+ })
17034
17629
  }),
17035
17630
  /* @__PURE__ */ jsx("p", {
17036
17631
  className: "text-sm text-green-700 dark:text-green-300 mt-1",
17037
- children: "Copy this token now — it won't be shown again."
17632
+ children: _t({
17633
+ id: "DyPcUA",
17634
+ message: "Copy this token now — it won't be shown again."
17635
+ })
17038
17636
  }),
17039
17637
  /* @__PURE__ */ jsxs("div", {
17040
17638
  className: "mt-3 flex items-center gap-2",
@@ -17047,21 +17645,33 @@ function ApiTokenSettings() {
17047
17645
  variant: "ghost",
17048
17646
  shape: "square",
17049
17647
  onClick: () => setTokenVisible(!tokenVisible),
17050
- "aria-label": tokenVisible ? "Hide token" : "Show token",
17648
+ "aria-label": tokenVisible ? _t({
17649
+ id: "NEZFJN",
17650
+ message: "Hide token"
17651
+ }) : _t({
17652
+ id: "10eUfR",
17653
+ message: "Show token"
17654
+ }),
17051
17655
  children: tokenVisible ? /* @__PURE__ */ jsx(EyeSlash, {}) : /* @__PURE__ */ jsx(Eye, {})
17052
17656
  }),
17053
17657
  /* @__PURE__ */ jsx(Button, {
17054
17658
  variant: "ghost",
17055
17659
  shape: "square",
17056
17660
  onClick: handleCopyToken,
17057
- "aria-label": "Copy token",
17661
+ "aria-label": _t({
17662
+ id: "BddwrJ",
17663
+ message: "Copy token"
17664
+ }),
17058
17665
  children: /* @__PURE__ */ jsx(Copy, {})
17059
17666
  })
17060
17667
  ]
17061
17668
  }),
17062
17669
  copied && /* @__PURE__ */ jsx("p", {
17063
17670
  className: "text-xs text-green-600 dark:text-green-400 mt-1",
17064
- children: "Copied to clipboard"
17671
+ children: _t({
17672
+ id: "FxVG/l",
17673
+ message: "Copied to clipboard"
17674
+ })
17065
17675
  })
17066
17676
  ]
17067
17677
  }),
@@ -17069,13 +17679,20 @@ function ApiTokenSettings() {
17069
17679
  variant: "ghost",
17070
17680
  size: "sm",
17071
17681
  onClick: () => setNewToken(null),
17072
- "aria-label": "Dismiss",
17073
- children: "Dismiss"
17682
+ "aria-label": _t({
17683
+ id: "1QfxQT",
17684
+ message: "Dismiss"
17685
+ }),
17686
+ children: _t({
17687
+ id: "1QfxQT",
17688
+ message: "Dismiss"
17689
+ })
17074
17690
  })
17075
17691
  ]
17076
17692
  })
17077
17693
  }),
17078
17694
  showCreateForm ? /* @__PURE__ */ jsx(CreateTokenForm, {
17695
+ expirySelectItems,
17079
17696
  isCreating: createMutation.isPending,
17080
17697
  error: createMutation.error?.message ?? null,
17081
17698
  onSubmit: (input) => createMutation.mutate({
@@ -17087,7 +17704,10 @@ function ApiTokenSettings() {
17087
17704
  }) : /* @__PURE__ */ jsx(Button, {
17088
17705
  icon: /* @__PURE__ */ jsx(Plus, {}),
17089
17706
  onClick: () => setShowCreateForm(true),
17090
- children: "Create Token"
17707
+ children: _t({
17708
+ id: "JkdaXO",
17709
+ message: "Create Token"
17710
+ })
17091
17711
  }),
17092
17712
  /* @__PURE__ */ jsx("div", {
17093
17713
  className: "rounded-lg border bg-kumo-base",
@@ -17096,7 +17716,10 @@ function ApiTokenSettings() {
17096
17716
  children: /* @__PURE__ */ jsx(Loader, {})
17097
17717
  }) : !tokens || tokens.length === 0 ? /* @__PURE__ */ jsx("div", {
17098
17718
  className: "py-8 text-center text-sm text-kumo-subtle",
17099
- children: "No API tokens yet. Create one to get started."
17719
+ children: _t({
17720
+ id: "aio6Hc",
17721
+ message: "No API tokens yet. Create one to get started."
17722
+ })
17100
17723
  }) : /* @__PURE__ */ jsx("div", {
17101
17724
  className: "divide-y",
17102
17725
  children: tokens.map((token) => /* @__PURE__ */ jsxs("div", {
@@ -17117,14 +17740,30 @@ function ApiTokenSettings() {
17117
17740
  /* @__PURE__ */ jsxs("div", {
17118
17741
  className: "flex gap-3 mt-1 text-xs text-kumo-subtle",
17119
17742
  children: [
17120
- /* @__PURE__ */ jsxs("span", { children: ["Scopes: ", token.scopes.join(", ")] }),
17121
- token.expiresAt && /* @__PURE__ */ jsxs("span", { children: ["Expires ", new Date(token.expiresAt).toLocaleDateString()] }),
17122
- token.lastUsedAt && /* @__PURE__ */ jsxs("span", { children: ["Last used ", new Date(token.lastUsedAt).toLocaleDateString()] })
17743
+ /* @__PURE__ */ jsx("span", { children: _t({
17744
+ id: "NSzA6n",
17745
+ message: "Scopes: {0}",
17746
+ values: { 0: token.scopes.join(", ") }
17747
+ }) }),
17748
+ token.expiresAt && /* @__PURE__ */ jsx("span", { children: _t({
17749
+ id: "oOfJ56",
17750
+ message: "Expires {0}",
17751
+ values: { 0: new Date(token.expiresAt).toLocaleDateString() }
17752
+ }) }),
17753
+ token.lastUsedAt && /* @__PURE__ */ jsx("span", { children: _t({
17754
+ id: "ylo1I0",
17755
+ message: "Last used {0}",
17756
+ values: { 0: new Date(token.lastUsedAt).toLocaleDateString() }
17757
+ }) })
17123
17758
  ]
17124
17759
  }),
17125
- /* @__PURE__ */ jsxs("div", {
17760
+ /* @__PURE__ */ jsx("div", {
17126
17761
  className: "text-xs text-kumo-subtle mt-0.5",
17127
- children: ["Created ", new Date(token.createdAt).toLocaleDateString()]
17762
+ children: _t({
17763
+ id: "MXSt4t",
17764
+ message: "Created {0}",
17765
+ values: { 0: new Date(token.createdAt).toLocaleDateString() }
17766
+ })
17128
17767
  })
17129
17768
  ]
17130
17769
  }), revokeConfirmId === token.id ? /* @__PURE__ */ jsxs("div", {
@@ -17136,14 +17775,23 @@ function ApiTokenSettings() {
17136
17775
  }),
17137
17776
  /* @__PURE__ */ jsx("span", {
17138
17777
  className: "text-sm text-kumo-danger",
17139
- children: "Revoke?"
17778
+ children: _t({
17779
+ id: "U4/l0o",
17780
+ message: "Revoke?"
17781
+ })
17140
17782
  }),
17141
17783
  /* @__PURE__ */ jsx(Button, {
17142
17784
  variant: "destructive",
17143
17785
  size: "sm",
17144
17786
  disabled: revokeMutation.isPending,
17145
17787
  onClick: () => revokeMutation.mutate(token.id),
17146
- children: revokeMutation.isPending ? "Revoking..." : "Confirm"
17788
+ children: revokeMutation.isPending ? _t({
17789
+ id: "zzTOEt",
17790
+ message: "Revoking..."
17791
+ }) : _t({
17792
+ id: "7VpPHA",
17793
+ message: "Confirm"
17794
+ })
17147
17795
  }),
17148
17796
  /* @__PURE__ */ jsx(Button, {
17149
17797
  variant: "outline",
@@ -17152,14 +17800,20 @@ function ApiTokenSettings() {
17152
17800
  setRevokeConfirmId(null);
17153
17801
  revokeMutation.reset();
17154
17802
  },
17155
- children: "Cancel"
17803
+ children: _t({
17804
+ id: "dEgA5A",
17805
+ message: "Cancel"
17806
+ })
17156
17807
  })
17157
17808
  ]
17158
17809
  }) : /* @__PURE__ */ jsx(Button, {
17159
17810
  variant: "ghost",
17160
17811
  shape: "square",
17161
17812
  onClick: () => setRevokeConfirmId(token.id),
17162
- "aria-label": "Revoke token",
17813
+ "aria-label": _t({
17814
+ id: "7/ePoE",
17815
+ message: "Revoke token"
17816
+ }),
17163
17817
  children: /* @__PURE__ */ jsx(Trash, { className: "h-4 w-4 text-kumo-subtle hover:text-kumo-danger" })
17164
17818
  })]
17165
17819
  }, token.id))
@@ -17168,7 +17822,8 @@ function ApiTokenSettings() {
17168
17822
  ]
17169
17823
  });
17170
17824
  }
17171
- function CreateTokenForm({ isCreating, error, onSubmit, onCancel }) {
17825
+ function CreateTokenForm({ expirySelectItems, isCreating, error, onSubmit, onCancel }) {
17826
+ const { _: _t2 } = useLingui();
17172
17827
  const [name, setName] = React.useState("");
17173
17828
  const [selectedScopes, setSelectedScopes] = React.useState(/* @__PURE__ */ new Set());
17174
17829
  const [expiry, setExpiry] = React.useState("30d");
@@ -17194,7 +17849,10 @@ function CreateTokenForm({ isCreating, error, onSubmit, onCancel }) {
17194
17849
  children: [
17195
17850
  /* @__PURE__ */ jsx("h2", {
17196
17851
  className: "text-lg font-semibold mb-4",
17197
- children: "Create New Token"
17852
+ children: _t2({
17853
+ id: "IBeakM",
17854
+ message: "Create New Token"
17855
+ })
17198
17856
  }),
17199
17857
  error && /* @__PURE__ */ jsxs("div", {
17200
17858
  className: "mb-4 rounded-lg border border-kumo-danger/50 bg-kumo-danger/10 p-3 flex items-center gap-2 text-sm text-kumo-danger",
@@ -17205,40 +17863,54 @@ function CreateTokenForm({ isCreating, error, onSubmit, onCancel }) {
17205
17863
  className: "space-y-4",
17206
17864
  children: [
17207
17865
  /* @__PURE__ */ jsx(Input, {
17208
- label: "Token Name",
17866
+ label: _t2({
17867
+ id: "jJrqb1",
17868
+ message: "Token Name"
17869
+ }),
17209
17870
  value: name,
17210
17871
  onChange: (e) => setName(e.target.value),
17211
- placeholder: "e.g., CI/CD Pipeline",
17872
+ placeholder: _t2({
17873
+ id: "O1SmJ+",
17874
+ message: "e.g., CI/CD Pipeline"
17875
+ }),
17212
17876
  required: true,
17213
17877
  autoFocus: true
17214
17878
  }),
17215
17879
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
17216
17880
  className: "text-sm font-medium mb-2",
17217
- children: "Scopes"
17881
+ children: _t2({
17882
+ id: "N/rFzD",
17883
+ message: "Scopes"
17884
+ })
17218
17885
  }), /* @__PURE__ */ jsx("div", {
17219
17886
  className: "space-y-2",
17220
- children: API_TOKEN_SCOPES.map((scope) => /* @__PURE__ */ jsxs("label", {
17221
- className: "flex items-start gap-2 cursor-pointer",
17222
- children: [/* @__PURE__ */ jsx(Checkbox, {
17223
- checked: selectedScopes.has(scope.value),
17224
- onCheckedChange: () => toggleScope(scope.value)
17225
- }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
17226
- className: "text-sm font-medium",
17227
- children: scope.label
17228
- }), /* @__PURE__ */ jsx("div", {
17229
- className: "text-xs text-kumo-subtle",
17230
- children: scope.description
17231
- })] })]
17232
- }, scope.value))
17887
+ children: API_TOKEN_SCOPE_VALUES.map(({ scope, label, description }) => {
17888
+ return /* @__PURE__ */ jsxs("label", {
17889
+ className: "flex items-start gap-2 cursor-pointer",
17890
+ children: [/* @__PURE__ */ jsx(Checkbox, {
17891
+ checked: selectedScopes.has(scope),
17892
+ onCheckedChange: () => toggleScope(scope)
17893
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
17894
+ className: "text-sm font-medium",
17895
+ children: _t2(label)
17896
+ }), /* @__PURE__ */ jsx("div", {
17897
+ className: "text-xs text-kumo-subtle",
17898
+ children: _t2(description)
17899
+ })] })]
17900
+ }, scope);
17901
+ })
17233
17902
  })] }),
17234
17903
  /* @__PURE__ */ jsx(Select, {
17235
- label: "Expiry",
17904
+ label: _t2({
17905
+ id: "agO/T/",
17906
+ message: "Expiry"
17907
+ }),
17236
17908
  value: expiry,
17237
17909
  onValueChange: (v) => v !== null && setExpiry(v),
17238
- items: Object.fromEntries(EXPIRY_OPTIONS.map((o) => [o.value, o.label])),
17910
+ items: expirySelectItems,
17239
17911
  children: EXPIRY_OPTIONS.map((option) => /* @__PURE__ */ jsx(Select.Option, {
17240
17912
  value: option.value,
17241
- children: option.label
17913
+ children: _t2(option.label)
17242
17914
  }, option.value))
17243
17915
  }),
17244
17916
  /* @__PURE__ */ jsxs("div", {
@@ -17246,12 +17918,21 @@ function CreateTokenForm({ isCreating, error, onSubmit, onCancel }) {
17246
17918
  children: [/* @__PURE__ */ jsx(Button, {
17247
17919
  type: "submit",
17248
17920
  disabled: !isValid || isCreating,
17249
- children: isCreating ? "Creating..." : "Create Token"
17921
+ children: isCreating ? _t2({
17922
+ id: "HM56Bx",
17923
+ message: "Creating..."
17924
+ }) : _t2({
17925
+ id: "JkdaXO",
17926
+ message: "Create Token"
17927
+ })
17250
17928
  }), /* @__PURE__ */ jsx(Button, {
17251
17929
  type: "button",
17252
17930
  variant: "outline",
17253
17931
  onClick: onCancel,
17254
- children: "Cancel"
17932
+ children: _t2({
17933
+ id: "dEgA5A",
17934
+ message: "Cancel"
17935
+ })
17255
17936
  })]
17256
17937
  })
17257
17938
  ]
@@ -17276,7 +17957,7 @@ function EmailSettings() {
17276
17957
  const timer = setTimeout(setStatus, 5e3, null);
17277
17958
  return () => clearTimeout(timer);
17278
17959
  }, [status]);
17279
- const { data: settings, isLoading } = useQuery({
17960
+ const { data: settings, isLoading, error: fetchError } = useQuery({
17280
17961
  queryKey: ["email-settings"],
17281
17962
  queryFn: fetchEmailSettings
17282
17963
  });
@@ -17305,6 +17986,27 @@ function EmailSettings() {
17305
17986
  className: "flex items-center justify-center py-12",
17306
17987
  children: /* @__PURE__ */ jsx(Loader, { size: "lg" })
17307
17988
  });
17989
+ if (fetchError) return /* @__PURE__ */ jsxs("div", {
17990
+ className: "space-y-6",
17991
+ children: [/* @__PURE__ */ jsxs("div", {
17992
+ className: "flex items-center gap-3",
17993
+ children: [/* @__PURE__ */ jsx(Link$1, {
17994
+ to: "/settings",
17995
+ children: /* @__PURE__ */ jsx(Button, {
17996
+ variant: "ghost",
17997
+ shape: "square",
17998
+ "aria-label": "Back to settings",
17999
+ children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
18000
+ })
18001
+ }), /* @__PURE__ */ jsx("h1", {
18002
+ className: "text-2xl font-bold",
18003
+ children: "Email Settings"
18004
+ })]
18005
+ }), /* @__PURE__ */ jsxs("div", {
18006
+ className: "flex items-center gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200",
18007
+ children: [/* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), getMutationError(fetchError) || "Failed to load email settings"]
18008
+ })]
18009
+ });
17308
18010
  return /* @__PURE__ */ jsxs("div", {
17309
18011
  className: "space-y-6",
17310
18012
  children: [
@@ -19123,7 +19825,10 @@ async function searchContent(query) {
19123
19825
  function buildNavItems(manifest, userRole) {
19124
19826
  const items = [{
19125
19827
  id: "dashboard",
19126
- title: "Dashboard",
19828
+ title: {
19829
+ id: "7p5kLi",
19830
+ message: "Dashboard"
19831
+ },
19127
19832
  to: "/",
19128
19833
  icon: SquaresFour,
19129
19834
  keywords: ["home", "overview"]
@@ -19138,7 +19843,10 @@ function buildNavItems(manifest, userRole) {
19138
19843
  });
19139
19844
  items.push({
19140
19845
  id: "media",
19141
- title: "Media Library",
19846
+ title: {
19847
+ id: "ia4TsE",
19848
+ message: "Media Library"
19849
+ },
19142
19850
  to: "/media",
19143
19851
  icon: Image$1,
19144
19852
  keywords: [
@@ -19148,35 +19856,50 @@ function buildNavItems(manifest, userRole) {
19148
19856
  ]
19149
19857
  }, {
19150
19858
  id: "menus",
19151
- title: "Menus",
19859
+ title: {
19860
+ id: "NXjnVQ",
19861
+ message: "Menus"
19862
+ },
19152
19863
  to: "/menus",
19153
19864
  icon: List,
19154
19865
  minRole: ROLE_EDITOR$2,
19155
19866
  keywords: ["navigation"]
19156
19867
  }, {
19157
19868
  id: "widgets",
19158
- title: "Widgets",
19869
+ title: {
19870
+ id: "tL6W2K",
19871
+ message: "Widgets"
19872
+ },
19159
19873
  to: "/widgets",
19160
19874
  icon: GridFour,
19161
19875
  minRole: ROLE_EDITOR$2,
19162
19876
  keywords: ["sidebar", "footer"]
19163
19877
  }, {
19164
19878
  id: "sections",
19165
- title: "Sections",
19879
+ title: {
19880
+ id: "R4OWFD",
19881
+ message: "Sections"
19882
+ },
19166
19883
  to: "/sections",
19167
19884
  icon: Stack,
19168
19885
  minRole: ROLE_EDITOR$2,
19169
19886
  keywords: ["page builder", "blocks"]
19170
19887
  }, {
19171
19888
  id: "content-types",
19172
- title: "Content Types",
19889
+ title: {
19890
+ id: "F7Jcuy",
19891
+ message: "Content Types"
19892
+ },
19173
19893
  to: "/content-types",
19174
19894
  icon: Database,
19175
19895
  minRole: ROLE_ADMIN$2,
19176
19896
  keywords: ["schema", "collections"]
19177
19897
  }, {
19178
19898
  id: "categories",
19179
- title: "Categories",
19899
+ title: {
19900
+ id: "NUrY9o",
19901
+ message: "Categories"
19902
+ },
19180
19903
  to: "/taxonomies/$taxonomy",
19181
19904
  params: { taxonomy: "category" },
19182
19905
  icon: FileText,
@@ -19184,7 +19907,10 @@ function buildNavItems(manifest, userRole) {
19184
19907
  keywords: ["taxonomy"]
19185
19908
  }, {
19186
19909
  id: "tags",
19187
- title: "Tags",
19910
+ title: {
19911
+ id: "OYHzN1",
19912
+ message: "Tags"
19913
+ },
19188
19914
  to: "/taxonomies/$taxonomy",
19189
19915
  params: { taxonomy: "tag" },
19190
19916
  icon: FileText,
@@ -19192,35 +19918,50 @@ function buildNavItems(manifest, userRole) {
19192
19918
  keywords: ["taxonomy"]
19193
19919
  }, {
19194
19920
  id: "users",
19195
- title: "Users",
19921
+ title: {
19922
+ id: "Sxm8rQ",
19923
+ message: "Users"
19924
+ },
19196
19925
  to: "/users",
19197
19926
  icon: Users,
19198
19927
  minRole: ROLE_ADMIN$2,
19199
19928
  keywords: ["accounts", "team"]
19200
19929
  }, {
19201
19930
  id: "plugins",
19202
- title: "Plugins",
19931
+ title: {
19932
+ id: "ohUJJM",
19933
+ message: "Plugins"
19934
+ },
19203
19935
  to: "/plugins-manager",
19204
19936
  icon: PuzzlePiece,
19205
19937
  minRole: ROLE_ADMIN$2,
19206
19938
  keywords: ["extensions", "add-ons"]
19207
19939
  }, {
19208
19940
  id: "import",
19209
- title: "Import",
19941
+ title: {
19942
+ id: "l3s5ri",
19943
+ message: "Import"
19944
+ },
19210
19945
  to: "/import/wordpress",
19211
19946
  icon: Upload,
19212
19947
  minRole: ROLE_ADMIN$2,
19213
19948
  keywords: ["wordpress", "migrate"]
19214
19949
  }, {
19215
19950
  id: "settings",
19216
- title: "Settings",
19951
+ title: {
19952
+ id: "Tz0i8g",
19953
+ message: "Settings"
19954
+ },
19217
19955
  to: "/settings",
19218
19956
  icon: Gear,
19219
19957
  minRole: ROLE_ADMIN$2,
19220
19958
  keywords: ["configuration", "preferences"]
19221
19959
  }, {
19222
19960
  id: "security",
19223
- title: "Security Settings",
19961
+ title: {
19962
+ id: "zro6wS",
19963
+ message: "Security Settings"
19964
+ },
19224
19965
  to: "/settings/security",
19225
19966
  icon: Gear,
19226
19967
  minRole: ROLE_ADMIN$2,
@@ -19241,16 +19982,17 @@ function buildNavItems(manifest, userRole) {
19241
19982
  }
19242
19983
  return items.filter((item) => !item.minRole || userRole >= item.minRole);
19243
19984
  }
19244
- function filterNavItems(items, query) {
19985
+ function filterNavItems(items, query, translate) {
19245
19986
  if (!query) return items;
19246
19987
  const lowerQuery = query.toLowerCase();
19247
19988
  return items.filter((item) => {
19248
- const titleMatch = item.title.toLowerCase().includes(lowerQuery);
19989
+ const titleMatch = (typeof item.title === "string" ? item.title : translate(item.title)).toLowerCase().includes(lowerQuery);
19249
19990
  const keywordMatch = item.keywords?.some((k) => k.toLowerCase().includes(lowerQuery));
19250
19991
  return titleMatch || keywordMatch;
19251
19992
  });
19252
19993
  }
19253
19994
  function AdminCommandPalette({ manifest }) {
19995
+ const { _: _t } = useLingui();
19254
19996
  const [open, setOpen] = React.useState(false);
19255
19997
  const [query, setQuery] = React.useState("");
19256
19998
  const navigate = useNavigate$1();
@@ -19265,14 +20007,22 @@ function AdminCommandPalette({ manifest }) {
19265
20007
  });
19266
20008
  const isPendingSearch = query.length >= 2 && query !== debouncedQuery || isSearching;
19267
20009
  const allNavItems = React.useMemo(() => buildNavItems(manifest, userRole), [manifest, userRole]);
19268
- const filteredNavItems = React.useMemo(() => filterNavItems(allNavItems, query), [allNavItems, query]);
20010
+ const filteredNavItems = React.useMemo(() => filterNavItems(allNavItems, query, _t), [
20011
+ allNavItems,
20012
+ query,
20013
+ _t
20014
+ ]);
19269
20015
  const resultGroups = React.useMemo(() => {
19270
20016
  const groups = [];
19271
20017
  if (filteredNavItems.length > 0) groups.push({
19272
- label: "Navigation",
20018
+ id: "navigation",
20019
+ label: {
20020
+ id: "UxKoFf",
20021
+ message: "Navigation"
20022
+ },
19273
20023
  items: filteredNavItems.map((item) => ({
19274
20024
  id: item.id,
19275
- title: item.title,
20025
+ title: typeof item.title === "string" ? item.title : _t(item.title),
19276
20026
  to: item.to,
19277
20027
  params: item.params,
19278
20028
  icon: /* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4" })
@@ -19280,7 +20030,7 @@ function AdminCommandPalette({ manifest }) {
19280
20030
  });
19281
20031
  if (searchResults?.items && searchResults.items.length > 0) {
19282
20032
  const contentItems = searchResults.items.map((result) => {
19283
- const collectionConfig = manifest.collections[result.collection];
20033
+ const collectionLabel = manifest.collections[result.collection]?.label ?? result.collection;
19284
20034
  return {
19285
20035
  id: `content-${result.id}`,
19286
20036
  title: result.title || result.slug,
@@ -19290,12 +20040,16 @@ function AdminCommandPalette({ manifest }) {
19290
20040
  id: result.id
19291
20041
  },
19292
20042
  icon: /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4" }),
19293
- description: collectionConfig?.label || result.collection,
20043
+ description: collectionLabel,
19294
20044
  collection: result.collection
19295
20045
  };
19296
20046
  });
19297
20047
  groups.push({
19298
- label: "Content",
20048
+ id: "content",
20049
+ label: {
20050
+ id: "4b3oEV",
20051
+ message: "Content"
20052
+ },
19299
20053
  items: contentItems
19300
20054
  });
19301
20055
  }
@@ -19303,7 +20057,8 @@ function AdminCommandPalette({ manifest }) {
19303
20057
  }, [
19304
20058
  filteredNavItems,
19305
20059
  searchResults,
19306
- manifest.collections
20060
+ manifest.collections,
20061
+ _t
19307
20062
  ]);
19308
20063
  useHotkeys("mod+k", (e) => {
19309
20064
  e.preventDefault();
@@ -19331,12 +20086,15 @@ function AdminCommandPalette({ manifest }) {
19331
20086
  items: resultGroups,
19332
20087
  value: query,
19333
20088
  onValueChange: setQuery,
19334
- itemToStringValue: (group) => group.label,
20089
+ itemToStringValue: (group) => _t(group.label),
19335
20090
  onSelect: handleSelect,
19336
20091
  getSelectableItems: (groups) => groups.flatMap((g) => g.items),
19337
20092
  children: [
19338
20093
  /* @__PURE__ */ jsx(CommandPalette.Input, {
19339
- placeholder: "Search pages and content...",
20094
+ placeholder: _t({
20095
+ id: "z/bggT",
20096
+ message: "Search pages and content..."
20097
+ }),
19340
20098
  leading: /* @__PURE__ */ jsx(MagnifyingGlass, {
19341
20099
  className: "h-4 w-4 text-kumo-subtle",
19342
20100
  weight: "bold"
@@ -19344,14 +20102,17 @@ function AdminCommandPalette({ manifest }) {
19344
20102
  }),
19345
20103
  /* @__PURE__ */ jsx(CommandPalette.List, { children: isPendingSearch ? /* @__PURE__ */ jsx(CommandPalette.Loading, {}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CommandPalette.Results, { children: (group) => /* @__PURE__ */ jsxs(CommandPalette.Group, {
19346
20104
  items: group.items,
19347
- children: [/* @__PURE__ */ jsx(CommandPalette.GroupLabel, { children: group.label }), /* @__PURE__ */ jsx(CommandPalette.Items, { children: (item) => /* @__PURE__ */ jsx(CommandPalette.ResultItem, {
20105
+ children: [/* @__PURE__ */ jsx(CommandPalette.GroupLabel, { children: _t(group.label) }), /* @__PURE__ */ jsx(CommandPalette.Items, { children: (item) => /* @__PURE__ */ jsx(CommandPalette.ResultItem, {
19348
20106
  value: item,
19349
20107
  title: item.title,
19350
20108
  description: item.description,
19351
20109
  icon: item.icon,
19352
20110
  onClick: (e) => handleItemClick(item, e)
19353
20111
  }, item.id) })]
19354
- }, group.label) }), /* @__PURE__ */ jsx(CommandPalette.Empty, { children: "No results found" })] }) }),
20112
+ }, group.id) }), /* @__PURE__ */ jsx(CommandPalette.Empty, { children: _t({
20113
+ id: "AxPAXW",
20114
+ message: "No results found"
20115
+ }) })] }) }),
19355
20116
  /* @__PURE__ */ jsx(CommandPalette.Footer, { children: /* @__PURE__ */ jsxs("div", {
19356
20117
  className: "flex items-center gap-4 text-kumo-subtle",
19357
20118
  children: [
@@ -19360,21 +20121,30 @@ function AdminCommandPalette({ manifest }) {
19360
20121
  children: [/* @__PURE__ */ jsx("kbd", {
19361
20122
  className: "rounded bg-kumo-control px-1.5 py-0.5 text-xs",
19362
20123
  children: "Enter"
19363
- }), /* @__PURE__ */ jsx("span", { children: "to select" })]
20124
+ }), /* @__PURE__ */ jsx("span", { children: _t({
20125
+ id: "jpBN9M",
20126
+ message: "to select"
20127
+ }) })]
19364
20128
  }),
19365
20129
  /* @__PURE__ */ jsxs("span", {
19366
20130
  className: "flex items-center gap-1",
19367
20131
  children: [/* @__PURE__ */ jsxs("kbd", {
19368
20132
  className: "rounded bg-kumo-control px-1.5 py-0.5 text-xs",
19369
20133
  children: [IS_MAC ? "Cmd" : "Ctrl", "+Enter"]
19370
- }), /* @__PURE__ */ jsx("span", { children: "new tab" })]
20134
+ }), /* @__PURE__ */ jsx("span", { children: _t({
20135
+ id: "k2rZ7L",
20136
+ message: "new tab"
20137
+ }) })]
19371
20138
  }),
19372
20139
  /* @__PURE__ */ jsxs("span", {
19373
20140
  className: "flex items-center gap-1",
19374
20141
  children: [/* @__PURE__ */ jsx("kbd", {
19375
20142
  className: "rounded bg-kumo-control px-1.5 py-0.5 text-xs",
19376
20143
  children: "Esc"
19377
- }), /* @__PURE__ */ jsx("span", { children: "to close" })]
20144
+ }), /* @__PURE__ */ jsx("span", { children: _t({
20145
+ id: "UbVgIu",
20146
+ message: "to close"
20147
+ }) })]
19378
20148
  })
19379
20149
  ]
19380
20150
  }) })
@@ -19489,20 +20259,13 @@ function SidebarNav({ manifest }) {
19489
20259
  icon: Stack,
19490
20260
  minRole: ROLE_EDITOR$1
19491
20261
  },
19492
- {
19493
- to: "/taxonomies/$taxonomy",
19494
- label: "Categories",
19495
- icon: FileText,
19496
- params: { taxonomy: "category" },
19497
- minRole: ROLE_EDITOR$1
19498
- },
19499
- {
20262
+ ...manifest.taxonomies.map((tax) => ({
19500
20263
  to: "/taxonomies/$taxonomy",
19501
- label: "Tags",
20264
+ label: tax.label,
19502
20265
  icon: FileText,
19503
- params: { taxonomy: "tag" },
20266
+ params: { taxonomy: tax.name },
19504
20267
  minRole: ROLE_EDITOR$1
19505
- },
20268
+ })),
19506
20269
  {
19507
20270
  to: "/bylines",
19508
20271
  label: "Bylines",
@@ -19710,7 +20473,11 @@ function SidebarNav({ manifest }) {
19710
20473
  ] }),
19711
20474
  /* @__PURE__ */ jsx(KumoSidebar.Footer, { children: /* @__PURE__ */ jsxs("p", {
19712
20475
  className: "emdash-nav-label px-3 py-2 text-[11px] text-white/30",
19713
- children: ["EmDash CMS v", manifest.version || "0.0.0"]
20476
+ children: [
20477
+ "EmDash CMS v",
20478
+ manifest.version || "0.0.0",
20479
+ manifest.commit && ` (${manifest.commit})`
20480
+ ]
19714
20481
  }) })
19715
20482
  ]
19716
20483
  })] });
@@ -19851,13 +20618,71 @@ function Header() {
19851
20618
  *
19852
20619
  * Shown to new users on their first login to welcome them to EmDash.
19853
20620
  */
19854
- function getRoleLabel$1(role) {
19855
- if (role >= 50) return "Administrator";
19856
- if (role >= 40) return "Editor";
19857
- if (role >= 30) return "Author";
19858
- if (role >= 20) return "Contributor";
19859
- return "Subscriber";
19860
- }
20621
+ const MSG_ROLE_ADMINISTRATOR = {
20622
+ id: "Faptqt",
20623
+ message: "Administrator"
20624
+ };
20625
+ const MSG_ROLE_EDITOR = {
20626
+ id: "uBAxNB",
20627
+ message: "Editor"
20628
+ };
20629
+ const MSG_ROLE_AUTHOR = {
20630
+ id: "VbeIOx",
20631
+ message: "Author"
20632
+ };
20633
+ const MSG_ROLE_CONTRIBUTOR = {
20634
+ id: "3L51iw",
20635
+ message: "Contributor"
20636
+ };
20637
+ const MSG_ROLE_SUBSCRIBER = {
20638
+ id: "M2vfqB",
20639
+ message: "Subscriber"
20640
+ };
20641
+ function roleDescriptor(role) {
20642
+ if (role >= 50) return MSG_ROLE_ADMINISTRATOR;
20643
+ if (role >= 40) return MSG_ROLE_EDITOR;
20644
+ if (role >= 30) return MSG_ROLE_AUTHOR;
20645
+ if (role >= 20) return MSG_ROLE_CONTRIBUTOR;
20646
+ return MSG_ROLE_SUBSCRIBER;
20647
+ }
20648
+ const MSG_ACCOUNT_CREATED = {
20649
+ id: "ATCJJC",
20650
+ message: "Your account has been created successfully."
20651
+ };
20652
+ const MSG_YOUR_ROLE = {
20653
+ id: "T25KCz",
20654
+ message: "Your Role"
20655
+ };
20656
+ const MSG_SCOPE_ADMIN = {
20657
+ id: "T8yDG9",
20658
+ message: "You have full access to manage this site, including users, settings, and all content."
20659
+ };
20660
+ const MSG_SCOPE_EDITOR = {
20661
+ id: "Ic/8JR",
20662
+ message: "You can manage content, media, menus, and taxonomies."
20663
+ };
20664
+ const MSG_SCOPE_AUTHOR = {
20665
+ id: "kKNInt",
20666
+ message: "You can create and edit your own content."
20667
+ };
20668
+ const MSG_SCOPE_CONTRIBUTOR = {
20669
+ id: "nzN9WF",
20670
+ message: "You can view and contribute to the site."
20671
+ };
20672
+ function scopeDescriptor(isAdmin, userRole) {
20673
+ if (isAdmin) return MSG_SCOPE_ADMIN;
20674
+ if (userRole >= 40) return MSG_SCOPE_EDITOR;
20675
+ if (userRole >= 30) return MSG_SCOPE_AUTHOR;
20676
+ return MSG_SCOPE_CONTRIBUTOR;
20677
+ }
20678
+ const MSG_ADMIN_INVITE = {
20679
+ id: "QwxfcZ",
20680
+ message: "As an administrator, you can invite other users from the Users section."
20681
+ };
20682
+ const MSG_CLOSE = {
20683
+ id: "yz7wBu",
20684
+ message: "Close"
20685
+ };
19861
20686
  async function dismissWelcome() {
19862
20687
  const response = await apiFetch("/_emdash/api/auth/me", {
19863
20688
  method: "POST",
@@ -19867,6 +20692,7 @@ async function dismissWelcome() {
19867
20692
  if (!response.ok) await throwResponseError(response, "Failed to dismiss welcome");
19868
20693
  }
19869
20694
  function WelcomeModal({ open, onClose, userName, userRole }) {
20695
+ const { _: _t } = useLingui();
19870
20696
  const queryClient = useQueryClient();
19871
20697
  const dismissMutation = useMutation({
19872
20698
  mutationFn: dismissWelcome,
@@ -19887,28 +20713,36 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
19887
20713
  const handleGetStarted = () => {
19888
20714
  dismissMutation.mutate();
19889
20715
  };
19890
- const roleLabel = getRoleLabel$1(userRole);
20716
+ const roleLabel = _t(roleDescriptor(userRole));
19891
20717
  const isAdmin = userRole >= 50;
20718
+ const firstName = userName?.split(" ")?.[0]?.trim() ?? "";
20719
+ const titleDescriptor = firstName.length > 0 ? {
20720
+ id: "7vQyJA",
20721
+ message: "Welcome to EmDash, {firstName}!",
20722
+ values: { firstName }
20723
+ } : {
20724
+ id: "N5RCak",
20725
+ message: "Welcome to EmDash!"
20726
+ };
19892
20727
  return /* @__PURE__ */ jsx(Dialog.Root, {
19893
20728
  open,
19894
20729
  onOpenChange: (isOpen) => !isOpen && handleGetStarted(),
19895
20730
  children: /* @__PURE__ */ jsxs(Dialog, {
19896
20731
  className: "p-6 sm:max-w-md",
19897
- size: "lg",
19898
20732
  children: [
19899
20733
  /* @__PURE__ */ jsxs("div", {
19900
20734
  className: "flex items-start justify-between gap-4",
19901
20735
  children: [/* @__PURE__ */ jsx("div", { className: "flex-1" }), /* @__PURE__ */ jsx(Dialog.Close, {
19902
- "aria-label": "Close",
20736
+ "aria-label": _t(MSG_CLOSE),
19903
20737
  render: (props) => /* @__PURE__ */ jsxs(Button, {
19904
20738
  ...props,
19905
20739
  variant: "ghost",
19906
20740
  shape: "square",
19907
- "aria-label": "Close",
20741
+ "aria-label": _t(MSG_CLOSE),
19908
20742
  className: "absolute right-4 top-4",
19909
20743
  children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
19910
20744
  className: "sr-only",
19911
- children: "Close"
20745
+ children: _t(MSG_CLOSE)
19912
20746
  })]
19913
20747
  })
19914
20748
  })]
@@ -19920,17 +20754,13 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
19920
20754
  className: "mx-auto mb-4",
19921
20755
  children: /* @__PURE__ */ jsx(LogoIcon, { className: "h-16 w-16" })
19922
20756
  }),
19923
- /* @__PURE__ */ jsxs(Dialog.Title, {
20757
+ /* @__PURE__ */ jsx(Dialog.Title, {
19924
20758
  className: "text-2xl font-semibold leading-none tracking-tight",
19925
- children: [
19926
- "Welcome to EmDash",
19927
- userName ? `, ${userName.split(" ")[0]}` : "",
19928
- "!"
19929
- ]
20759
+ children: _t(titleDescriptor)
19930
20760
  }),
19931
20761
  /* @__PURE__ */ jsx(Dialog.Description, {
19932
20762
  className: "text-base text-kumo-subtle",
19933
- children: "Your account has been created successfully."
20763
+ children: _t(MSG_ACCOUNT_CREATED)
19934
20764
  })
19935
20765
  ]
19936
20766
  }),
@@ -19941,7 +20771,7 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
19941
20771
  children: [
19942
20772
  /* @__PURE__ */ jsx("div", {
19943
20773
  className: "text-sm font-medium",
19944
- children: "Your Role"
20774
+ children: _t(MSG_YOUR_ROLE)
19945
20775
  }),
19946
20776
  /* @__PURE__ */ jsx("div", {
19947
20777
  className: "text-lg font-semibold text-kumo-brand",
@@ -19949,20 +20779,12 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
19949
20779
  }),
19950
20780
  /* @__PURE__ */ jsx("p", {
19951
20781
  className: "text-sm text-kumo-subtle mt-1",
19952
- children: isAdmin ? "You have full access to manage this site, including users, settings, and all content." : userRole >= 40 ? "You can manage content, media, menus, and taxonomies." : userRole >= 30 ? "You can create and edit your own content." : "You can view and contribute to the site."
20782
+ children: _t(scopeDescriptor(isAdmin, userRole))
19953
20783
  })
19954
20784
  ]
19955
- }), isAdmin && /* @__PURE__ */ jsxs("p", {
20785
+ }), isAdmin && /* @__PURE__ */ jsx("p", {
19956
20786
  className: "text-sm text-kumo-subtle",
19957
- children: [
19958
- "As an administrator, you can invite other users from the",
19959
- " ",
19960
- /* @__PURE__ */ jsx("span", {
19961
- className: "font-medium",
19962
- children: "Users"
19963
- }),
19964
- " section."
19965
- ]
20787
+ children: _t(MSG_ADMIN_INVITE)
19966
20788
  })]
19967
20789
  }),
19968
20790
  /* @__PURE__ */ jsx("div", {
@@ -19971,7 +20793,13 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
19971
20793
  onClick: handleGetStarted,
19972
20794
  disabled: dismissMutation.isPending,
19973
20795
  size: "lg",
19974
- children: dismissMutation.isPending ? "Loading..." : "Get Started"
20796
+ children: dismissMutation.isPending ? _t({
20797
+ id: "Z3FXyt",
20798
+ message: "Loading..."
20799
+ }) : _t({
20800
+ id: "c3b0B0",
20801
+ message: "Get Started"
20802
+ })
19975
20803
  })
19976
20804
  })
19977
20805
  ]
@@ -21450,22 +22278,29 @@ function isPaletteItem(data) {
21450
22278
  /** Built-in widget types available in the palette */
21451
22279
  const BUILTIN_WIDGETS = [{
21452
22280
  id: "palette-content",
21453
- label: "Content Block",
21454
- description: "Rich text content",
21455
- input: {
21456
- type: "content",
21457
- title: "Content Block"
21458
- }
22281
+ label: {
22282
+ id: "ESoS5n",
22283
+ message: "Content Block"
22284
+ },
22285
+ description: {
22286
+ id: "oYoiTA",
22287
+ message: "Rich text content"
22288
+ },
22289
+ input: { type: "content" }
21459
22290
  }, {
21460
22291
  id: "palette-menu",
21461
- label: "Menu",
21462
- description: "Display a navigation menu",
21463
- input: {
21464
- type: "menu",
21465
- title: "Menu"
21466
- }
22292
+ label: {
22293
+ id: "zucql+",
22294
+ message: "Menu"
22295
+ },
22296
+ description: {
22297
+ id: "an2U5p",
22298
+ message: "Display a navigation menu"
22299
+ },
22300
+ input: { type: "menu" }
21467
22301
  }];
21468
22302
  function Widgets() {
22303
+ const { _: _t } = useLingui();
21469
22304
  const queryClient = useQueryClient();
21470
22305
  const toastManager = Toast.useToastManager();
21471
22306
  const [isCreateAreaOpen, setIsCreateAreaOpen] = React.useState(false);
@@ -21707,9 +22542,12 @@ function Widgets() {
21707
22542
  className: "space-y-2",
21708
22543
  children: [BUILTIN_WIDGETS.map((item) => /* @__PURE__ */ jsx(DraggablePaletteItem, {
21709
22544
  id: item.id,
21710
- label: item.label,
21711
- description: item.description,
21712
- widgetInput: item.input
22545
+ label: _t(item.label),
22546
+ description: _t(item.description),
22547
+ widgetInput: {
22548
+ ...item.input,
22549
+ title: _t(item.label)
22550
+ }
21713
22551
  }, item.id)), components.map((comp) => /* @__PURE__ */ jsx(DraggablePaletteItem, {
21714
22552
  id: `palette-comp-${comp.id}`,
21715
22553
  label: comp.label,
@@ -24473,50 +25311,11 @@ function BylinesPage() {
24473
25311
 
24474
25312
  //#endregion
24475
25313
  //#region src/components/users/RoleBadge.tsx
24476
- /** Role level to name mapping */
24477
- const ROLE_CONFIG = {
24478
- 10: {
24479
- label: "Subscriber",
24480
- color: "gray",
24481
- description: "Can view content"
24482
- },
24483
- 20: {
24484
- label: "Contributor",
24485
- color: "blue",
24486
- description: "Can create content"
24487
- },
24488
- 30: {
24489
- label: "Author",
24490
- color: "green",
24491
- description: "Can publish own content"
24492
- },
24493
- 40: {
24494
- label: "Editor",
24495
- color: "purple",
24496
- description: "Can manage all content"
24497
- },
24498
- 50: {
24499
- label: "Admin",
24500
- color: "red",
24501
- description: "Full access"
24502
- }
24503
- };
24504
- /** Get role config, with fallback for unknown roles */
24505
- function getRoleConfig(role) {
24506
- return ROLE_CONFIG[role] ?? {
24507
- label: `Role ${role}`,
24508
- color: "gray",
24509
- description: "Unknown role"
24510
- };
24511
- }
24512
- /** Get role label from role level */
24513
- function getRoleLabel(role) {
24514
- return getRoleConfig(role).label;
24515
- }
24516
25314
  /**
24517
25315
  * Role badge component with semantic colors
24518
25316
  */
24519
25317
  function RoleBadge({ role, size = "sm", showDescription = false, className }) {
25318
+ const { _: _t } = useLingui();
24520
25319
  const config = getRoleConfig(role);
24521
25320
  return /* @__PURE__ */ jsxs("span", {
24522
25321
  className: cn("inline-flex items-center rounded-full font-medium", {
@@ -24529,41 +25328,13 @@ function RoleBadge({ role, size = "sm", showDescription = false, className }) {
24529
25328
  purple: "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200",
24530
25329
  red: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"
24531
25330
  }[config.color], className),
24532
- title: showDescription ? void 0 : config.description,
24533
- children: [config.label, showDescription && /* @__PURE__ */ jsxs("span", {
25331
+ title: showDescription ? void 0 : _t(config.description),
25332
+ children: [_t(config.label), showDescription && /* @__PURE__ */ jsxs("span", {
24534
25333
  className: "ml-1 opacity-75",
24535
- children: ["- ", config.description]
25334
+ children: ["- ", _t(config.description)]
24536
25335
  })]
24537
25336
  });
24538
25337
  }
24539
- /** List of all roles for dropdowns */
24540
- const ROLES = [
24541
- {
24542
- value: 10,
24543
- label: "Subscriber",
24544
- description: "Can view content"
24545
- },
24546
- {
24547
- value: 20,
24548
- label: "Contributor",
24549
- description: "Can create content"
24550
- },
24551
- {
24552
- value: 30,
24553
- label: "Author",
24554
- description: "Can publish own content"
24555
- },
24556
- {
24557
- value: 40,
24558
- label: "Editor",
24559
- description: "Can manage all content"
24560
- },
24561
- {
24562
- value: 50,
24563
- label: "Admin",
24564
- description: "Full access"
24565
- }
24566
- ];
24567
25338
 
24568
25339
  //#endregion
24569
25340
  //#region src/components/users/UserList.tsx
@@ -24571,6 +25342,22 @@ const ROLES = [
24571
25342
  * User list component with search, filter, and table display
24572
25343
  */
24573
25344
  function UserList({ users, isLoading, hasMore, searchQuery, roleFilter, onSearchChange, onRoleFilterChange, onSelectUser, onInviteUser, onLoadMore }) {
25345
+ const { _: _t } = useLingui();
25346
+ const { roles, roleLabels } = useRolesConfig();
25347
+ const roleFilterSelectItems = React.useMemo(() => ({
25348
+ all: _t({
25349
+ id: "Hm90t3",
25350
+ message: "All roles"
25351
+ }),
25352
+ ...roleLabels
25353
+ }), [_t, roleLabels]);
25354
+ const roleFilterSelectOptions = React.useMemo(() => [{
25355
+ value: "all",
25356
+ label: _t({
25357
+ id: "Hm90t3",
25358
+ message: "All roles"
25359
+ })
25360
+ }, ...roles], [_t, roles]);
24574
25361
  return /* @__PURE__ */ jsxs("div", {
24575
25362
  className: "space-y-4",
24576
25363
  children: [
@@ -24600,21 +25387,15 @@ function UserList({ users, isLoading, hasMore, searchQuery, roleFilter, onSearch
24600
25387
  onChange: (e) => onSearchChange(e.target.value),
24601
25388
  "aria-label": "Search users"
24602
25389
  })]
24603
- }), /* @__PURE__ */ jsxs(Select, {
25390
+ }), /* @__PURE__ */ jsx(Select, {
24604
25391
  value: roleFilter?.toString() ?? "all",
24605
25392
  onValueChange: (value) => onRoleFilterChange(value === "all" || value === null ? void 0 : parseInt(value, 10)),
24606
- items: {
24607
- all: "All roles",
24608
- ...Object.fromEntries(ROLES.map((r) => [r.value.toString(), r.label]))
24609
- },
25393
+ items: roleFilterSelectItems,
24610
25394
  "aria-label": "Filter by role",
24611
- children: [/* @__PURE__ */ jsx(Select.Option, {
24612
- value: "all",
24613
- children: "All roles"
24614
- }), ROLES.map((role) => /* @__PURE__ */ jsx(Select.Option, {
24615
- value: role.value.toString(),
24616
- children: role.label
24617
- }, role.value))]
25395
+ children: roleFilterSelectOptions.map((option) => /* @__PURE__ */ jsx(Select.Option, {
25396
+ value: option.value,
25397
+ children: option.label
25398
+ }, option.value))
24618
25399
  })]
24619
25400
  }),
24620
25401
  /* @__PURE__ */ jsx("div", {
@@ -24791,6 +25572,7 @@ function UserListSkeleton() {
24791
25572
  * User detail slide-over panel with inline editing
24792
25573
  */
24793
25574
  function UserDetail({ user, isLoading, isOpen, isSaving, isSendingRecovery, recoverySent, recoveryError, currentUserId, onClose, onSave, onDisable, onEnable, onSendRecovery }) {
25575
+ const { roles, roleLabels, getRoleLabel } = useRolesConfig();
24794
25576
  const [name, setName] = React.useState(user?.name ?? "");
24795
25577
  const [email, setEmail] = React.useState(user?.email ?? "");
24796
25578
  const [role, setRole] = React.useState(user?.role ?? 30);
@@ -24902,8 +25684,8 @@ function UserDetail({ user, isLoading, isOpen, isSaving, isSendingRecovery, reco
24902
25684
  label: "Role",
24903
25685
  value: role.toString(),
24904
25686
  onValueChange: (v) => v !== null && setRole(parseInt(v, 10)),
24905
- items: Object.fromEntries(ROLES.map((r) => [r.value.toString(), r.label])),
24906
- children: ROLES.map((r) => /* @__PURE__ */ jsx(Select.Option, {
25687
+ items: roleLabels,
25688
+ children: roles.map((r) => /* @__PURE__ */ jsx(Select.Option, {
24907
25689
  value: r.value.toString(),
24908
25690
  children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: r.label }), /* @__PURE__ */ jsx("div", {
24909
25691
  className: "text-xs text-kumo-subtle",
@@ -25114,6 +25896,7 @@ function UserDetailSkeleton() {
25114
25896
  * Invite user modal — sends invite email or shows copy-link fallback
25115
25897
  */
25116
25898
  function InviteUserModal({ open, isSending, error, inviteUrl, onOpenChange, onInvite }) {
25899
+ const { roles, roleLabels } = useRolesConfig();
25117
25900
  const [email, setEmail] = React.useState("");
25118
25901
  const [role, setRole] = React.useState(30);
25119
25902
  const [copied, setCopied] = React.useState(false);
@@ -25241,8 +26024,8 @@ function InviteUserModal({ open, isSending, error, inviteUrl, onOpenChange, onIn
25241
26024
  label: "Role",
25242
26025
  value: role.toString(),
25243
26026
  onValueChange: (v) => v !== null && setRole(parseInt(v, 10)),
25244
- items: Object.fromEntries(ROLES.map((r) => [r.value.toString(), r.label])),
25245
- children: ROLES.map((r) => /* @__PURE__ */ jsx(Select.Option, {
26027
+ items: roleLabels,
26028
+ children: roles.map((r) => /* @__PURE__ */ jsx(Select.Option, {
25246
26029
  value: r.value.toString(),
25247
26030
  children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: r.label }), /* @__PURE__ */ jsx("div", {
25248
26031
  className: "text-xs text-kumo-subtle",
@@ -25297,6 +26080,7 @@ function useDebounce(value, delay) {
25297
26080
  return debouncedValue;
25298
26081
  }
25299
26082
  function UsersPage() {
26083
+ const { getRoleLabel } = useRolesConfig();
25300
26084
  const queryClient = useQueryClient();
25301
26085
  const [searchQuery, setSearchQuery] = React.useState("");
25302
26086
  const [roleFilter, setRoleFilter] = React.useState();
@@ -25551,6 +26335,10 @@ const adminLayoutRoute = createRoute({
25551
26335
  id: "_admin",
25552
26336
  component: RootComponent
25553
26337
  });
26338
+ if (typeof window !== "undefined" && typeof window.requestIdleCallback === "undefined") {
26339
+ window.requestIdleCallback = (cb) => setTimeout(cb, 50);
26340
+ window.cancelIdleCallback = (id) => clearTimeout(id);
26341
+ }
25554
26342
  function RootComponent() {
25555
26343
  const { data: manifest, isLoading, error } = useQuery({
25556
26344
  queryKey: ["manifest"],
@@ -25683,6 +26471,9 @@ function ContentListPage() {
25683
26471
  });
25684
26472
  }
25685
26473
  });
26474
+ const items = React.useMemo(() => {
26475
+ return data?.pages.flatMap((page) => page.items) || [];
26476
+ }, [data]);
25686
26477
  if (!manifest) return /* @__PURE__ */ jsx(LoadingScreen, {});
25687
26478
  const collectionConfig = manifest.collections[collection];
25688
26479
  if (!collectionConfig) return /* @__PURE__ */ jsx(NotFoundPage, { message: `Collection "${collection}" not found` });
@@ -25694,9 +26485,6 @@ function ContentListPage() {
25694
26485
  search: { locale: locale || void 0 }
25695
26486
  });
25696
26487
  };
25697
- const items = React.useMemo(() => {
25698
- return data?.pages.flatMap((page) => page.items) || [];
25699
- }, [data]);
25700
26488
  return /* @__PURE__ */ jsx(ContentList, {
25701
26489
  collection,
25702
26490
  collectionLabel: collectionConfig.label,
@@ -25705,7 +26493,7 @@ function ContentListPage() {
25705
26493
  isLoading: isLoading || isFetchingNextPage,
25706
26494
  isTrashedLoading,
25707
26495
  hasMore: !!hasNextPage,
25708
- onLoadMore: () => void fetchNextPage(),
26496
+ onLoadMore: React.useCallback(() => void fetchNextPage(), [fetchNextPage]),
25709
26497
  trashedCount: trashedData?.items?.length || 0,
25710
26498
  onDelete: (id) => deleteMutation.mutate(id),
25711
26499
  onRestore: (id) => restoreMutation.mutate(id),
@@ -25729,10 +26517,12 @@ function getPluginBlocks(manifest) {
25729
26517
  const contentNewRoute = createRoute({
25730
26518
  getParentRoute: () => adminLayoutRoute,
25731
26519
  path: "/content/$collection/new",
25732
- component: ContentNewPage
26520
+ component: ContentNewPage,
26521
+ validateSearch: (search) => ({ locale: typeof search.locale === "string" ? search.locale : void 0 })
25733
26522
  });
25734
26523
  function ContentNewPage() {
25735
26524
  const { collection } = useParams({ from: "/_admin/content/$collection/new" });
26525
+ const { locale } = useSearch({ from: "/_admin/content/$collection/new" });
25736
26526
  const navigate = useNavigate();
25737
26527
  const queryClient = useQueryClient();
25738
26528
  const [selectedBylines, setSelectedBylines] = React.useState([]);
@@ -25741,7 +26531,10 @@ function ContentNewPage() {
25741
26531
  queryFn: fetchManifest
25742
26532
  });
25743
26533
  const createMutation = useMutation({
25744
- mutationFn: (data) => createContent(collection, data),
26534
+ mutationFn: (data) => createContent(collection, {
26535
+ ...data,
26536
+ locale
26537
+ }),
25745
26538
  onSuccess: (result) => {
25746
26539
  queryClient.invalidateQueries({ queryKey: ["content", collection] });
25747
26540
  navigate({
@@ -25808,11 +26601,13 @@ function ContentNewPage() {
25808
26601
  const contentEditRoute = createRoute({
25809
26602
  getParentRoute: () => adminLayoutRoute,
25810
26603
  path: "/content/$collection/$id",
25811
- component: ContentEditPage
26604
+ component: ContentEditPage,
26605
+ validateSearch: (search) => ({ ...typeof search.field === "string" && { field: search.field } })
25812
26606
  });
25813
26607
  const ROLE_EDITOR = 40;
25814
26608
  function ContentEditPage() {
25815
26609
  const { collection, id } = useParams({ from: "/_admin/content/$collection/$id" });
26610
+ const searchParams = useSearch({ from: "/_admin/content/$collection/$id" });
25816
26611
  const queryClient = useQueryClient();
25817
26612
  const navigate = useNavigate();
25818
26613
  const toastManager = Toast.useToastManager();
@@ -25829,6 +26624,29 @@ function ContentEditPage() {
25829
26624
  ],
25830
26625
  queryFn: () => fetchContent(collection, id)
25831
26626
  });
26627
+ React.useEffect(() => {
26628
+ if (typeof searchParams.field !== "string" || isLoading) return;
26629
+ const timeoutId = requestIdleCallback(() => {
26630
+ const el = document.getElementById(`field-${searchParams.field}`);
26631
+ if (el) {
26632
+ el.scrollIntoView({
26633
+ behavior: "smooth",
26634
+ block: "center"
26635
+ });
26636
+ el.focus();
26637
+ const { field: _, ...preservedSearch } = searchParams;
26638
+ navigate({
26639
+ search: preservedSearch,
26640
+ replace: true
26641
+ });
26642
+ }
26643
+ });
26644
+ return () => cancelIdleCallback(timeoutId);
26645
+ }, [
26646
+ searchParams,
26647
+ isLoading,
26648
+ navigate
26649
+ ]);
25832
26650
  const { data: translationsData } = useQuery({
25833
26651
  queryKey: [
25834
26652
  "translations",
@@ -26826,19 +27644,30 @@ const router = createAdminRouter(queryClient);
26826
27644
  * Main Admin Application
26827
27645
  */
26828
27646
  const EMPTY_PLUGINS = {};
26829
- function AdminApp({ pluginAdmins = EMPTY_PLUGINS }) {
27647
+ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, locale = "en", messages = {} }) {
26830
27648
  React.useEffect(() => {
26831
27649
  document.getElementById("emdash-boot-loader")?.remove();
26832
27650
  }, []);
26833
- return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(Toasty, { children: /* @__PURE__ */ jsx(PluginAdminProvider, {
26834
- pluginAdmins,
26835
- children: /* @__PURE__ */ jsx(QueryClientProvider, {
26836
- client: queryClient,
26837
- children: /* @__PURE__ */ jsx(RouterProvider, { router })
26838
- })
26839
- }) }) });
27651
+ const i18nInitialized = React.useRef(false);
27652
+ if (!i18nInitialized.current) {
27653
+ i18n.loadAndActivate({
27654
+ locale,
27655
+ messages
27656
+ });
27657
+ i18nInitialized.current = true;
27658
+ }
27659
+ return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(I18nProvider, {
27660
+ i18n,
27661
+ children: /* @__PURE__ */ jsx(Toasty, { children: /* @__PURE__ */ jsx(PluginAdminProvider, {
27662
+ pluginAdmins,
27663
+ children: /* @__PURE__ */ jsx(QueryClientProvider, {
27664
+ client: queryClient,
27665
+ children: /* @__PURE__ */ jsx(RouterProvider, { router })
27666
+ })
27667
+ }) })
27668
+ }) });
26840
27669
  }
26841
27670
 
26842
27671
  //#endregion
26843
- export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, CAPABILITY_LABELS, ContentEditor, ContentList, Dashboard, Header, Link, LoginPage, MediaLibrary, MediaPickerModal, PasskeyLogin, PasskeyRegistration, PluginAdminProvider, PortableTextEditor, SaveButton, Settings, SetupWizard, Shell, KumoSidebar as Sidebar, SidebarNav, analyzeWpPluginSite, analyzeWxr, apiFetch, bulkCommentAction, checkPluginUpdates, cn, compareRevisions, completeSignup, createAdminRouter, createAllowedDomain, createApiToken, createByline, createCollection, createContent, createField, createMenu, createMenuItem, createRedirect, createSection, createTaxonomy, createTerm, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteCollection, deleteComment, deleteContent, deleteField, deleteFromProvider, deleteMedia, deleteMenu, deleteMenuItem, deletePasskey, deleteRedirect, deleteSection, deleteTerm, deleteWidget, deleteWidgetArea, describeCapability, disablePlugin, disableUser, discardDraft, duplicateContent, enablePlugin, enableUser, executeWpPluginImport, executeWxrImport, fetch404Summary, fetchAllowedDomains, fetchApiTokens, fetchByline, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, installMarketplacePlugin, inviteUser, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useCurrentUser, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, verifySignupToken };
27672
+ export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, CAPABILITY_LABELS, ContentEditor, ContentList, DEFAULT_LOCALE, Dashboard, Header, Link, LoginPage, MediaLibrary, MediaPickerModal, PasskeyLogin, PasskeyRegistration, PluginAdminProvider, PortableTextEditor, SUPPORTED_LOCALES, SUPPORTED_LOCALE_CODES, SaveButton, Settings, SetupWizard, Shell, KumoSidebar as Sidebar, SidebarNav, analyzeWpPluginSite, analyzeWxr, apiFetch, bulkCommentAction, checkPluginUpdates, cn, compareRevisions, completeSignup, createAdminRouter, createAllowedDomain, createApiToken, createByline, createCollection, createContent, createField, createMenu, createMenuItem, createRedirect, createSection, createTaxonomy, createTerm, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteCollection, deleteComment, deleteContent, deleteField, deleteFromProvider, deleteMedia, deleteMenu, deleteMenuItem, deletePasskey, deleteRedirect, deleteSection, deleteTerm, deleteWidget, deleteWidgetArea, describeCapability, disablePlugin, disableUser, discardDraft, duplicateContent, enablePlugin, enableUser, executeWpPluginImport, executeWxrImport, fetch404Summary, fetchAllowedDomains, fetchApiTokens, fetchByline, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, installMarketplacePlugin, inviteUser, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, resolveLocale, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, verifySignupToken };
26844
27673
  //# sourceMappingURL=index.js.map