@spree/dashboard-core 6.0.2 → 6.0.4

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/vite/index.js +81 -5
  2. package/package.json +24 -24
  3. package/src/api-client.ts +7 -1
  4. package/src/client.ts +2 -0
  5. package/src/components/address-form-dialog.tsx +75 -28
  6. package/src/components/app-shell.tsx +5 -4
  7. package/src/components/app-sidebar.tsx +2 -1
  8. package/src/components/bulk-action-bar.tsx +6 -5
  9. package/src/components/country-state-fields.tsx +3 -0
  10. package/src/components/currency-select.tsx +6 -4
  11. package/src/components/developer-inspector-drawer.tsx +322 -0
  12. package/src/components/file-upload-field.tsx +64 -37
  13. package/src/components/image-upload-field.tsx +4 -0
  14. package/src/components/import-wizard-dialog.tsx +14 -12
  15. package/src/components/invitation-acceptance.tsx +15 -11
  16. package/src/components/media-editor-dialog.tsx +131 -0
  17. package/src/components/media-picker-dialog.tsx +866 -0
  18. package/src/components/media-picker-sheet.tsx +13 -300
  19. package/src/components/mobile-top-bar.tsx +1 -1
  20. package/src/components/nav-main.tsx +13 -2
  21. package/src/components/page-header.tsx +57 -13
  22. package/src/components/page-tabs.tsx +1 -1
  23. package/src/components/product-stock-summary.tsx +58 -0
  24. package/src/components/read-row.tsx +38 -0
  25. package/src/components/resource-combobox.tsx +1 -1
  26. package/src/components/resource-image-field.tsx +150 -0
  27. package/src/components/resource-picker-sheet.tsx +2 -2
  28. package/src/components/resource-table.tsx +161 -40
  29. package/src/components/section-heading.tsx +2 -2
  30. package/src/components/settings-container.tsx +19 -0
  31. package/src/components/settings-fields.tsx +165 -0
  32. package/src/components/settings-section.tsx +46 -0
  33. package/src/components/settings-sidebar.tsx +35 -18
  34. package/src/components/sheet-top-bar.tsx +7 -8
  35. package/src/components/sidebar-search.tsx +11 -12
  36. package/src/components/sidebar-user.tsx +39 -44
  37. package/src/components/skip-link.tsx +1 -1
  38. package/src/components/store-switcher.tsx +1 -1
  39. package/src/components/table-toolbar.tsx +194 -331
  40. package/src/components/timezone-combobox.tsx +119 -0
  41. package/src/components/top-bar-breadcrumbs.tsx +1 -1
  42. package/src/hooks/use-stock-locations.ts +6 -2
  43. package/src/hooks/use-table-density.ts +50 -0
  44. package/src/index.ts +20 -2
  45. package/src/lib/curl.test.ts +18 -0
  46. package/src/lib/curl.ts +28 -0
  47. package/src/lib/developer-inspector.test.ts +169 -0
  48. package/src/lib/developer-inspector.ts +134 -0
  49. package/src/lib/docs.ts +3 -2
  50. package/src/lib/fulfillment-items.test.ts +51 -0
  51. package/src/lib/fulfillment-items.ts +53 -0
  52. package/src/lib/request-snippets.ts +41 -0
  53. package/src/lib/table-registry.ts +13 -6
  54. package/src/lib/timestamp-columns.tsx +1 -1
  55. package/src/locales/ar.json +128 -14
  56. package/src/locales/de.json +128 -14
  57. package/src/locales/en.json +128 -14
  58. package/src/locales/es.json +128 -14
  59. package/src/locales/fr.json +128 -14
  60. package/src/locales/ja.json +128 -14
  61. package/src/locales/pl.json +130 -14
  62. package/src/locales/vi.json +128 -14
  63. package/src/locales/zh-CN.json +128 -14
  64. package/src/pages/settings-index.tsx +79 -48
  65. package/src/pages/stock-locations.tsx +14 -6
  66. package/src/products/digital-asset-edit-sheet.tsx +1 -1
  67. package/src/products/digital-asset-provider-sheet.tsx +1 -1
  68. package/src/products/digital-assets-card.tsx +7 -7
  69. package/src/products/inventory-section.tsx +2 -2
  70. package/src/products/media-edit-sheet.tsx +136 -133
  71. package/src/products/product-form-cards.tsx +204 -73
  72. package/src/products/product-form-mapping.test.ts +42 -1
  73. package/src/products/product-form-mapping.ts +20 -0
  74. package/src/products/product-schema.ts +8 -0
  75. package/src/products/product-types.ts +2 -0
  76. package/src/products/use-product-form-data.ts +30 -3
  77. package/src/products/variant-edit-sheet.tsx +3 -3
  78. package/src/products/variants-options-builder.tsx +13 -8
  79. package/src/products/variants-section.tsx +196 -4
  80. package/src/providers/auth-provider.tsx +2 -0
  81. package/src/providers/developer-inspector-provider.tsx +145 -0
  82. package/src/providers/json-link-resolver-provider.tsx +27 -0
  83. package/src/sellers/seller-schema.ts +107 -0
  84. package/src/tables/package-types.tsx +3 -3
  85. package/src/tables/stock-locations.tsx +15 -14
  86. package/src/virtual.d.ts +4 -0
  87. package/src/vite/index.ts +103 -5
