@emdash-cms/admin 0.13.0 → 0.14.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.
Files changed (87) hide show
  1. package/dist/{LocaleDirectionProvider-rC0KuZjM.js → LocaleDirectionProvider-D32XwRVp.js} +17 -17
  2. package/dist/{LocaleDirectionProvider-rC0KuZjM.js.map → LocaleDirectionProvider-D32XwRVp.js.map} +1 -1
  3. package/dist/index.js +167 -16
  4. package/dist/index.js.map +1 -1
  5. package/dist/locales/ar/messages.mjs +1 -1
  6. package/dist/locales/de/messages.mjs +1 -1
  7. package/dist/locales/en/messages.mjs +1 -1
  8. package/dist/locales/es-419/messages.mjs +1 -1
  9. package/dist/locales/es-ES/messages.mjs +1 -1
  10. package/dist/locales/eu/messages.mjs +1 -1
  11. package/dist/locales/fa/messages.mjs +1 -1
  12. package/dist/locales/fr/messages.mjs +1 -1
  13. package/dist/locales/id/messages.mjs +1 -1
  14. package/dist/locales/index.js +1 -1
  15. package/dist/locales/ja/messages.mjs +1 -1
  16. package/dist/locales/ko/messages.mjs +1 -1
  17. package/dist/locales/pl/messages.mjs +1 -1
  18. package/dist/locales/pseudo/messages.mjs +1 -1
  19. package/dist/locales/pt-BR/messages.mjs +1 -1
  20. package/dist/locales/zh-CN/messages.mjs +1 -1
  21. package/dist/locales/zh-TW/messages.mjs +1 -1
  22. package/dist/messages-35lkat0c.js +6 -0
  23. package/dist/messages-35lkat0c.js.map +1 -0
  24. package/dist/messages-BSSaBU23.js +6 -0
  25. package/dist/messages-BSSaBU23.js.map +1 -0
  26. package/dist/messages-Boy6iMLn.js +6 -0
  27. package/dist/messages-Boy6iMLn.js.map +1 -0
  28. package/dist/messages-BwIV9n3i.js +6 -0
  29. package/dist/messages-BwIV9n3i.js.map +1 -0
  30. package/dist/messages-CGrAqIHP.js +6 -0
  31. package/dist/messages-CGrAqIHP.js.map +1 -0
  32. package/dist/messages-CgLmZ-Xe.js +6 -0
  33. package/dist/messages-CgLmZ-Xe.js.map +1 -0
  34. package/dist/messages-Ckz9OJjP.js +6 -0
  35. package/dist/messages-Ckz9OJjP.js.map +1 -0
  36. package/dist/messages-CrY9gw3p.js +6 -0
  37. package/dist/messages-CrY9gw3p.js.map +1 -0
  38. package/dist/messages-CtrOZaXa.js +6 -0
  39. package/dist/messages-CtrOZaXa.js.map +1 -0
  40. package/dist/messages-CxW3nwfb.js +6 -0
  41. package/dist/messages-CxW3nwfb.js.map +1 -0
  42. package/dist/messages-D7CyjcVC.js +6 -0
  43. package/dist/messages-D7CyjcVC.js.map +1 -0
  44. package/dist/messages-Dhy7Stmm.js +6 -0
  45. package/dist/messages-Dhy7Stmm.js.map +1 -0
  46. package/dist/messages-FyJi9vss.js +6 -0
  47. package/dist/messages-FyJi9vss.js.map +1 -0
  48. package/dist/messages-JMBMiRyI.js +6 -0
  49. package/dist/messages-JMBMiRyI.js.map +1 -0
  50. package/dist/messages-pPTbyAIO.js +6 -0
  51. package/dist/messages-pPTbyAIO.js.map +1 -0
  52. package/dist/messages-zl21KaEb.js +6 -0
  53. package/dist/messages-zl21KaEb.js.map +1 -0
  54. package/dist/styles.css +1 -1
  55. package/package.json +3 -3
  56. package/dist/messages-BB7Ta5GU.js +0 -6
  57. package/dist/messages-BB7Ta5GU.js.map +0 -1
  58. package/dist/messages-BMyaLK4h.js +0 -6
  59. package/dist/messages-BMyaLK4h.js.map +0 -1
  60. package/dist/messages-BRIi_T5q.js +0 -6
  61. package/dist/messages-BRIi_T5q.js.map +0 -1
  62. package/dist/messages-Be0FUVz_.js +0 -6
  63. package/dist/messages-Be0FUVz_.js.map +0 -1
  64. package/dist/messages-BvE8RBIJ.js +0 -6
  65. package/dist/messages-BvE8RBIJ.js.map +0 -1
  66. package/dist/messages-CBHhMxhB.js +0 -6
  67. package/dist/messages-CBHhMxhB.js.map +0 -1
  68. package/dist/messages-CQJpnG4Q.js +0 -6
  69. package/dist/messages-CQJpnG4Q.js.map +0 -1
  70. package/dist/messages-CZpDTTb_.js +0 -6
  71. package/dist/messages-CZpDTTb_.js.map +0 -1
  72. package/dist/messages-CemjGhIt.js +0 -6
  73. package/dist/messages-CemjGhIt.js.map +0 -1
  74. package/dist/messages-Czya1QvU.js +0 -6
  75. package/dist/messages-Czya1QvU.js.map +0 -1
  76. package/dist/messages-DR7ouL8H.js +0 -6
  77. package/dist/messages-DR7ouL8H.js.map +0 -1
  78. package/dist/messages-DyiZEfUj.js +0 -6
  79. package/dist/messages-DyiZEfUj.js.map +0 -1
  80. package/dist/messages-JMuy3Tpu.js +0 -6
  81. package/dist/messages-JMuy3Tpu.js.map +0 -1
  82. package/dist/messages-h9IORlKh.js +0 -6
  83. package/dist/messages-h9IORlKh.js.map +0 -1
  84. package/dist/messages-iu8SG58F.js +0 -6
  85. package/dist/messages-iu8SG58F.js.map +0 -1
  86. package/dist/messages-uD16X789.js +0 -6
  87. package/dist/messages-uD16X789.js.map +0 -1
