nuxt-media-library 0.0.59 → 0.0.60

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/README.md CHANGED
@@ -316,6 +316,42 @@ name, extension, MIME type, kind, size, duration, width, height and pages.
316
316
  has none yet.
317
317
  - `useMLMediaMeta(ids, getAsset)` gives the same map for your own lists.
318
318
 
319
+ ### Error messages, in the editor's language
320
+
321
+ When a backend refuses a request it answers `{ detail, reason }`: `reason`
322
+ is a stable code (py_client `UploadRejected`: `too_large`, `mime_type`,
323
+ `context_mismatch`, …), `detail` English text. The widgets show, first that
324
+ applies:
325
+
326
+ 1. the text for a known `reason`, in the editor's language;
327
+ 2. `detail` (a 422's first `msg`);
328
+ 3. the backend's inner `message`, then the status text;
329
+ 4. the place's own fallback ("Upload failed", "Could not add the file", …).
330
+
331
+ The body is read whether the module's own proxy streamed it (`err.data`) or
332
+ an app proxy rethrew it with h3 `createError({ data })` (`err.data.data`).
333
+ The raw "[POST] url: 400" text is never shown.
334
+
335
+ The language is the app's `@nuxtjs/i18n` locale, else `mediaLibrary.locale`;
336
+ its language part picks the texts (`ar-SA` → `ar`). Built in: `en` and `ar`.
337
+ An app can change any text, or add a language:
338
+
339
+ ```ts
340
+ mediaLibrary: {
341
+ locale: 'ar', // without @nuxtjs/i18n
342
+ errorTexts: {
343
+ ar: {
344
+ reasons: { too_large: 'حجم هذا الملف أكبر من المسموح.' },
345
+ fallbacks: { upload: 'فشل رفع الملف' },
346
+ },
347
+ },
348
+ },
349
+ ```
350
+
351
+ A reason with no text in the editor's language uses the English one, then
352
+ `detail`. `mediaErrorMessage(err, fallback, { locale, texts })` and
353
+ `mlErrorMessage(err, fallback)` are exported for your own pages.
354
+
319
355
  ### Vue installation (non-Nuxt)
320
356
 
321
357
  ```bash
package/dist/module.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
  import { ServiceUploadBackend } from '../dist/runtime/utils/uploader/serviceBackends.js';
3
3
  import { MediaBackends } from '../dist/runtime/utils/backends.js';
4
+ import { MediaErrorTextsByLocale } from '../dist/runtime/utils/errorMessage.js';
4
5
  export * from '../dist/runtime/types/resourcePolicy.js';
5
6
  export * from '../dist/runtime/types/uploader.js';
6
7
  export * from '../dist/runtime/types/preview.js';
@@ -72,6 +73,17 @@ interface ModuleOptions {
72
73
  * its routes.
73
74
  */
74
75
  backends?: MediaBackends;
76
+ /**
77
+ * Language of the widgets' error texts when the app has no i18n locale
78
+ * (with @nuxtjs/i18n its active locale wins). Built in: `en`, `ar`.
79
+ */
80
+ locale?: string;
81
+ /**
82
+ * The app's error texts over the built-in ones, by language:
83
+ * `{ ar: { reasons: { too_large: '…' }, fallbacks: { upload: '…' } } }`.
84
+ * `reasons` are backend reason codes (py_client `UploadRejected`).
85
+ */
86
+ errorTexts?: MediaErrorTextsByLocale;
75
87
  }
76
88
  declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
77
89
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
package/dist/module.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
  import { ServiceUploadBackend } from '../dist/runtime/utils/uploader/serviceBackends.js';
3
3
  import { MediaBackends } from '../dist/runtime/utils/backends.js';
4
+ import { MediaErrorTextsByLocale } from '../dist/runtime/utils/errorMessage.js';
4
5
  export * from '../dist/runtime/types/resourcePolicy.js';
5
6
  export * from '../dist/runtime/types/uploader.js';
6
7
  export * from '../dist/runtime/types/preview.js';
@@ -72,6 +73,17 @@ interface ModuleOptions {
72
73
  * its routes.
73
74
  */
74
75
  backends?: MediaBackends;
76
+ /**
77
+ * Language of the widgets' error texts when the app has no i18n locale
78
+ * (with @nuxtjs/i18n its active locale wins). Built in: `en`, `ar`.
79
+ */
80
+ locale?: string;
81
+ /**
82
+ * The app's error texts over the built-in ones, by language:
83
+ * `{ ar: { reasons: { too_large: '…' }, fallbacks: { upload: '…' } } }`.
84
+ * `reasons` are backend reason codes (py_client `UploadRejected`).
85
+ */
86
+ errorTexts?: MediaErrorTextsByLocale;
75
87
  }
76
88
  declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
77
89
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "media-library",
3
3
  "configKey": "mediaLibrary",
4
- "version": "0.0.59",
4
+ "version": "0.0.60",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -177,6 +177,9 @@ const module$1 = defineNuxtModule({
177
177
  // The backend registry (NUXT_PUBLIC_MEDIA_LIBRARY_BACKENDS): names widgets pass as `backend`.
178
178
  backends,
179
179
  // Older form, kept for code that reads it: each backend's upload routes.
180
+ // NUXT_PUBLIC_MEDIA_LIBRARY_LOCALE; the app's i18n locale wins at runtime.
181
+ locale: existingPublic.locale || options.locale || "",
182
+ errorTexts: { ...options.errorTexts, ...existingPublic.errorTexts },
180
183
  serviceUploadBackends: Object.fromEntries(
181
184
  Object.entries(backends).flatMap(([name, b]) => b.upload ? [[name, b.upload]] : [])
182
185
  )
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
  import { computed, ref, watch } from "vue";
3
3
  import { useMLResourcePolicy, Visibility } from "../../index";
4
+ import { mlErrorMessage } from "../../utils/errorMessage";
4
5
  const props = defineProps({
5
6
  asset: { type: Object, required: true },
6
7
  manager: { type: Object, required: false }
@@ -71,7 +72,7 @@ async function changeVisibility(next) {
71
72
  try {
72
73
  await props.manager.updateAsset(props.asset.id, { visibility: next });
73
74
  } catch (err) {
74
- visibilityError.value = err instanceof Error ? err.message : "Failed to update visibility";
75
+ visibilityError.value = mlErrorMessage(err, "updateVisibility");
75
76
  } finally {
76
77
  visibilityUpdating.value = false;
77
78
  }
@@ -138,7 +139,7 @@ async function loadPolicy() {
138
139
  try {
139
140
  policy.value = await policyApi.get(id);
140
141
  } catch (err) {
141
- policyError.value = err instanceof Error ? err.message : "Failed to load policy";
142
+ policyError.value = mlErrorMessage(err, "loadPolicy");
142
143
  policy.value = null;
143
144
  } finally {
144
145
  policyLoading.value = false;
@@ -16,10 +16,10 @@ type __VLS_Props = {
16
16
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
17
17
  progress: number | null;
18
18
  compact: boolean;
19
+ detail: string;
19
20
  label: string;
20
21
  onCancel: () => void;
21
22
  onRetry: () => void;
22
- detail: string;
23
23
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
24
  declare const _default: typeof __VLS_export;
25
25
  export default _default;
@@ -16,10 +16,10 @@ type __VLS_Props = {
16
16
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
17
17
  progress: number | null;
18
18
  compact: boolean;
19
+ detail: string;
19
20
  label: string;
20
21
  onCancel: () => void;
21
22
  onRetry: () => void;
22
- detail: string;
23
23
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
24
  declare const _default: typeof __VLS_export;
25
25
  export default _default;
@@ -7,6 +7,7 @@ import { formatBytes } from "../../utils/assetFormat";
7
7
  import { isBackgroundStep, stepLabel } from "../../utils/processingSteps";
8
8
  import UploadProgress from "./UploadProgress.vue";
9
9
  import UploadSteps from "./UploadSteps.vue";
10
+ import { mlErrorMessage } from "../../utils/errorMessage";
10
11
  const props = defineProps({
11
12
  upload: { type: Object, required: true },
12
13
  expanded: { type: Boolean, required: false }
@@ -94,8 +95,7 @@ async function retryStep() {
94
95
  try {
95
96
  await queue.retryStep(u.value);
96
97
  } catch (err) {
97
- const message = err?.data?.detail ?? err?.data?.message ?? err?.statusMessage ?? "Could not retry this step";
98
- toast.add({ title: "Retry failed", description: String(message), color: "error" });
98
+ toast.add({ title: "Retry failed", description: mlErrorMessage(err, "retryStep"), color: "error" });
99
99
  } finally {
100
100
  retrying.value = false;
101
101
  }
@@ -5,6 +5,7 @@ import { useMLProcessingPipeline } from "../../composables/useMLProcessingPipeli
5
5
  import { stepLabel, usableAfterSteps } from "../../utils/processingSteps";
6
6
  import ConfigureStepModal from "../MLProcessing/Forms/ConfigureStepModal.vue";
7
7
  import UploadStep from "./UploadStep.vue";
8
+ import { mlErrorMessage } from "../../utils/errorMessage";
8
9
  const props = defineProps({
9
10
  assetId: { type: String, required: true },
10
11
  kind: { type: [String, null], required: false }
@@ -34,8 +35,7 @@ async function onRetry(taskId) {
34
35
  try {
35
36
  await retryTask(taskId);
36
37
  } catch (err) {
37
- const message = err?.data?.detail ?? err?.data?.message ?? err?.statusMessage ?? "Could not retry this step";
38
- toast.add({ title: "Retry failed", description: String(message), color: "error" });
38
+ toast.add({ title: "Retry failed", description: mlErrorMessage(err, "retryStep"), color: "error" });
39
39
  }
40
40
  }
41
41
  async function onConfigure(taskId) {
@@ -3,6 +3,7 @@ import { computed, watch } from "vue";
3
3
  import { useOverlay, useToast } from "#imports";
4
4
  import { useMLProcessingPipeline } from "../../composables/useMLProcessingPipeline";
5
5
  import ConfigureStepModal from "./Forms/ConfigureStepModal.vue";
6
+ import { mlErrorMessage } from "../../utils/errorMessage";
6
7
  const props = defineProps({
7
8
  assetId: { type: String, required: true },
8
9
  downloadUrl: { type: [String, null], required: false },
@@ -42,8 +43,7 @@ async function onRetry(taskId) {
42
43
  try {
43
44
  await retryTask(taskId);
44
45
  } catch (err) {
45
- const message = err?.data?.detail ?? err?.data?.message ?? err?.statusMessage ?? "Could not retry task";
46
- toast.add({ title: "Retry failed", description: String(message), color: "error" });
46
+ toast.add({ title: "Retry failed", description: mlErrorMessage(err, "retryTask"), color: "error" });
47
47
  }
48
48
  }
49
49
  async function openConfigure(taskId) {
@@ -1,5 +1,6 @@
1
1
  import { ref, watch } from "vue";
2
2
  import { $fetch } from "ofetch";
3
+ import { mlErrorMessage } from "../utils/errorMessage.js";
3
4
  export function useMLAssetLibrary(options = {}) {
4
5
  const items = ref([]);
5
6
  const total = ref(0);
@@ -36,7 +37,7 @@ export function useMLAssetLibrary(options = {}) {
36
37
  total.value = result.total ?? 0;
37
38
  }
38
39
  } catch (err) {
39
- error.value = err?.message ?? "Failed to load assets";
40
+ error.value = mlErrorMessage(err, "loadAssets");
40
41
  } finally {
41
42
  loading.value = false;
42
43
  }
@@ -1,6 +1,7 @@
1
1
  import { computed, onUnmounted, ref, shallowReactive, shallowRef, watch, toValue } from "vue";
2
2
  import { $fetch } from "ofetch";
3
3
  import { useMLProcessingStatus } from "./useMLProcessingStatus.js";
4
+ import { mlErrorMessage } from "../utils/errorMessage.js";
4
5
  export function useMLAssetManager(assetIds) {
5
6
  const assets = ref(/* @__PURE__ */ new Map());
6
7
  const loading = ref(false);
@@ -72,7 +73,7 @@ export function useMLAssetManager(assetIds) {
72
73
  }
73
74
  syncProcessingStreams();
74
75
  } catch (err) {
75
- error.value = err?.message ?? "Failed to load assets";
76
+ error.value = mlErrorMessage(err, "loadAssets");
76
77
  } finally {
77
78
  loading.value = false;
78
79
  }
@@ -1,6 +1,7 @@
1
1
  import { computed, getCurrentScope, onMounted, onScopeDispose, ref, toValue, watch } from "vue";
2
2
  import { $fetch } from "ofetch";
3
3
  import { streamUrlsRefreshDelay } from "../utils/streamUrls.js";
4
+ import { mlErrorMessage } from "../utils/errorMessage.js";
4
5
  export function useMLMediaAssets(ids) {
5
6
  const assets = ref({});
6
7
  const lookups = ref({});
@@ -46,7 +47,7 @@ export function useMLMediaAssets(ids) {
46
47
  assets.value = nextAssets;
47
48
  lookups.value = nextLookups;
48
49
  } catch (err) {
49
- error.value = err?.data?.message ?? err?.message ?? "Could not load files";
50
+ error.value = mlErrorMessage(err, "loadFiles");
50
51
  lookups.value = { ...lookups.value, ...Object.fromEntries(list.map((id) => [id, "error"])) };
51
52
  } finally {
52
53
  const next = new Set(pending.value);
@@ -1,5 +1,6 @@
1
1
  import { computed, onBeforeUnmount, onMounted, ref, toValue, watch } from "vue";
2
2
  import { $fetch } from "ofetch";
3
+ import { mlErrorMessage } from "../utils/errorMessage.js";
3
4
  export function useMLMediaBrowse(filters, opts = {}) {
4
5
  const pageSize = opts.pageSize ?? 30;
5
6
  const debounceMs = opts.debounceMs ?? 300;
@@ -28,7 +29,7 @@ export function useMLMediaBrowse(filters, opts = {}) {
28
29
  page.value = n;
29
30
  } catch (err) {
30
31
  if (id !== request) return;
31
- error.value = err?.data?.message ?? err?.message ?? "Could not load the library";
32
+ error.value = mlErrorMessage(err, "loadLibrary");
32
33
  } finally {
33
34
  if (id === request) loading.value = false;
34
35
  }
@@ -7,6 +7,7 @@ import {
7
7
  } from "vue";
8
8
  import { $fetch } from "ofetch";
9
9
  import { useMLProcessingStatus } from "./useMLProcessingStatus.js";
10
+ import { mlErrorMessage } from "../utils/errorMessage.js";
10
11
  function formatStepName(name) {
11
12
  return name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
12
13
  }
@@ -137,7 +138,7 @@ export function useMLProcessingStepper(assetId) {
137
138
  openStream();
138
139
  } catch (err) {
139
140
  step.status = "failed";
140
- step.error = err?.data?.detail ?? err?.message ?? "Failed to trigger step";
141
+ step.error = mlErrorMessage(err, "triggerStep");
141
142
  steps.value = [...steps.value];
142
143
  recomputeLocks();
143
144
  }
@@ -6,6 +6,7 @@ import { resolveUploadTarget } from "../utils/backends.js";
6
6
  import { kindForFile, mimeForFile } from "../utils/fileAccept.js";
7
7
  import { isUsableStep, stepLabel } from "../utils/processingSteps.js";
8
8
  import { useMLProcessingStatus } from "./useMLProcessingStatus.js";
9
+ import { mlErrorMessage } from "../utils/errorMessage.js";
9
10
  const STORAGE_KEY = "ml-upload-queue";
10
11
  const POLL_MS = 4e3;
11
12
  let seq = 0;
@@ -240,7 +241,9 @@ function createQueue() {
240
241
  } catch (err) {
241
242
  if (upload.phase === "canceled") return;
242
243
  upload.phase = "failed";
243
- upload.error = err?.data?.message ?? err?.message ?? "Upload failed";
244
+ const cause = err?.cause && typeof err.cause === "object" ? err.cause : null;
245
+ const fromCause = cause ? mlErrorMessage(cause, "upload") : null;
246
+ upload.error = fromCause && fromCause !== mlErrorMessage(null, "upload") ? fromCause : mlErrorMessage(err, "upload");
244
247
  resolveCreated(null);
245
248
  }
246
249
  }
@@ -289,7 +292,7 @@ function createQueue() {
289
292
  upload.orphaned = false;
290
293
  upload.claimError = null;
291
294
  } else {
292
- upload.claimError = err?.data?.detail ?? err?.data?.message ?? err?.statusMessage ?? err?.message ?? "Could not add the file";
295
+ upload.claimError = mlErrorMessage(err, "claim");
293
296
  }
294
297
  } finally {
295
298
  claiming.delete(upload.key);
@@ -28,3 +28,4 @@ export * from './utils/streamUrls.js';
28
28
  export * from './utils/webAppDeploys.js';
29
29
  export * from './composables/useMLWebAppDeploys.js';
30
30
  export * from './composables/useMLMediaMeta.js';
31
+ export * from './utils/errorMessage.js';
@@ -28,3 +28,4 @@ export * from "./utils/streamUrls.js";
28
28
  export * from "./utils/webAppDeploys.js";
29
29
  export * from "./composables/useMLWebAppDeploys.js";
30
30
  export * from "./composables/useMLMediaMeta.js";
31
+ export * from "./utils/errorMessage.js";
@@ -1,4 +1,6 @@
1
+ import { unref } from "vue";
1
2
  import { defineNuxtPlugin, useRuntimeConfig } from "#app";
3
+ import { configureMediaErrors } from "../utils/errorMessage.js";
2
4
  export default defineNuxtPlugin((nuxtApp) => {
3
5
  const config = useRuntimeConfig();
4
6
  const publicCfg = config.public?.mediaLibrary;
@@ -20,4 +22,9 @@ export default defineNuxtPlugin((nuxtApp) => {
20
22
  headers: publicCfg?.headers ?? {}
21
23
  };
22
24
  nuxtApp.provide("chunkUploader", cfg);
25
+ const ml = publicCfg;
26
+ configureMediaErrors(() => {
27
+ const i18nLocale = unref(nuxtApp.$i18n?.locale);
28
+ return typeof i18nLocale === "string" && i18nLocale ? i18nLocale : ml?.locale;
29
+ }, ml?.errorTexts);
23
30
  });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * One reader for every error the widgets show. Backends refuse with
3
+ * `{ detail, reason }`: `reason` is a stable code (py_client
4
+ * `UploadRejected`), `detail` English text whose wording may change. The
5
+ * body arrives at one of two levels:
6
+ *
7
+ * - app proxy (a layer's REST proxy rethrows with h3 `createError({ data })`):
8
+ * `err.data.data`, under an outer envelope whose `message` is the bare
9
+ * status text ("Bad Request");
10
+ * - module proxy (the module streams the upstream body): `err.data`.
11
+ *
12
+ * Shown, first that applies: the text for a known `reason` in the editor's
13
+ * language, then `detail`, then the inner `message`, `statusMessage`, the
14
+ * message of an error the module threw itself, and the site's own fallback. Never blank, never the "[POST] url: 400" text.
15
+ */
16
+ /** Where an error was shown; each has its own last-resort text. */
17
+ export type MediaErrorFallback = 'upload' | 'claim' | 'retryStep' | 'retryTask' | 'triggerStep' | 'loadFiles' | 'loadLibrary' | 'loadAssets' | 'updateVisibility' | 'loadPolicy';
18
+ export interface MediaErrorTexts {
19
+ /** Text per backend reason code (`too_large`, `context_mismatch`, …). */
20
+ reasons?: Record<string, string>;
21
+ /** Text per site, used when the error carries nothing to show. */
22
+ fallbacks?: Partial<Record<MediaErrorFallback, string>>;
23
+ }
24
+ /** Texts by language ("en", "ar"). */
25
+ export type MediaErrorTextsByLocale = Record<string, MediaErrorTexts>;
26
+ export declare const DEFAULT_ERROR_TEXTS: Record<'en' | 'ar', Required<MediaErrorTexts>>;
27
+ export interface MediaErrorOptions {
28
+ /** The editor's locale ("ar-SA", "en"); its language part picks the texts. */
29
+ locale?: string | null;
30
+ /** The app's texts over the built-in ones (module option `mediaLibrary.errorTexts`). */
31
+ texts?: MediaErrorTextsByLocale | null;
32
+ }
33
+ /** "ar-SA" → "ar"; a language without texts → "en". */
34
+ export declare function errorLanguage(locale: string | null | undefined, texts?: MediaErrorTextsByLocale | null): string;
35
+ /** The backend's reason code, when it sent a string one. */
36
+ export declare function errorReason(err: unknown): string | undefined;
37
+ /** The message to show for `err` at a site; see the file's comment for the order. */
38
+ export declare function mediaErrorMessage(err: unknown, fallback: MediaErrorFallback, options?: MediaErrorOptions): string;
39
+ /** Where the widgets read the editor's locale and the app's texts (the module plugin calls it). */
40
+ export declare function configureMediaErrors(locale: () => string | null | undefined, texts?: MediaErrorTextsByLocale | null): void;
41
+ /** `mediaErrorMessage` in the app's current locale, with its texts. */
42
+ export declare function mlErrorMessage(err: unknown, fallback: MediaErrorFallback): string;
@@ -0,0 +1,112 @@
1
+ export const DEFAULT_ERROR_TEXTS = {
2
+ en: {
3
+ reasons: {
4
+ mime_type: "This file type isn't allowed here.",
5
+ too_large: "This file is larger than the allowed size.",
6
+ too_small: "This file is smaller than the minimum size.",
7
+ kind: "This kind of file can't be uploaded here.",
8
+ size_required: "The file size is missing. Please try again.",
9
+ not_finalized: "The upload hasn't finished processing yet. Try again in a moment.",
10
+ asset_mismatch: "This file doesn't match the upload. Please upload it again.",
11
+ visibility: "This file's visibility doesn't fit here.",
12
+ unknown_session: "This upload was not found or has expired. Please upload again.",
13
+ profile_mismatch: "This file was uploaded for a different field.",
14
+ context_mismatch: "This file was uploaded for a different item."
15
+ },
16
+ fallbacks: {
17
+ upload: "Upload failed",
18
+ claim: "Could not add the file",
19
+ retryStep: "Could not retry this step",
20
+ retryTask: "Could not retry task",
21
+ triggerStep: "Failed to trigger step",
22
+ loadFiles: "Could not load files",
23
+ loadLibrary: "Could not load the library",
24
+ loadAssets: "Failed to load assets",
25
+ updateVisibility: "Failed to update visibility",
26
+ loadPolicy: "Failed to load policy"
27
+ }
28
+ },
29
+ ar: {
30
+ reasons: {
31
+ mime_type: "\u0646\u0648\u0639 \u0647\u0630\u0627 \u0627\u0644\u0645\u0644\u0641 \u063A\u064A\u0631 \u0645\u0633\u0645\u0648\u062D \u0628\u0647 \u0647\u0646\u0627.",
32
+ too_large: "\u062D\u062C\u0645 \u0647\u0630\u0627 \u0627\u0644\u0645\u0644\u0641 \u0623\u0643\u0628\u0631 \u0645\u0646 \u0627\u0644\u062D\u062F \u0627\u0644\u0645\u0633\u0645\u0648\u062D \u0628\u0647.",
33
+ too_small: "\u062D\u062C\u0645 \u0647\u0630\u0627 \u0627\u0644\u0645\u0644\u0641 \u0623\u0635\u063A\u0631 \u0645\u0646 \u0627\u0644\u062D\u062F \u0627\u0644\u0623\u062F\u0646\u0649.",
34
+ kind: "\u0644\u0627 \u064A\u0645\u0643\u0646 \u0631\u0641\u0639 \u0647\u0630\u0627 \u0627\u0644\u0646\u0648\u0639 \u0645\u0646 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0647\u0646\u0627.",
35
+ size_required: "\u062D\u062C\u0645 \u0627\u0644\u0645\u0644\u0641 \u063A\u064A\u0631 \u0645\u0639\u0631\u0648\u0641. \u064A\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649.",
36
+ not_finalized: "\u0644\u0645 \u062A\u0643\u062A\u0645\u0644 \u0645\u0639\u0627\u0644\u062C\u0629 \u0627\u0644\u0645\u0644\u0641 \u0628\u0639\u062F. \u062D\u0627\u0648\u0644 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649 \u0628\u0639\u062F \u0642\u0644\u064A\u0644.",
37
+ asset_mismatch: "\u0647\u0630\u0627 \u0627\u0644\u0645\u0644\u0641 \u0644\u0627 \u064A\u0637\u0627\u0628\u0642 \u0639\u0645\u0644\u064A\u0629 \u0627\u0644\u0631\u0641\u0639. \u064A\u0631\u062C\u0649 \u0631\u0641\u0639\u0647 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649.",
38
+ visibility: "\u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0638\u0647\u0648\u0631 \u0647\u0630\u0627 \u0627\u0644\u0645\u0644\u0641 \u0644\u0627 \u062A\u0646\u0627\u0633\u0628 \u0647\u0630\u0627 \u0627\u0644\u0645\u0643\u0627\u0646.",
39
+ unknown_session: "\u0644\u0645 \u064A\u062A\u0645 \u0627\u0644\u0639\u062B\u0648\u0631 \u0639\u0644\u0649 \u0639\u0645\u0644\u064A\u0629 \u0627\u0644\u0631\u0641\u0639 \u0623\u0648 \u0627\u0646\u062A\u0647\u062A \u0635\u0644\u0627\u062D\u064A\u062A\u0647\u0627. \u064A\u0631\u062C\u0649 \u0627\u0644\u0631\u0641\u0639 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649.",
40
+ profile_mismatch: "\u062A\u0645 \u0631\u0641\u0639 \u0647\u0630\u0627 \u0627\u0644\u0645\u0644\u0641 \u0644\u062D\u0642\u0644 \u0622\u062E\u0631.",
41
+ context_mismatch: "\u062A\u0645 \u0631\u0641\u0639 \u0647\u0630\u0627 \u0627\u0644\u0645\u0644\u0641 \u0644\u0639\u0646\u0635\u0631 \u0622\u062E\u0631."
42
+ },
43
+ fallbacks: {
44
+ upload: "\u0641\u0634\u0644 \u0631\u0641\u0639 \u0627\u0644\u0645\u0644\u0641",
45
+ claim: "\u062A\u0639\u0630\u0651\u0631\u062A \u0625\u0636\u0627\u0641\u0629 \u0627\u0644\u0645\u0644\u0641",
46
+ retryStep: "\u062A\u0639\u0630\u0651\u0631\u062A \u0625\u0639\u0627\u062F\u0629 \u0645\u062D\u0627\u0648\u0644\u0629 \u0647\u0630\u0647 \u0627\u0644\u062E\u0637\u0648\u0629",
47
+ retryTask: "\u062A\u0639\u0630\u0651\u0631\u062A \u0625\u0639\u0627\u062F\u0629 \u0645\u062D\u0627\u0648\u0644\u0629 \u0627\u0644\u0645\u0647\u0645\u0629",
48
+ triggerStep: "\u062A\u0639\u0630\u0651\u0631 \u062A\u0634\u063A\u064A\u0644 \u0627\u0644\u062E\u0637\u0648\u0629",
49
+ loadFiles: "\u062A\u0639\u0630\u0651\u0631 \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0645\u0644\u0641\u0627\u062A",
50
+ loadLibrary: "\u062A\u0639\u0630\u0651\u0631 \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0645\u0643\u062A\u0628\u0629",
51
+ loadAssets: "\u062A\u0639\u0630\u0651\u0631 \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0645\u0644\u0641\u0627\u062A",
52
+ updateVisibility: "\u062A\u0639\u0630\u0651\u0631 \u062A\u062D\u062F\u064A\u062B \u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0627\u0644\u0638\u0647\u0648\u0631",
53
+ loadPolicy: "\u062A\u0639\u0630\u0651\u0631 \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0633\u064A\u0627\u0633\u0629"
54
+ }
55
+ }
56
+ };
57
+ const nonEmpty = (s) => typeof s === "string" && s ? s : void 0;
58
+ function bodies(err) {
59
+ return [.../* @__PURE__ */ new Set([err, err?.data, err?.data?.data, err?.response?._data])].filter((b) => !!b && typeof b === "object");
60
+ }
61
+ function first(list, key) {
62
+ return list.map((b) => b[key]).find((v) => v != null);
63
+ }
64
+ function detailText(detail) {
65
+ if (typeof detail === "string") return nonEmpty(detail);
66
+ if (Array.isArray(detail)) return detail.map((d) => d && typeof d === "object" ? nonEmpty(d.msg) : void 0).find(Boolean);
67
+ if (detail && typeof detail === "object") return nonEmpty(detail.message);
68
+ return void 0;
69
+ }
70
+ function ownMessage(err) {
71
+ if (!(err instanceof Error) || err.name === "FetchError") return void 0;
72
+ const msg = nonEmpty(err.message);
73
+ return msg && !/^\[[A-Z]+\]\s/.test(msg) ? msg : void 0;
74
+ }
75
+ export function errorLanguage(locale, texts) {
76
+ const lang = (locale ?? "").toLowerCase().split(/[-_]/)[0] ?? "";
77
+ return lang && (lang in DEFAULT_ERROR_TEXTS || texts && lang in texts) ? lang : "en";
78
+ }
79
+ function textFor(kind, key, lang, texts) {
80
+ const pick = (l) => nonEmpty(texts?.[l]?.[kind]?.[key]) ?? nonEmpty(DEFAULT_ERROR_TEXTS[l]?.[kind]?.[key]);
81
+ return pick(lang) ?? (lang !== "en" ? pick("en") : void 0);
82
+ }
83
+ export function errorReason(err) {
84
+ return nonEmpty(first(bodies(err), "reason"));
85
+ }
86
+ export function mediaErrorMessage(err, fallback, options = {}) {
87
+ const lang = errorLanguage(options.locale, options.texts);
88
+ const list = bodies(err);
89
+ const reason = nonEmpty(first(list, "reason"));
90
+ const byReason = reason ? textFor("reasons", reason, lang, options.texts) : void 0;
91
+ if (byReason) return byReason;
92
+ const detail = detailText(first(list, "detail"));
93
+ if (detail) return detail;
94
+ const inner = list.filter((b) => b !== err).reverse().map((b) => nonEmpty(b.message)).find(Boolean);
95
+ const e = err;
96
+ return inner ?? nonEmpty(e?.statusMessage) ?? nonEmpty(e?.data?.statusMessage) ?? ownMessage(err) ?? textFor("fallbacks", fallback, lang, options.texts) ?? fallback;
97
+ }
98
+ let current = {
99
+ locale: () => null,
100
+ texts: null
101
+ };
102
+ export function configureMediaErrors(locale, texts) {
103
+ current = { locale, texts: texts ?? null };
104
+ }
105
+ export function mlErrorMessage(err, fallback) {
106
+ let locale = null;
107
+ try {
108
+ locale = current.locale();
109
+ } catch {
110
+ }
111
+ return mediaErrorMessage(err, fallback, { locale, texts: current.texts });
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-media-library",
3
- "version": "0.0.59",
3
+ "version": "0.0.60",
4
4
  "description": "Nuxt module for media library management — chunked uploads, asset previews, and resource policies",
5
5
  "repository": {
6
6
  "type": "git",