@@ -4,6 +4,7 @@ import {
4
4
  fulfillmentItemRows,
5
5
  type GroupableFulfillment,
6
6
  type GroupableLineItem,
7
+ postSaleItemRow,
7
8
  unfulfilledItemRows,
8
9
  } from './fulfillment-items'
9
10
 
@@ -16,6 +17,7 @@ function lineItem(overrides: Partial<GroupableLineItem> = {}): GroupableLineItem
16
17
  thumbnail_url: 'https://example.test/shirt.jpg',
17
18
  price: '10.00',
18
19
  currency: 'USD',
20
+ product_id: 'prod_1',
19
21
  ...overrides,
20
22
  }
21
23
  }
@@ -45,6 +47,7 @@ describe('fulfillmentItemRows', () => {
45
47
  optionsText: 'Blue / M',
46
48
  thumbnailUrl: 'https://example.test/shirt.jpg',
47
49
  displayPrice: '$10.00',
50
+ productId: 'prod_1',
48
51
  quantity: 1,
49
52
  },
50
53
  ])
@@ -107,6 +110,7 @@ describe('fulfillmentItemRows', () => {
107
110
  optionsText: 'White',
108
111
  thumbnailUrl: null,
109
112
  displayPrice: null,
113
+ productId: null,
110
114
  quantity: 1,
111
115
  },
112
116
  ])
@@ -241,3 +245,50 @@ describe('fulfilledQuantities', () => {
241
245
  expect(quantities.size).toBe(0)
242
246
  })
243
247
  })
248
+
249
+ describe('postSaleItemRow', () => {
250
+ it('takes the name, image, price and product from the order line it came from', () => {
251
+ const row = postSaleItemRow(
252
+ 'rli_1',
253
+ { line_item_id: 'li_1', variant_id: 'variant_1', quantity: 1 },
254
+ [lineItem({ variant_id: 'variant_1' })],
255
+ )
256
+
257
+ expect(row).toMatchObject({
258
+ key: 'rli_1',
259
+ name: 'Shirt',
260
+ optionsText: 'Blue / M',
261
+ thumbnailUrl: 'https://example.test/shirt.jpg',
262
+ displayPrice: '$10.00',
263
+ productId: 'prod_1',
264
+ quantity: 1,
265
+ })
266
+ })
267
+
268
+ it('falls back to the variant it names when the order line is gone', () => {
269
+ const row = postSaleItemRow(
270
+ 'rli_1',
271
+ {
272
+ line_item_id: 'li_gone',
273
+ variant_id: 'variant_1',
274
+ variant: {
275
+ product_name: 'Mug',
276
+ options_text: 'Red',
277
+ thumbnail_url: 'https://example.test/mug.jpg',
278
+ product_id: 'prod_2',
279
+ },
280
+ quantity: 2,
281
+ },
282
+ [lineItem()],
283
+ )
284
+
285
+ expect(row).toMatchObject({
286
+ name: 'Mug',
287
+ optionsText: 'Red',
288
+ thumbnailUrl: 'https://example.test/mug.jpg',
289
+ displayPrice: null,
290
+ productId: 'prod_2',
291
+ quantity: 2,
292
+ })
293
+ })
294
+ })
@@ -20,6 +20,7 @@ export interface GroupableLineItem {
20
20
  thumbnail_url?: string | null
21
21
  price?: string | null
22
22
  currency?: string | null
23
+ product_id?: string | null
23
24
  }