@@ -193,22 +193,22 @@ function resolveLocale(request) {
193
193
  //#endregion
194
194
  //#region src/locales/loadMessages.ts
195
195
  const LOCALE_LOADERS = /* @__PURE__ */ Object.assign({
196
- "./ar/messages.mjs": () => import("./messages-JMuy3Tpu.js"),
197
- "./de/messages.mjs": () => import("./messages-CQJpnG4Q.js"),
198
- "./en/messages.mjs": () => import("./messages-CemjGhIt.js"),
199
- "./es-419/messages.mjs": () => import("./messages-CBHhMxhB.js"),
200
- "./es-ES/messages.mjs": () => import("./messages-h9IORlKh.js"),
201
- "./eu/messages.mjs": () => import("./messages-CZpDTTb_.js"),
202
- "./fa/messages.mjs": () => import("./messages-Czya1QvU.js"),
203
- "./fr/messages.mjs": () => import("./messages-uD16X789.js"),
204
- "./id/messages.mjs": () => import("./messages-BRIi_T5q.js"),
205
- "./ja/messages.mjs": () => import("./messages-DR7ouL8H.js"),
206
- "./ko/messages.mjs": () => import("./messages-BvE8RBIJ.js"),
207
- "./pl/messages.mjs": () => import("./messages-DyiZEfUj.js"),
208
- "./pseudo/messages.mjs": () => import("./messages-BMyaLK4h.js"),
209
- "./pt-BR/messages.mjs": () => import("./messages-Be0FUVz_.js"),
210
- "./zh-CN/messages.mjs": () => import("./messages-BB7Ta5GU.js"),
211
- "./zh-TW/messages.mjs": () => import("./messages-iu8SG58F.js")
196
+ "./ar/messages.mjs": () => import("./messages-35lkat0c.js"),
197
+ "./de/messages.mjs": () => import("./messages-zl21KaEb.js"),
198
+ "./en/messages.mjs": () => import("./messages-FyJi9vss.js"),
199
+ "./es-419/messages.mjs": () => import("./messages-D7CyjcVC.js"),
200
+ "./es-ES/messages.mjs": () => import("./messages-pPTbyAIO.js"),
201
+ "./eu/messages.mjs": () => import("./messages-CtrOZaXa.js"),
202
+ "./fa/messages.mjs": () => import("./messages-Ckz9OJjP.js"),
203
+ "./fr/messages.mjs": () => import("./messages-Boy6iMLn.js"),
204
+ "./id/messages.mjs": () => import("./messages-CGrAqIHP.js"),
205
+ "./ja/messages.mjs": () => import("./messages-CrY9gw3p.js"),
206
+ "./ko/messages.mjs": () => import("./messages-CxW3nwfb.js"),
207
+ "./pl/messages.mjs": () => import("./messages-BSSaBU23.js"),
208
+ "./pseudo/messages.mjs": () => import("./messages-Dhy7Stmm.js"),
209
+ "./pt-BR/messages.mjs": () => import("./messages-JMBMiRyI.js"),
210
+ "./zh-CN/messages.mjs": () => import("./messages-CgLmZ-Xe.js"),
211
+ "./zh-TW/messages.mjs": () => import("./messages-BwIV9n3i.js")
212
212
  });
213
213
  async function loadMessages(locale) {
214
214
  const key = `./${locale}/messages.mjs`;
@@ -275,4 +275,4 @@ function LocaleDirectionProvider({ children }) {
275
275
 
276
276
  //#endregion
277
277
  export { SUPPORTED_LOCALES as a, getLocaleLabel as c, DEFAULT_LOCALE as i, resolveLocale as l, useLocale as n, SUPPORTED_LOCALE_CODES as o, loadMessages as r, getLocaleDir as s, LocaleDirectionProvider as t };
278
- //# sourceMappingURL=LocaleDirectionProvider-rC0KuZjM.js.map
278
+ //# sourceMappingURL=LocaleDirectionProvider-D32XwRVp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LocaleDirectionProvider-rC0KuZjM.js","names":["React"],"sources":["../src/locales/locales.ts","../src/locales/config.ts","../src/locales/loadMessages.ts","../src/locales/useLocale.ts","../src/locales/LocaleDirectionProvider.tsx"],"sourcesContent":["/**\n * Canonical locale definitions -- the single source of truth.\n *\n * This file is intentionally free of Vite/Astro APIs (`import.meta.env` etc.)\n * so it can be imported from CLI tools (Lingui, Lunaria) running in plain Node.\n *\n * To add a new locale:\n * 1. Add an entry here (with `enabled: false`).\n * 2. Run `pnpm locale:extract` to generate the PO file.\n * 3. Translate the strings in the PO file.\n * 4. Set `enabled: true` once coverage is sufficient.\n *\n * Lingui and Lunaria use all locales (for extraction and tracking).\n * The admin runtime only exposes locales with `enabled: true`.\n */\n\nexport interface LocaleDefinition {\n\t/** BCP 47 locale code (e.g. \"en\", \"pt-BR\"). */\n\tcode: string;\n\t/** Human-readable label in the locale's own language. */\n\tlabel: string;\n\t/** Whether this locale is selectable in the admin UI. */\n\tenabled: boolean;\n\t/** Text direction for this locale. Defaults to \"ltr\" if not specified. */\n\tdir?: \"rtl\" | \"ltr\";\n}\n\n/**\n * All locales that have (or should have) a PO catalog.\n * First entry is the source/default locale.\n */\nexport const LOCALES: LocaleDefinition[] = [\n\t// Source locale first, then alphabetical by English name.\n\t{ code: \"en\", label: \"English\", enabled: true },\n\t{ code: \"ar\", label: \"العربية\", enabled: true, dir: \"rtl\" }, // Arabic\n\t{ code: \"eu\", label: \"Euskara\", enabled: true }, // Basque\n\t{ code: \"zh-CN\", label: \"简体中文\", enabled: true }, // Chinese (Simplified)\n\t{ code: \"zh-TW\", label: \"繁體中文\", enabled: true }, // Chinese (Traditional)\n\t{ code: \"fa\", label: \"فارسی\", enabled: true, dir: \"rtl\" }, // Farsi (also known as Persian)\n\t{ code: \"fr\", label: \"Français\", enabled: true }, // French\n\t{ code: \"de\", label: \"Deutsch\", enabled: true }, // German\n\t{ code: \"id\", label: \"Bahasa Indonesia\", enabled: true }, // Indonesian\n\t{ code: \"ja\", label: \"日本語\", enabled: true }, // Japanese\n\t{ code: \"ko\", label: \"한국어\", enabled: false }, // Korean\n\t{ code: \"pl\", label: \"Polski\", enabled: true }, // Polish\n\t{ code: \"pt-BR\", label: \"Português (Brasil)\", enabled: true }, // Portuguese (Brazil)\n\t{ code: \"es-419\", label: \"Español (Latinoamérica)\", enabled: true }, // Spanish (Latin America)\n\t{ code: \"es-ES\", label: \"Español (España)\", enabled: true }, // Spanish (Spain) - BCP 47\n\t// Pseudo-locale for i18n testing - never enabled in the admin UI by default.\n\t// Set EMDASH_PSEUDO_LOCALE=1 in .env to expose it in the locale switcher (dev only).\n\t{ code: \"pseudo\", label: \"Pseudo\", enabled: false },\n];\n\n/** The source locale (first entry). */\nexport const SOURCE_LOCALE = LOCALES[0]!;\n\n/** All locale codes (for Lingui extraction / Lunaria tracking). */\nexport const LOCALE_CODES = LOCALES.map((l) => l.code);\n\n/** Target locales -- everything except the source (for Lunaria). */\nexport const TARGET_LOCALES = LOCALES.slice(1);\n\n/** Locales enabled in the admin UI. */\nexport const ENABLED_LOCALES = LOCALES.filter((l) => l.enabled);\n","/**\n * Locale configuration for the admin UI runtime.\n *\n * Locale definitions are in `./locales.ts` -- the single source of truth\n * shared by this file, lingui.config.ts and lunaria.config.ts.\n */\n\nimport { ENABLED_LOCALES, LOCALES, SOURCE_LOCALE } from \"./locales.js\";\n\nexport type { LocaleDefinition as SupportedLocale } from \"./locales.js\";\n\nfunction isValidLocale(code: string): boolean {\n\ttry {\n\t\tconst locale = new Intl.Locale(code);\n\t\treturn locale.baseName !== \"\";\n\t} catch {\n\t\tif (import.meta.env.DEV) {\n\t\t\tthrow new Error(`Invalid locale code: \"${code}\"`);\n\t\t}\n\t\treturn false;\n\t}\n}\n\n// Injected by the EmDash Vite integration from process.env.EMDASH_PSEUDO_LOCALE.\n// Only true in dev when EMDASH_PSEUDO_LOCALE=1 is set.\ndeclare const __EMDASH_PSEUDO_LOCALE__: boolean;\n\n/**\n * The pseudo locale, injected into the supported list only when\n * EMDASH_PSEUDO_LOCALE=1 is set. Never available in production.\n */\nconst PSEUDO_LOCALE =\n\ttypeof __EMDASH_PSEUDO_LOCALE__ !== \"undefined\" && __EMDASH_PSEUDO_LOCALE__\n\t\t? LOCALES.find((l) => l.code === \"pseudo\")\n\t\t: undefined;\n\n/** Available locales at runtime, validated against BCP 47. */\nexport const SUPPORTED_LOCALES = [\n\t...ENABLED_LOCALES.filter((l) => isValidLocale(l.code)),\n\t...(PSEUDO_LOCALE ? [PSEUDO_LOCALE] : []),\n];\n\nexport const SUPPORTED_LOCALE_CODES = new Set(SUPPORTED_LOCALES.map((l) => l.code));\n\nexport const DEFAULT_LOCALE = SOURCE_LOCALE.code;\n\n/** Maps base language codes to supported locales (e.g. \"pt\" -> \"pt-BR\"). */\nconst BASE_LANGUAGE_MAP = new Map<string, string>();\n/** Maps script codes to supported locales (e.g. \"zh-Hant\" -> \"zh-TW\", \"zh-Hans\" -> \"zh-CN\"). */\nconst SCRIPT_LANGUAGE_MAP = new Map<string, string>();\n\nfor (const l of SUPPORTED_LOCALES) {\n\tconst base = l.code.split(\"-\")[0]!.toLowerCase();\n\t// First match wins -- if we have both \"pt\" and \"pt-BR\", exact wins via direct lookup.\n\tif (!BASE_LANGUAGE_MAP.has(base)) {\n\t\tBASE_LANGUAGE_MAP.set(base, l.code);\n\t}\n\n\tconst maximized = new Intl.Locale(l.code).maximize();\n\tif (maximized.script) {\n\t\tconst scriptKey = `${maximized.language}-${maximized.script}`.toLowerCase();\n\t\tif (!SCRIPT_LANGUAGE_MAP.has(scriptKey)) {\n\t\t\tSCRIPT_LANGUAGE_MAP.set(scriptKey, l.code);\n\t\t}\n\t}\n}\n\n/**\n * Find the best matching supported locale for a BCP 47 tag.\n * Canonicalizes via Intl.Locale so case differences (e.g. \"pt-br\" vs \"pt-BR\")\n * don't prevent matching. Supports script codes (zh-Hant -> zh-TW, zh-Hans -> zh-CN)\n * and falls back to base language (pt-PT -> pt-BR).\n */\nfunction matchLocale(tag: string): string | undefined {\n\tconst trimmed = tag.trim();\n\tif (!trimmed) return undefined;\n\tlet canonical: string;\n\ttry {\n\t\tcanonical = new Intl.Locale(trimmed).baseName;\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\t// Exact match (case-insensitive via Intl.Locale)\n\tif (SUPPORTED_LOCALE_CODES.has(canonical)) return canonical;\n\n\t// Try script-based matching (zh-Hant -> zh-TW, zh-Hans -> zh-CN)\n\tconst locale = new Intl.Locale(trimmed);\n\tif (locale.script) {\n\t\tconst scriptKey = `${locale.language}-${locale.script}`.toLowerCase();\n\t\tconst scriptMatch = SCRIPT_LANGUAGE_MAP.get(scriptKey);\n\t\tif (scriptMatch) return scriptMatch;\n\t}\n\n\t// Fallback to base language (pt-PT -> pt-BR)\n\tconst base = canonical.split(\"-\")[0]!.toLowerCase();\n\treturn BASE_LANGUAGE_MAP.get(base);\n}\n\nconst LOCALE_LABELS = new Map(SUPPORTED_LOCALES.map((l) => [l.code, l.label]));\n\n/** Get a display label for a locale code, falling back to uppercase code. */\nexport function getLocaleLabel(code: string): string {\n\treturn LOCALE_LABELS.get(code) ?? code.toUpperCase();\n}\n\nconst LOCALE_DIRS = new Map(SUPPORTED_LOCALES.map((l) => [l.code, l.dir]));\n\n/** Get the text direction for a locale code. Defaults to \"ltr\" if not specified. */\nexport function getLocaleDir(code: string): \"ltr\" | \"rtl\" {\n\treturn LOCALE_DIRS.get(code) ?? \"ltr\";\n}\n\nconst LOCALE_COOKIE_RE = /(?:^|;\\s*)emdash-locale=([^;]+)/;\n\n/**\n * Resolve the admin locale from a Request.\n * Priority: emdash-locale cookie -> Accept-Language -> DEFAULT_LOCALE.\n */\nexport function resolveLocale(request: Request): string {\n\tconst cookieHeader = request.headers.get(\"cookie\") ?? \"\";\n\tconst cookieMatch = cookieHeader.match(LOCALE_COOKIE_RE);\n\tconst cookieLocale = cookieMatch?.[1]?.trim() ?? \"\";\n\n\tif (SUPPORTED_LOCALE_CODES.has(cookieLocale)) return cookieLocale;\n\n\tconst acceptLang = request.headers.get(\"accept-language\") ?? \"\";\n\tfor (const entry of acceptLang.split(\",\")) {\n\t\tconst tag = entry.split(\";\")[0]!.trim();\n\t\tconst matched = matchLocale(tag);\n\t\tif (matched) return matched;\n\t}\n\n\treturn DEFAULT_LOCALE;\n}\n","import type { Messages } from \"@lingui/core\";\n\nimport { DEFAULT_LOCALE } from \"./config.js\";\n\nconst LOCALE_LOADERS = import.meta.glob<{ messages: Messages }>(\"./**/messages.mjs\");\n\nexport async function loadMessages(locale: string): Promise<Messages> {\n\tconst key = `./${locale}/messages.mjs`;\n\tconst fallbackKey = `./${DEFAULT_LOCALE}/messages.mjs`;\n\tconst loader = LOCALE_LOADERS[key] ?? LOCALE_LOADERS[fallbackKey];\n\tif (!loader) {\n\t\tthrow new Error(\n\t\t\t`No locale catalog found for \"${locale}\" or \"${DEFAULT_LOCALE}\". Run \\`pnpm locale:compile\\` to generate catalogs.`,\n\t\t);\n\t}\n\tconst { messages } = await loader();\n\treturn messages;\n}\n","import { useLingui } from \"@lingui/react\";\nimport * as React from \"react\";\n\nimport { SUPPORTED_LOCALE_CODES } from \"./config.js\";\nimport { loadMessages } from \"./loadMessages.js\";\n\nfunction setCookie(code: string) {\n\tconst secure = window.location.protocol === \"https:\" ? \"; Secure\" : \"\";\n\tdocument.cookie = `emdash-locale=${code}; Path=/_emdash; SameSite=Lax; Max-Age=31536000${secure}`;\n}\n\n/**\n * Get the current locale and a function to switch locales.\n * Loads the new catalog dynamically and sets a cookie for server-side persistence.\n */\nexport function useLocale() {\n\tconst { i18n } = useLingui();\n\tconst [locale, setLocaleState] = React.useState(i18n.locale);\n\n\tconst setLocale = React.useCallback(\n\t\t(code: string) => {\n\t\t\tif (code === i18n.locale || !SUPPORTED_LOCALE_CODES.has(code)) return;\n\t\t\tsetCookie(code);\n\t\t\tvoid loadMessages(code)\n\t\t\t\t.then((messages) => i18n.loadAndActivate({ locale: code, messages }))\n\t\t\t\t.catch(() => {\n\t\t\t\t\tsetCookie(i18n.locale);\n\t\t\t\t});\n\t\t},\n\t\t[i18n],\n\t);\n\n\t// Subscribe to i18n change events to trigger re-renders\n\tReact.useEffect(() => {\n\t\tconst unsubscribe = i18n.on(\"change\", () => {\n\t\t\tsetLocaleState(i18n.locale);\n\t\t});\n\t\treturn unsubscribe;\n\t}, [i18n]);\n\n\treturn { locale, setLocale };\n}\n","import { DirectionProvider } from \"@cloudflare/kumo/primitives\";\nimport * as React from \"react\";\n\nimport { getLocaleDir } from \"./config.js\";\nimport { useLocale } from \"./useLocale.js\";\n\ninterface LocaleDirectionProviderProps {\n\tchildren: React.ReactNode;\n}\n\n/**\n * Wraps the app with DirectionProvider and keeps it in sync with the current locale.\n * Automatically updates direction when locale changes via useLocale.\n */\nexport function LocaleDirectionProvider({ children }: LocaleDirectionProviderProps) {\n\tconst { locale } = useLocale();\n\tconst dir = React.useMemo(() => getLocaleDir(locale), [locale]);\n\n\t/** Sync the direction and lang attributes with the current locale. */\n\tReact.useEffect(() => {\n\t\tdocument.documentElement.setAttribute(\"lang\", locale);\n\t\tdocument.documentElement.setAttribute(\"dir\", dir);\n\t}, [dir, locale]);\n\n\treturn <DirectionProvider direction={dir}>{children}</DirectionProvider>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LocaleDirectionProvider-D32XwRVp.js","names":["React"],"sources":["../src/locales/locales.ts","../src/locales/config.ts","../src/locales/loadMessages.ts","../src/locales/useLocale.ts","../src/locales/LocaleDirectionProvider.tsx"],"sourcesContent":["/**\n * Canonical locale definitions -- the single source of truth.\n *\n * This file is intentionally free of Vite/Astro APIs (`import.meta.env` etc.)\n * so it can be imported from CLI tools (Lingui, Lunaria) running in plain Node.\n *\n * To add a new locale:\n * 1. Add an entry here (with `enabled: false`).\n * 2. Run `pnpm locale:extract` to generate the PO file.\n * 3. Translate the strings in the PO file.\n * 4. Set `enabled: true` once coverage is sufficient.\n *\n * Lingui and Lunaria use all locales (for extraction and tracking).\n * The admin runtime only exposes locales with `enabled: true`.\n */\n\nexport interface LocaleDefinition {\n\t/** BCP 47 locale code (e.g. \"en\", \"pt-BR\"). */\n\tcode: string;\n\t/** Human-readable label in the locale's own language. */\n\tlabel: string;\n\t/** Whether this locale is selectable in the admin UI. */\n\tenabled: boolean;\n\t/** Text direction for this locale. Defaults to \"ltr\" if not specified. */\n\tdir?: \"rtl\" | \"ltr\";\n}\n\n/**\n * All locales that have (or should have) a PO catalog.\n * First entry is the source/default locale.\n */\nexport const LOCALES: LocaleDefinition[] = [\n\t// Source locale first, then alphabetical by English name.\n\t{ code: \"en\", label: \"English\", enabled: true },\n\t{ code: \"ar\", label: \"العربية\", enabled: true, dir: \"rtl\" }, // Arabic\n\t{ code: \"eu\", label: \"Euskara\", enabled: true }, // Basque\n\t{ code: \"zh-CN\", label: \"简体中文\", enabled: true }, // Chinese (Simplified)\n\t{ code: \"zh-TW\", label: \"繁體中文\", enabled: true }, // Chinese (Traditional)\n\t{ code: \"fa\", label: \"فارسی\", enabled: true, dir: \"rtl\" }, // Farsi (also known as Persian)\n\t{ code: \"fr\", label: \"Français\", enabled: true }, // French\n\t{ code: \"de\", label: \"Deutsch\", enabled: true }, // German\n\t{ code: \"id\", label: \"Bahasa Indonesia\", enabled: true }, // Indonesian\n\t{ code: \"ja\", label: \"日本語\", enabled: true }, // Japanese\n\t{ code: \"ko\", label: \"한국어\", enabled: false }, // Korean\n\t{ code: \"pl\", label: \"Polski\", enabled: true }, // Polish\n\t{ code: \"pt-BR\", label: \"Português (Brasil)\", enabled: true }, // Portuguese (Brazil)\n\t{ code: \"es-419\", label: \"Español (Latinoamérica)\", enabled: true }, // Spanish (Latin America)\n\t{ code: \"es-ES\", label: \"Español (España)\", enabled: true }, // Spanish (Spain) - BCP 47\n\t// Pseudo-locale for i18n testing - never enabled in the admin UI by default.\n\t// Set EMDASH_PSEUDO_LOCALE=1 in .env to expose it in the locale switcher (dev only).\n\t{ code: \"pseudo\", label: \"Pseudo\", enabled: false },\n];\n\n/** The source locale (first entry). */\nexport const SOURCE_LOCALE = LOCALES[0]!;\n\n/** All locale codes (for Lingui extraction / Lunaria tracking). */\nexport const LOCALE_CODES = LOCALES.map((l) => l.code);\n\n/** Target locales -- everything except the source (for Lunaria). */\nexport const TARGET_LOCALES = LOCALES.slice(1);\n\n/** Locales enabled in the admin UI. */\nexport const ENABLED_LOCALES = LOCALES.filter((l) => l.enabled);\n","/**\n * Locale configuration for the admin UI runtime.\n *\n * Locale definitions are in `./locales.ts` -- the single source of truth\n * shared by this file, lingui.config.ts and lunaria.config.ts.\n */\n\nimport { ENABLED_LOCALES, LOCALES, SOURCE_LOCALE } from \"./locales.js\";\n\nexport type { LocaleDefinition as SupportedLocale } from \"./locales.js\";\n\nfunction isValidLocale(code: string): boolean {\n\ttry {\n\t\tconst locale = new Intl.Locale(code);\n\t\treturn locale.baseName !== \"\";\n\t} catch {\n\t\tif (import.meta.env.DEV) {\n\t\t\tthrow new Error(`Invalid locale code: \"${code}\"`);\n\t\t}\n\t\treturn false;\n\t}\n}\n\n// Injected by the EmDash Vite integration from process.env.EMDASH_PSEUDO_LOCALE.\n// Only true in dev when EMDASH_PSEUDO_LOCALE=1 is set.\ndeclare const __EMDASH_PSEUDO_LOCALE__: boolean;\n\n/**\n * The pseudo locale, injected into the supported list only when\n * EMDASH_PSEUDO_LOCALE=1 is set. Never available in production.\n */\nconst PSEUDO_LOCALE =\n\ttypeof __EMDASH_PSEUDO_LOCALE__ !== \"undefined\" && __EMDASH_PSEUDO_LOCALE__\n\t\t? LOCALES.find((l) => l.code === \"pseudo\")\n\t\t: undefined;\n\n/** Available locales at runtime, validated against BCP 47. */\nexport const SUPPORTED_LOCALES = [\n\t...ENABLED_LOCALES.filter((l) => isValidLocale(l.code)),\n\t...(PSEUDO_LOCALE ? [PSEUDO_LOCALE] : []),\n];\n\nexport const SUPPORTED_LOCALE_CODES = new Set(SUPPORTED_LOCALES.map((l) => l.code));\n\nexport const DEFAULT_LOCALE = SOURCE_LOCALE.code;\n\n/** Maps base language codes to supported locales (e.g. \"pt\" -> \"pt-BR\"). */\nconst BASE_LANGUAGE_MAP = new Map<string, string>();\n/** Maps script codes to supported locales (e.g. \"zh-Hant\" -> \"zh-TW\", \"zh-Hans\" -> \"zh-CN\"). */\nconst SCRIPT_LANGUAGE_MAP = new Map<string, string>();\n\nfor (const l of SUPPORTED_LOCALES) {\n\tconst base = l.code.split(\"-\")[0]!.toLowerCase();\n\t// First match wins -- if we have both \"pt\" and \"pt-BR\", exact wins via direct lookup.\n\tif (!BASE_LANGUAGE_MAP.has(base)) {\n\t\tBASE_LANGUAGE_MAP.set(base, l.code);\n\t}\n\n\tconst maximized = new Intl.Locale(l.code).maximize();\n\tif (maximized.script) {\n\t\tconst scriptKey = `${maximized.language}-${maximized.script}`.toLowerCase();\n\t\tif (!SCRIPT_LANGUAGE_MAP.has(scriptKey)) {\n\t\t\tSCRIPT_LANGUAGE_MAP.set(scriptKey, l.code);\n\t\t}\n\t}\n}\n\n/**\n * Find the best matching supported locale for a BCP 47 tag.\n * Canonicalizes via Intl.Locale so case differences (e.g. \"pt-br\" vs \"pt-BR\")\n * don't prevent matching. Supports script codes (zh-Hant -> zh-TW, zh-Hans -> zh-CN)\n * and falls back to base language (pt-PT -> pt-BR).\n */\nfunction matchLocale(tag: string): string | undefined {\n\tconst trimmed = tag.trim();\n\tif (!trimmed) return undefined;\n\tlet canonical: string;\n\ttry {\n\t\tcanonical = new Intl.Locale(trimmed).baseName;\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\t// Exact match (case-insensitive via Intl.Locale)\n\tif (SUPPORTED_LOCALE_CODES.has(canonical)) return canonical;\n\n\t// Try script-based matching (zh-Hant -> zh-TW, zh-Hans -> zh-CN)\n\tconst locale = new Intl.Locale(trimmed);\n\tif (locale.script) {\n\t\tconst scriptKey = `${locale.language}-${locale.script}`.toLowerCase();\n\t\tconst scriptMatch = SCRIPT_LANGUAGE_MAP.get(scriptKey);\n\t\tif (scriptMatch) return scriptMatch;\n\t}\n\n\t// Fallback to base language (pt-PT -> pt-BR)\n\tconst base = canonical.split(\"-\")[0]!.toLowerCase();\n\treturn BASE_LANGUAGE_MAP.get(base);\n}\n\nconst LOCALE_LABELS = new Map(SUPPORTED_LOCALES.map((l) => [l.code, l.label]));\n\n/** Get a display label for a locale code, falling back to uppercase code. */\nexport function getLocaleLabel(code: string): string {\n\treturn LOCALE_LABELS.get(code) ?? code.toUpperCase();\n}\n\nconst LOCALE_DIRS = new Map(SUPPORTED_LOCALES.map((l) => [l.code, l.dir]));\n\n/** Get the text direction for a locale code. Defaults to \"ltr\" if not specified. */\nexport function getLocaleDir(code: string): \"ltr\" | \"rtl\" {\n\treturn LOCALE_DIRS.get(code) ?? \"ltr\";\n}\n\nconst LOCALE_COOKIE_RE = /(?:^|;\\s*)emdash-locale=([^;]+)/;\n\n/**\n * Resolve the admin locale from a Request.\n * Priority: emdash-locale cookie -> Accept-Language -> DEFAULT_LOCALE.\n */\nexport function resolveLocale(request: Request): string {\n\tconst cookieHeader = request.headers.get(\"cookie\") ?? \"\";\n\tconst cookieMatch = cookieHeader.match(LOCALE_COOKIE_RE);\n\tconst cookieLocale = cookieMatch?.[1]?.trim() ?? \"\";\n\n\tif (SUPPORTED_LOCALE_CODES.has(cookieLocale)) return cookieLocale;\n\n\tconst acceptLang = request.headers.get(\"accept-language\") ?? \"\";\n\tfor (const entry of acceptLang.split(\",\")) {\n\t\tconst tag = entry.split(\";\")[0]!.trim();\n\t\tconst matched = matchLocale(tag);\n\t\tif (matched) return matched;\n\t}\n\n\treturn DEFAULT_LOCALE;\n}\n","import type { Messages } from \"@lingui/core\";\n\nimport { DEFAULT_LOCALE } from \"./config.js\";\n\nconst LOCALE_LOADERS = import.meta.glob<{ messages: Messages }>(\"./**/messages.mjs\");\n\nexport async function loadMessages(locale: string): Promise<Messages> {\n\tconst key = `./${locale}/messages.mjs`;\n\tconst fallbackKey = `./${DEFAULT_LOCALE}/messages.mjs`;\n\tconst loader = LOCALE_LOADERS[key] ?? LOCALE_LOADERS[fallbackKey];\n\tif (!loader) {\n\t\tthrow new Error(\n\t\t\t`No locale catalog found for \"${locale}\" or \"${DEFAULT_LOCALE}\". Run \\`pnpm locale:compile\\` to generate catalogs.`,\n\t\t);\n\t}\n\tconst { messages } = await loader();\n\treturn messages;\n}\n","import { useLingui } from \"@lingui/react\";\nimport * as React from \"react\";\n\nimport { SUPPORTED_LOCALE_CODES } from \"./config.js\";\nimport { loadMessages } from \"./loadMessages.js\";\n\nfunction setCookie(code: string) {\n\tconst secure = window.location.protocol === \"https:\" ? \"; Secure\" : \"\";\n\tdocument.cookie = `emdash-locale=${code}; Path=/_emdash; SameSite=Lax; Max-Age=31536000${secure}`;\n}\n\n/**\n * Get the current locale and a function to switch locales.\n * Loads the new catalog dynamically and sets a cookie for server-side persistence.\n */\nexport function useLocale() {\n\tconst { i18n } = useLingui();\n\tconst [locale, setLocaleState] = React.useState(i18n.locale);\n\n\tconst setLocale = React.useCallback(\n\t\t(code: string) => {\n\t\t\tif (code === i18n.locale || !SUPPORTED_LOCALE_CODES.has(code)) return;\n\t\t\tsetCookie(code);\n\t\t\tvoid loadMessages(code)\n\t\t\t\t.then((messages) => i18n.loadAndActivate({ locale: code, messages }))\n\t\t\t\t.catch(() => {\n\t\t\t\t\tsetCookie(i18n.locale);\n\t\t\t\t});\n\t\t},\n\t\t[i18n],\n\t);\n\n\t// Subscribe to i18n change events to trigger re-renders\n\tReact.useEffect(() => {\n\t\tconst unsubscribe = i18n.on(\"change\", () => {\n\t\t\tsetLocaleState(i18n.locale);\n\t\t});\n\t\treturn unsubscribe;\n\t}, [i18n]);\n\n\treturn { locale, setLocale };\n}\n","import { DirectionProvider } from \"@cloudflare/kumo/primitives\";\nimport * as React from \"react\";\n\nimport { getLocaleDir } from \"./config.js\";\nimport { useLocale } from \"./useLocale.js\";\n\ninterface LocaleDirectionProviderProps {\n\tchildren: React.ReactNode;\n}\n\n/**\n * Wraps the app with DirectionProvider and keeps it in sync with the current locale.\n * Automatically updates direction when locale changes via useLocale.\n */\nexport function LocaleDirectionProvider({ children }: LocaleDirectionProviderProps) {\n\tconst { locale } = useLocale();\n\tconst dir = React.useMemo(() => getLocaleDir(locale), [locale]);\n\n\t/** Sync the direction and lang attributes with the current locale. */\n\tReact.useEffect(() => {\n\t\tdocument.documentElement.setAttribute(\"lang\", locale);\n\t\tdocument.documentElement.setAttribute(\"dir\", dir);\n\t}, [dir, locale]);\n\n\treturn <DirectionProvider direction={dir}>{children}</DirectionProvider>;\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { c as fetchAuthMode, d as throwResponseError, i as fetchPlugins, l as fetchManifest, n as enablePlugin, o as API_BASE, r as fetchPlugin, s as apiFetch, t as disablePlugin, u as parseApiResponse } from "./plugins-qrjvPK4g.js";
2
- import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-rC0KuZjM.js";
2
+ import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-D32XwRVp.js";
3
3
  import "./locales/index.js";
4
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
5
  import { i18n } from "@lingui/core";
@@ -21963,32 +21963,32 @@ async function getDiscoveryClient(config) {
21963
21963
  });
21964
21964
  const wrapped = {
21965
21965
  async searchPackages(opts) {
21966
- return await discovery.searchPackages({
21966
+ return discovery.searchPackages({
21967
21967
  q: opts.q,
21968
21968
  cursor: opts.cursor,
21969
21969
  limit: opts.limit
21970
21970
  });
21971
21971
  },
21972
21972
  async resolvePackage(handle, slug) {
21973
- return await discovery.resolvePackage({
21973
+ return discovery.resolvePackage({
21974
21974
  handle,
21975
21975
  slug
21976
21976
  });
21977
21977
  },
21978
21978
  async getPackage(did, slug) {
21979
- return await discovery.getPackage({
21979
+ return discovery.getPackage({
21980
21980
  did,
21981
21981
  slug
21982
21982
  });
21983
21983
  },
21984
21984
  async getLatestRelease(did, slug) {
21985
- return await discovery.getLatestRelease({
21985
+ return discovery.getLatestRelease({
21986
21986
  did,
21987
21987
  package: slug
21988
21988
  });
21989
21989
  },
21990
21990
  async listReleases(did, slug, cursor) {
21991
- return await discovery.listReleases({
21991
+ return discovery.listReleases({
21992
21992
  did,
21993
21993
  package: slug,
21994
21994
  cursor
@@ -22392,7 +22392,9 @@ function RegistryBrowse({ config, installedRegistryUris = /* @__PURE__ */ new Se
22392
22392
  function RegistryPackageCard({ pkg, installed }) {
22393
22393
  const { _: _t2 } = useLingui();
22394
22394
  const linkSegment = usePublisherHandle(pkg.did, pkg.handle).handle ?? pkg.did;
22395
- const profile = pkg.profile;
22395
+ const name = pkg.profile?.name;
22396
+ const description = pkg.profile?.description;
22397
+ const license = pkg.profile?.license;
22396
22398
  const verified = (pkg.labels ?? []).some((l) => l.val === "verified");
22397
22399
  return /* @__PURE__ */ jsx(Link$1, {
22398
22400
  to: "/plugins/marketplace/$pluginId",
@@ -22410,7 +22412,7 @@ function RegistryPackageCard({ pkg, installed }) {
22410
22412
  className: "flex items-center gap-2",
22411
22413
  children: [/* @__PURE__ */ jsx("h2", {
22412
22414
  className: "truncate font-semibold",
22413
- children: profile.name ?? pkg.slug
22415
+ children: name ?? pkg.slug
22414
22416
  }), verified ? /* @__PURE__ */ jsx(ShieldCheck, {
22415
22417
  className: "h-4 w-4 shrink-0 text-kumo-brand",
22416
22418
  "aria-label": _t2({
@@ -22424,9 +22426,13 @@ function RegistryPackageCard({ pkg, installed }) {
22424
22426
  aggregatorHandle: pkg.handle,
22425
22427
  variant: "card"
22426
22428
  }),
22427
- profile.description ? /* @__PURE__ */ jsx("p", {
22429
+ description ? /* @__PURE__ */ jsx("p", {
22428
22430
  className: "mt-2 line-clamp-2 text-sm text-kumo-default",
22429
- children: profile.description
22431
+ children: description
22432
+ }) : null,
22433
+ license ? /* @__PURE__ */ jsx("p", {
22434
+ className: "mt-2 text-xs text-kumo-subtle",
22435
+ children: license
22430
22436
  }) : null,
22431
22437
  installed ? /* @__PURE__ */ jsx("div", {
22432
22438
  className: "mt-3",
@@ -22499,9 +22505,27 @@ function RegistryPluginDetail({ pluginId, config }) {
22499
22505
  queryFn: () => getLatestRegistryRelease(config, pkg.did, slug),
22500
22506
  enabled: Boolean(pkg?.did && slug)
22501
22507
  });
22502
- const ext = (release?.release)?.extensions?.["com.emdashcms.experimental.package.releaseExtension"];
22508
+ const ext = (release?.release?.extensions)?.["com.emdashcms.experimental.package.releaseExtension"];
22503
22509
  const capabilities = Array.isArray(ext?.capabilities) ? canonicalCapabilitiesForDriftCheck(ext?.capabilities) : canonicalCapabilitiesForDriftCheck(declaredAccessToCapabilityList(ext?.declaredAccess));
22504
- const profile = pkg?.profile;
22510
+ const pkgProfile = pkg?.profile ?? null;
22511
+ const displayName = pkgProfile?.name;
22512
+ const description = pkgProfile?.description;
22513
+ const licenseText = pkgProfile?.license;
22514
+ const keywordList = pkgProfile?.keywords ?? [];
22515
+ const authorList = (pkgProfile?.authors ?? []).map((a) => ({
22516
+ name: a.name,
22517
+ url: safeExternalHref(a.url),
22518
+ email: safeEmail(a.email)
22519
+ }));
22520
+ const securityList = (pkgProfile?.security ?? []).flatMap((c) => {
22521
+ const url = safeExternalHref(c.url);
22522
+ const email = safeEmail(c.email);
22523
+ return url || email ? [{
22524
+ url,
22525
+ email
22526
+ }] : [];
22527
+ });
22528
+ const repoHref = safeExternalHref(release?.release?.repo);
22505
22529
  const verified = (pkg?.labels ?? []).some((l) => l.val === "verified");
22506
22530
  const policyOk = release && pkg ? releasePassesPolicy(release, {
22507
22531
  did: pkg.did,
@@ -22582,7 +22606,7 @@ function RegistryPluginDetail({ pluginId, config }) {
22582
22606
  className: "flex items-center gap-2",
22583
22607
  children: [/* @__PURE__ */ jsx("h1", {
22584
22608
  className: "truncate text-3xl font-bold",
22585
- children: profile?.name ?? slug
22609
+ children: displayName ?? slug
22586
22610
  }), verified ? /* @__PURE__ */ jsx(ShieldCheck, {
22587
22611
  className: "h-5 w-5 shrink-0 text-kumo-brand",
22588
22612
  "aria-label": _t({
@@ -22677,10 +22701,82 @@ function RegistryPluginDetail({ pluginId, config }) {
22677
22701
  })
22678
22702
  })] })]
22679
22703
  }) : null,
22680
- profile?.description ? /* @__PURE__ */ jsx("p", {
22704
+ description ? /* @__PURE__ */ jsx("p", {
22681
22705
  className: "text-base text-kumo-default",
22682
- children: profile.description
22706
+ children: description
22707
+ }) : null,
22708
+ licenseText || repoHref || keywordList.length > 0 ? /* @__PURE__ */ jsxs("section", {
22709
+ className: "flex flex-wrap items-center gap-2",
22710
+ children: [
22711
+ licenseText ? /* @__PURE__ */ jsx(LicenseBadge, { license: licenseText }) : null,
22712
+ keywordList.map((k) => /* @__PURE__ */ jsx(Badge, { children: k }, k)),
22713
+ repoHref ? /* @__PURE__ */ jsx(LinkButton, {
22714
+ href: repoHref,
22715
+ external: true,
22716
+ variant: "secondary",
22717
+ children: _t({
22718
+ id: "n/I+da",
22719
+ message: "View source"
22720
+ })
22721
+ }) : null
22722
+ ]
22683
22723
  }) : null,
22724
+ authorList.length > 0 ? /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h2", {
22725
+ className: "text-sm font-semibold text-kumo-subtle",
22726
+ children: _t({
22727
+ id: "dpqPBg",
22728
+ message: "Authors"
22729
+ })
22730
+ }), /* @__PURE__ */ jsx("ul", {
22731
+ className: "mt-2 space-y-1",
22732
+ children: authorList.map((a, i) => /* @__PURE__ */ jsxs("li", {
22733
+ className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-sm",
22734
+ children: [
22735
+ /* @__PURE__ */ jsx("span", {
22736
+ className: "font-medium text-kumo-default",
22737
+ children: a.name
22738
+ }),
22739
+ a.url ? /* @__PURE__ */ jsx("a", {
22740
+ href: a.url,
22741
+ target: "_blank",
22742
+ rel: "noreferrer",
22743
+ className: "text-kumo-brand hover:underline",
22744
+ children: _t({
22745
+ id: "On0aF2",
22746
+ message: "Website"
22747
+ })
22748
+ }) : null,
22749
+ a.email ? /* @__PURE__ */ jsx("a", {
22750
+ href: `mailto:${a.email}`,
22751
+ className: "text-kumo-brand hover:underline",
22752
+ children: a.email
22753
+ }) : null
22754
+ ]
22755
+ }, `${a.name}-${i}`))
22756
+ })] }) : null,
22757
+ securityList.length > 0 ? /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h2", {
22758
+ className: "text-sm font-semibold text-kumo-subtle",
22759
+ children: _t({
22760
+ id: "FDZXmz",
22761
+ message: "Security contacts"
22762
+ })
22763
+ }), /* @__PURE__ */ jsx("ul", {
22764
+ className: "mt-2 space-y-1",
22765
+ children: securityList.map((c, i) => /* @__PURE__ */ jsxs("li", {
22766
+ className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-sm",
22767
+ children: [c.email ? /* @__PURE__ */ jsx("a", {
22768
+ href: `mailto:${c.email}`,
22769
+ className: "text-kumo-brand hover:underline",
22770
+ children: c.email
22771
+ }) : null, c.url ? /* @__PURE__ */ jsx("a", {
22772
+ href: c.url,
22773
+ target: "_blank",
22774
+ rel: "noreferrer",
22775
+ className: "text-kumo-brand hover:underline",
22776
+ children: c.url
22777
+ }) : null]
22778
+ }, `${c.email ?? c.url ?? "contact"}-${i}`))
22779
+ })] }) : null,
22684
22780
  capabilities.length > 0 ? /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h2", {
22685
22781
  className: "text-sm font-semibold text-kumo-subtle",
22686
22782
  children: _t({
@@ -22693,7 +22789,7 @@ function RegistryPluginDetail({ pluginId, config }) {
22693
22789
  })] }) : null,
22694
22790
  showConsent && release ? /* @__PURE__ */ jsx(CapabilityConsentDialog, {
22695
22791
  mode: "install",
22696
- pluginName: profile?.name ?? slug,
22792
+ pluginName: displayName ?? slug,
22697
22793
  capabilities,
22698
22794
  isPending: installMutation.isPending,
22699
22795
  error: getMutationError(installMutation.error),
@@ -22744,6 +22840,61 @@ function formatDate(iso) {
22744
22840
  return iso;
22745
22841
  }
22746
22842
  }
22843
+ /**
22844
+ * SPDX page URL for a license, or `null` when the value isn't a single
22845
+ * SPDX identifier (compound expressions like "MIT OR Apache-2.0" and the
22846
+ * literal "proprietary" have no canonical spdx.org page).
22847
+ */
22848
+ /**
22849
+ * Validate an untrusted aggregator-supplied URL for use in an `href`.
22850
+ * Returns the normalised URL only when it is an absolute `http(s)` URL;
22851
+ * everything else (relative, `javascript:`, `data:`, garbage, non-string)
22852
+ * returns `null`. The profile/release records are pass-throughs from a
22853
+ * remote service, so an unsanitised `href` is stored XSS in the
22854
+ * authenticated admin origin.
22855
+ */
22856
+ function safeExternalHref(value) {
22857
+ if (typeof value !== "string" || value.length === 0) return null;
22858
+ let parsed;
22859
+ try {
22860
+ parsed = new URL(value);
22861
+ } catch {
22862
+ return null;
22863
+ }
22864
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
22865
+ return parsed.href;
22866
+ }
22867
+ const EMAIL_RE = /^[^\s@<>()[\]\\,;:"?&=%/]+@[^\s@<>()[\]\\,;:"?&=%/]+\.[^\s@<>()[\]\\,;:"?&=%/]+$/;
22868
+ function safeEmail(value) {
22869
+ if (typeof value !== "string") return null;
22870
+ const email = value.trim();
22871
+ if (email.length === 0 || email.length > 320) return null;
22872
+ return EMAIL_RE.test(email) ? email : null;
22873
+ }
22874
+ const SPDX_SINGLE_ID_RE = /^[A-Za-z0-9.+-]+$/;
22875
+ function spdxLicenseHref(license) {
22876
+ const id = license.trim();
22877
+ if (!SPDX_SINGLE_ID_RE.test(id)) return null;
22878
+ if (id.toLowerCase() === "proprietary") return null;
22879
+ return `https://spdx.org/licenses/${id}.html`;
22880
+ }
22881
+ function LicenseBadge({ license }) {
22882
+ const { _: _t3 } = useLingui();
22883
+ const href = spdxLicenseHref(license);
22884
+ if (!href) return /* @__PURE__ */ jsx(Badge, { children: license });
22885
+ return /* @__PURE__ */ jsx("a", {
22886
+ href,
22887
+ target: "_blank",
22888
+ rel: "noreferrer",
22889
+ "aria-label": _t3({
22890
+ id: "4Q1BOX",
22891
+ message: "View the {license} license on spdx.org",
22892
+ values: { license }
22893
+ }),
22894
+ className: "hover:opacity-80",
22895
+ children: /* @__PURE__ */ jsx(Badge, { children: license })
22896
+ });
22897
+ }
22747
22898
  function formatHoldback(seconds) {
22748
22899
  if (seconds <= 0) return "0s";
22749
22900
  if (seconds < 3600) return `${Math.round(seconds / 60)} min`;