24
25
 
25
26
  /** The fulfillment-item fields the grouping reads. */
@@ -53,6 +54,8 @@ export interface FulfillmentItemRow {
53
54
  optionsText: string | null
54
55
  thumbnailUrl: string | null
55
56
  displayPrice: string | null
57
+ /** The product to link the row to, when the line still names one. */
58
+ productId: string | null
56
59
  quantity: number
57
60
  }
58
61
 
@@ -144,6 +147,7 @@ function buildRow(
144
147
  name: copy?.name ?? fallback?.name ?? '',
145
148
  optionsText: copy?.options_text ?? fallback?.options_text ?? null,
146
149
  thumbnailUrl: copy?.thumbnail_url ?? null,
150
+ productId: copy?.product_id ?? null,
147
151
  displayPrice:
148
152
  copy?.price && copy.currency
149
153
  ? formatMoney(copy.price, copy.currency, i18n.language || 'en', { unitPrice: true })
@@ -186,6 +190,55 @@ export function fulfillmentItemRows(
186
190
  return [...rows.values()]
187
191
  }
188
192
 
193
+ /** A return, claim or exchange line, as the item row reads it. */
194
+ export interface PostSaleLine {
195
+ line_item_id?: string | null
196
+ variant_id?: string | null
197
+ /** The line's own copy of the product name, where the panel's API sends one. */
198
+ name?: string | null
199
+ variant?: {
200
+ product_name?: string | null
201
+ options_text?: string | null
202
+ thumbnail_url?: string | null
203
+ sku?: string | null
204
+ product_id?: string | null
205
+ } | null
206
+ quantity: number
207
+ }
208
+
209
+ /**
210
+ * A return, claim or exchange line as the same row a fulfillment shows: the
211
+ * order line it came from gives the name, image and price; the variant it
212
+ * names fills in when the order line is gone.
213
+ */
214
+ export function postSaleItemRow(
215
+ key: string,
216
+ line: PostSaleLine,
217
+ lineItems: GroupableLineItem[],
218
+ ): FulfillmentItemRow {
219
+ const lineItem = line.line_item_id
220
+ ? lineItems.find((item) => item.id === line.line_item_id)
221
+ : undefined
222
+ const variant = line.variant
223
+ const row = buildRow(
224
+ key,
225
+ lineItem,
226
+ {
227
+ variant_id: line.variant_id,
228
+ name: line.name ?? variant?.product_name ?? null,
229
+ options_text: variant?.options_text ?? null,
230
+ },
231
+ line.quantity,
232
+ )
233
+
234
+ return {
235
+ ...row,
236
+ name: row.name || variant?.sku || line.variant_id || '',
237
+ thumbnailUrl: row.thumbnailUrl ?? variant?.thumbnail_url ?? null,
238
+ productId: row.productId ?? variant?.product_id ?? null,
239
+ }
240
+ }
241
+
189
242
  /**
190
243
  * Line-item units no fulfillment has claimed: the ordered quantity minus what
191
244
  * every fulfillment holds. Only positive remainders are returned, so an order
@@ -0,0 +1,41 @@
1
+ import { buildCurlCommand } from './curl'
2
+ import { type DeveloperInspectorEntry, SENSITIVE_HEADERS } from './developer-inspector'
3
+
4
+ const ADMIN_API_PREFIX = '/api/v3/admin'
5
+ // Credentials are replaced by the placeholder; a replayed idempotency key
6
+ // would return the cached response instead of running the request again.
7
+ const OMITTED_HEADERS = new Set([...SENSITIVE_HEADERS, 'idempotency-key'])
8
+
9
+ function isSendableBody(body: unknown): boolean {
10
+ return body !== undefined && body !== null && typeof body !== 'string'
11
+ }
12
+
13
+ /** A curl command replaying the request, with a placeholder for the token. */
14
+ export function entryToCurl(entry: DeveloperInspectorEntry): string {
15
+ const headers = Object.fromEntries(
16
+ Object.entries(entry.requestHeaders).filter(
17
+ ([name]) => !OMITTED_HEADERS.has(name.toLowerCase()),
18
+ ),
19
+ )
20
+ return buildCurlCommand(entry.url, headers, {
21
+ method: entry.method,
22
+ body: isSendableBody(entry.requestBody) ? entry.requestBody : undefined,
23
+ })
24
+ }
25
+
26
+ /**
27
+ * The same request through `@spree/admin-sdk`. Uses the low-level
28
+ * `adminClient.request`, which always matches the wire call; the resource
29
+ * method names would be a guess.
30
+ */
31
+ export function entryToSdkCall(entry: DeveloperInspectorEntry): string {
32
+ const path = entry.path.startsWith(ADMIN_API_PREFIX)
33
+ ? entry.path.slice(ADMIN_API_PREFIX.length)
34
+ : entry.path
35
+ const options: Record<string, unknown> = {}
36
+ if (entry.query && Object.keys(entry.query).length > 0) options.params = entry.query
37
+ if (isSendableBody(entry.requestBody)) options.body = entry.requestBody
38
+ const args = [JSON.stringify(entry.method), JSON.stringify(path)]
39
+ if (Object.keys(options).length > 0) args.push(JSON.stringify(options, null, 2))
40
+ return `await adminClient.request(${args.join(', ')})`
41
+ }
@@ -178,6 +178,10 @@ export interface TableDef<T = any> {
178
178
  columns: ColumnDef<T>[]
179
179
  searchParam?: string
180
180
  searchPlaceholder?: string
181
+ /**
182
+ * Whether the toolbar offers a free-text search. On by default.
183
+ */
184
+ searchable?: boolean
181
185
  defaultSort?: SortOption
182
186
  emptyIcon?: ReactNode
183
187
  emptyMessage?: string
@@ -262,18 +266,21 @@ export const tables = new Proxy({} as Record<string, TableMutator>, {
262
266
  })
263
267
 
264
268
  interface TableMutator {
265
- addColumn<T = any>(column: ColumnDef<T>): void
269
+ /** `after` places the column right after that one, instead of last. */
270
+ addColumn<T = any>(column: ColumnDef<T>, options?: { after?: string }): void
266
271
  removeColumn(key: string): void
267
272
  updateColumn<T = any>(key: string, updates: Partial<ColumnDef<T>>): void
268
273
  }
269
274
 
270
- function applyAddColumn(table: TableDef, column: ColumnDef) {
275
+ function applyAddColumn(table: TableDef, column: ColumnDef, after?: string) {
271
276
  if (table.columns.some((c) => c.key === column.key)) {
272
277
  throw new Error(
273
278
  `Column "${column.key}" already exists in table "${table.key}". Use updateColumn() instead.`,
274
279
  )
275
280
  }
276
- table.columns.push(column)
281
+ const index = after ? table.columns.findIndex((c) => c.key === after) : -1
282
+ if (index === -1) table.columns.push(column)
283
+ else table.columns.splice(index + 1, 0, column)
277
284
  }
278
285
 
279
286
  function applyRemoveColumn(table: TableDef, key: string) {
@@ -288,10 +295,10 @@ function applyUpdateColumn(table: TableDef, key: string, updates: Partial<Column
288
295
 
289
296
  function createMutator(tableKey: string): TableMutator {
290
297
  return {
291
- addColumn(column) {
298
+ addColumn(column, options) {
292
299
  const table = registry.get(tableKey)
293
- if (table) applyAddColumn(table, column as ColumnDef)
294
- else enqueue(tableKey, (t) => applyAddColumn(t, column as ColumnDef))
300
+ if (table) applyAddColumn(table, column as ColumnDef, options?.after)
301
+ else enqueue(tableKey, (t) => applyAddColumn(t, column as ColumnDef, options?.after))
295
302
  },
296
303
  removeColumn(key) {
297
304
  const table = registry.get(tableKey)
@@ -6,7 +6,7 @@ const TIMESTAMP_KEYS = ['created_at', 'updated_at'] as const
6
6
 
7
7
  type TimestampKey = (typeof TIMESTAMP_KEYS)[number]
8
8
 
9
- const TIMESTAMP_CLASS = 'text-sm text-muted-foreground whitespace-nowrap'
9
+ const TIMESTAMP_CLASS = 'text-base text-muted-foreground whitespace-nowrap'
10
10
 
11
11
  function createTimestampColumn(key: TimestampKey, visibleByDefault = false): ColumnDef {
12
12
  return {
@@ -37,7 +37,6 @@
37
37
  },
38
38
  "account": {
39
39
  "community": "المجتمع",
40
- "contact_support": "الاتصال بالدعم",
41
40
  "documentation": "التوثيق",
42
41
  "edit_profile": "تعديل الملف الشخصي",
43
42
  "language": {
@@ -45,8 +44,8 @@
45
44
  "update_failed": "تعذّر حفظ تفضيل اللغة. يرجى المحاولة مجددًا."
46
45
  },
47
46
  "log_out": "تسجيل الخروج",
48
- "preferences": "التفضيلات",
49
- "view_store": "عرض المتجر"
47
+ "report_issue": "الإبلاغ عن مشكلة",
48
+ "send_feedback": "إرسال ملاحظات"
50
49
  },
51
50
  "actions": {
52
51
  "actions_menu": "الإجراءات",
@@ -102,7 +101,11 @@
102
101
  "default_shipping": "الشحن الافتراضي",
103
102
  "inherited_from": "موروث من {{name}}",
104
103
  "set_default_billing": "تعيين كعنوان فوترة افتراضي",
105
- "set_default_shipping": "تعيين كعنوان شحن افتراضي"
104
+ "set_default_shipping": "تعيين كعنوان شحن افتراضي",
105
+ "columns": {
106
+ "address": "العنوان",
107
+ "defaults": "افتراضي لـ"
108
+ }
106
109
  },
107
110
  "auth": {
108
111
  "back_to_login": "العودة إلى تسجيل الدخول",
@@ -190,7 +193,8 @@
190
193
  "unsaved_changes": "لديك تغييرات غير محفوظة.",
191
194
  "yes": "نعم",
192
195
  "done": "تم",
193
- "error": "حدث خطأ ما."
196
+ "error": "حدث خطأ ما.",
197
+ "unsaved_label": "تغييرات غير محفوظة"
194
198
  },
195
199
  "components": {
196
200
  "address_block": {
@@ -321,7 +325,8 @@
321
325
  "drop_label": "أسقط ملفًا هنا، أو انقر للاستعراض",
322
326
  "invalid_type": "نوع الملف هذا غير مقبول (المتوقع: {{accept}})",
323
327
  "upload_failed": "فشل الرفع: {{message}}",
324
- "uploading": "جارٍ الرفع…"
328
+ "uploading": "جارٍ الرفع…",
329
+ "replace": "استبدال الملف"
325
330
  },
326
331
  "import_button": {
327
332
  "delimiter_label": "فاصل الأعمدة",
@@ -342,7 +347,8 @@
342
347
  "json_preview_drawer": {
343
348
  "collapse_depth": "عمق الطي",
344
349
  "copied": "تم النسخ",
345
- "copy_all": "نسخ الكل",
350
+ "copy_curl": "نسخ كـ curl",
351
+ "copy_json": "نسخ JSON",
346
352
  "depth": "العمق {{level}}",
347
353
  "failed_to_load": "فشل التحميل: {{message}}",
348
354
  "open_raw": "فتح الاستجابة الخام",
@@ -437,11 +443,8 @@
437
443
  "this_month": "هذا الشهر",
438
444
  "today": "اليوم"
439
445
  },
440
- "date_range_end": "إلى",
441
- "date_range_start": "من",
442
446
  "descending": "تنازلي",
443
447
  "filter_by": "تصفية حسب…",
444
- "filter_date_placeholder": "اختر تاريخًا",
445
448
  "filter_select_placeholder": "اختر…",
446
449
  "filter_text_placeholder": "تصفية…",
447
450
  "filter_to_placeholder": "التصفية إلى…",
@@ -475,7 +478,10 @@
475
478
  "select_all": "تحديد الكل",
476
479
  "sort_by": "ترتيب حسب",
477
480
  "sort_tooltip": "ترتيب",
478
- "visible_columns": "الأعمدة المرئية"
481
+ "visible_columns": "الأعمدة المرئية",
482
+ "row_density": "كثافة الصفوف",
483
+ "density_comfortable": "مريحة",
484
+ "density_compact": "مضغوطة"
479
485
  },
480
486
  "tag_combobox": {
481
487
  "create_option": "إنشاء «{{tag}}»",
@@ -488,11 +494,58 @@
488
494
  "light": "فاتح",
489
495
  "system": "النظام"
490
496
  },
497
+ "timezone_combobox": {
498
+ "empty": "لم يتم العثور على مناطق زمنية",
499
+ "placeholder": "اختر منطقة زمنية",
500
+ "search_placeholder": "ابحث عن المناطق الزمنية…"
501
+ },
491
502
  "wizard": {
492
503
  "discard_cancel": "متابعة التحرير",
493
504
  "discard_confirm": "تجاهل",
494
505
  "discard_message": "سيُفقد كل ما أدخلته حتى الآن، ولا يمكن التراجع عن ذلك.",
495
506
  "discard_title": "تجاهل هذه المسودة؟"
507
+ },
508
+ "product_stock": {
509
+ "out_of_stock": "إنتهى من المخزن",
510
+ "on_backorder": "على المؤجل",
511
+ "in_stock": "{{count}} في المخزون",
512
+ "in_stock_short": "في المخزون",
513
+ "variants_one": "{{count}} البديل",
514
+ "variants_other": "{{count}} متغيرات"
515
+ },
516
+ "developer_inspector": {
517
+ "title": "أدوات المطوّر",
518
+ "description": "طلبات Admin API من علامة التبويب هذه، محفوظة في الذاكرة فقط. الرموز وكلمات المرور مخفية.",
519
+ "system_info": "معلومات النظام",
520
+ "clear": "مسح",
521
+ "tabs": {
522
+ "requests": "الطلبات",
523
+ "errors": "الأخطاء"
524
+ },
525
+ "empty_requests": "لا توجد طلبات بعد. استخدم لوحة التحكم وستظهر هنا.",
526
+ "empty_errors": "لا توجد طلبات فاشلة.",
527
+ "select_request": "اختر طلبًا لعرض تفاصيله.",
528
+ "network_error": "خطأ في الشبكة",
529
+ "status": "الحالة",
530
+ "duration": "المدة",
531
+ "duration_ms": "{{value}} مللي ثانية",
532
+ "request_id": "معرّف الطلب",
533
+ "error_code": "رمز الخطأ",
534
+ "query": "معلمات الاستعلام",
535
+ "request_body": "نص الطلب",
536
+ "response_body": "نص الاستجابة",
537
+ "no_body": "لا يوجد نص",
538
+ "copy_curl": "نسخ كأمر curl",
539
+ "copy_sdk": "نسخ كاستدعاء SDK",
540
+ "copy_response": "نسخ الاستجابة",
541
+ "copy_request_id": "نسخ معرّف الطلب",
542
+ "copied": "تم النسخ",
543
+ "menu_open": "أدوات المطوّر"
544
+ },
545
+ "server_error": {
546
+ "title": "حدث خطأ في الخادم",
547
+ "request_id": "الطلب {{id}}",
548
+ "report": "إبلاغ"
496
549
  }
497
550
  },
498
551
  "currency_amounts": {
@@ -963,6 +1016,24 @@
963
1016
  "digital_asset_authorized_days": {
964
1017
  "label": "الأيام المتاحة"
965
1018
  }
1019
+ },
1020
+ "seller": {
1021
+ "about": {
1022
+ "label": "نبذة",
1023
+ "help": "تظهر في ملف البائع بالمتجر."
1024
+ },
1025
+ "holiday_mode_until": {
1026
+ "label": "غائب حتى",
1027
+ "placeholder": "اختر تاريخاً",
1028
+ "help": "يبقى الكتالوج ظاهراً لكن لا يمكن الشراء حتى ذلك الحين."
1029
+ },
1030
+ "contact_email": {
1031
+ "help": "تصل إليه الدعوات وإشعارات السوق."
1032
+ },
1033
+ "billing_email": {
1034
+ "label": "بريد الفوترة",
1035
+ "help": "تصل إليه فواتير العمولة وإشعارات الدفع إن اختلف."
1036
+ }
966
1037
  }
967
1038
  },
968
1039
  "image_upload": {
@@ -1067,15 +1138,35 @@
1067
1138
  "signed_in_as_invitee": ". أنت مسجّل الدخول باسم {{email}}."
1068
1139
  },
1069
1140
  "media_picker": {
1141
+ "any": "الكل",
1070
1142
  "confirm": "إضافة المحدد",
1071
1143
  "description": "اختر ملفات موجودة في مكتبتك، أو ارفع ملفات جديدة.",
1144
+ "drop_hint_images": "أو اسحب الصور وأفلتها هنا",
1145
+ "drop_hint_media": "أو اسحب الصور والفيديوهات وأفلتها هنا",
1072
1146
  "empty": "مكتبتك فارغة. ارفع ملفًا للبدء.",
1147
+ "filter_type": "النوع",
1148
+ "filter_usage": "الاستخدام",
1073
1149
  "no_results": "لا توجد ملفات تطابق بحثك.",
1074
1150
  "search_placeholder": "البحث بالاسم…",
1075
1151
  "selected_count_one": "تم تحديد {{count}}",
1076
1152
  "selected_count_other": "تم تحديد {{count}}",
1153
+ "sort_date_added": "تاريخ الإضافة",
1154
+ "sort_file_name": "اسم الملف",
1155
+ "sort_file_size": "حجم الملف",
1077
1156
  "title": "مكتبة الوسائط",
1078
- "upload": "رفع"
1157
+ "type_image": "الصور",
1158
+ "type_video": "الفيديوهات",
1159
+ "upload": "رفع",
1160
+ "usage_unused": "غير مستخدم",
1161
+ "usage_used": "مستخدم",
1162
+ "view": "العرض",
1163
+ "view_grid": "عرض شبكي",
1164
+ "view_list": "عرض قائمة"
1165
+ },
1166
+ "media_editor": {
1167
+ "information": "المعلومات",
1168
+ "details": "التفاصيل",
1169
+ "added": "أُضيف في {{date}}"
1079
1170
  },
1080
1171
  "messages": {
1081
1172
  "address_removed": "تمت إزالة العنوان",
@@ -1568,12 +1659,18 @@
1568
1659
  "add_video_title": "إضافة رابط فيديو",
1569
1660
  "assigned_variants_help": "اختر المتغيرات التي تمثلها هذه الصورة. اتركه فارغًا ليتم تطبيقه على كافة المتغيرات.",
1570
1661
  "assigned_variants_label": "المتغيرات المخصصة",
1571
- "delete_confirm": "هل تريد حذف هذه الوسائط؟ لا يمكن التراجع عن هذا.",
1662
+ "delete_confirm": "إزالة هذه الوسائط؟ ستُحذف عند حفظ المنتج.",
1663
+ "delete_selected_confirm_one": "إزالة الوسائط المحددة؟ ستُحذف عند حفظ المنتج.",
1664
+ "delete_selected_confirm_other": "إزالة الوسائط المحددة ({{count}})؟ ستُحذف عند حفظ المنتج.",
1572
1665
  "drop_hint": "قم بسحب وإفلات الوسائط هنا، أو انقر للتصفح",
1573
1666
  "edit_title": "تحرير الوسائط",
1574
1667
  "file_types_hint": "صور (PNG، JPG، WebP) وفيديو (MP4، WebM، MOV)",
1575
1668
  "focal_point_help": "انقر على الصورة لتحديد الجزء الذي يبقى ظاهراً عند الاقتصاص.",
1576
1669
  "focal_point_reset": "إعادة تعيين",
1670
+ "select_all": "تحديد كل الوسائط",
1671
+ "select_item": "تحديد {{name}}",
1672
+ "selected_count_one": "تم تحديد {{count}}",
1673
+ "selected_count_other": "تم تحديد {{count}}",
1577
1674
  "unsupported_file": "لم تتم إضافة {{count}} ملف — الصور والفيديو فقط مدعومة.",
1578
1675
  "upload_failed": "فشل تحميل {{name}}: {{message}}",
1579
1676
  "upload_status_failed": "فشل",
@@ -1683,6 +1780,10 @@
1683
1780
  "failed_to_create_option_type": "فشل إنشاء نوع الخيار",
1684
1781
  "failed_to_create_value": "فشل إنشاء القيمة"
1685
1782
  },
1783
+ "images_aria": "صور {{name}}",
1784
+ "images_description": "حدد صور المنتج التي تعرض هذا المتغير. تظهر بترتيب بطاقة الوسائط، وتصبح أول صورة محددة صورته المصغرة — اسحب الصور في بطاقة الوسائط لتغييرها.",
1785
+ "images_title": "صور {{name}}",
1786
+ "images_unsaved": "احفظ المنتج لإضافة صور إلى هذا المتغير",
1686
1787
  "keep": "احتفظ بالكل",
1687
1788
  "option_type_label": "نوع الخيار",
1688
1789
  "option_type_placeholder": "اختر نوع الخيار",
@@ -1704,6 +1805,7 @@
1704
1805
  "carton_placeholder": "بدون كرتونة",
1705
1806
  "pending_save": "تُحفظ التغييرات هنا مع المنتج."
1706
1807
  },
1808
+ "thumbnail_badge": "الصورة المصغرة",
1707
1809
  "title": "المتغيرات",
1708
1810
  "variant_label": "البديل"
1709
1811
  }
@@ -1792,7 +1894,8 @@
1792
1894
  "value_count_other": "{{count}} قيم"
1793
1895
  },
1794
1896
  "media_library": {
1795
- "download": "تنزيل"
1897
+ "download": "تنزيل",
1898
+ "choose_from_library": "اختر من المكتبة"
1796
1899
  },
1797
1900
  "catalogs": {
1798
1901
  "terms": {
@@ -2120,6 +2223,17 @@
2120
2223
  "replace": "استبدال الملف",
2121
2224
  "replace_description": "سيُحذف \"{{name}}\" وسيحمّل كل من اشتراه الملف الجديد من روابطه الحالية. يسري فورًا.",
2122
2225
  "replace_title": "استبدال هذا الملف؟"
2226
+ },
2227
+ "sellers": {
2228
+ "images": {
2229
+ "cover_photo_label": "صورة الغلاف",
2230
+ "cover_photo_help": "لافتة عريضة أعلى ملف البائع.",
2231
+ "logo_label": "الشعار",
2232
+ "logo_help": "يُفضَّل الشكل المربّع. يظهر بجوار البائع في كل مكان يرد فيه.",
2233
+ "square_logo_label": "شعار مربّع",
2234
+ "square_logo_help": "للمواضع التي تتطلب مربّعاً دقيقاً، مثل الصورة الرمزية."
2235
+ },
2236
+ "not_provided": "غير محدد"
2123
2237
  }
2124
2238
  }
2125
2239
  }