@medusajs/dashboard 3.0.0-snapshot-20251216103925 → 3.0.0-snapshot-20251216135612
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-campaign-promotions-PHAHMGXP.mjs → add-campaign-promotions-OYPGISTF.mjs} +4 -4
- package/dist/{api-key-management-detail-NRGL7HHX.mjs → api-key-management-detail-6RCDH73M.mjs} +5 -5
- package/dist/{api-key-management-list-LJSWRAGE.mjs → api-key-management-list-KC5GOWAU.mjs} +5 -5
- package/dist/{api-key-management-sales-channels-3GRNDBZ4.mjs → api-key-management-sales-channels-LUB5G6RC.mjs} +5 -5
- package/dist/app.js +3752 -133
- package/dist/app.mjs +2 -2
- package/dist/{campaign-detail-22Q6XWGL.mjs → campaign-detail-5Q4BYCPX.mjs} +5 -5
- package/dist/{campaign-list-OH566KWW.mjs → campaign-list-PEOTTWBA.mjs} +6 -6
- package/dist/{category-detail-RRKJPMEG.mjs → category-detail-S5IPXMHX.mjs} +4 -4
- package/dist/{category-list-4HZP4FRE.mjs → category-list-QBYJ4T3R.mjs} +4 -4
- package/dist/{category-products-EFZRTCSF.mjs → category-products-KPW6BA5J.mjs} +4 -4
- package/dist/{chunk-LCEKY54O.mjs → chunk-27MGH3HR.mjs} +1 -1
- package/dist/{chunk-KQ4LC2YV.mjs → chunk-2DULKOPN.mjs} +1 -1
- package/dist/{chunk-SUYYSKCB.mjs → chunk-535OVBXR.mjs} +8 -1
- package/dist/{chunk-32FPYJ3S.mjs → chunk-AHZLMCZF.mjs} +1 -1
- package/dist/{chunk-5IFK2ZTA.mjs → chunk-APGDAT7X.mjs} +2 -2
- package/dist/{chunk-2X25XWOW.mjs → chunk-BTYBTKWK.mjs} +1 -1
- package/dist/{chunk-DNUVCBN7.mjs → chunk-CCQD65EY.mjs} +160 -58
- package/dist/{chunk-RL5EYTP6.mjs → chunk-CVHJAKLQ.mjs} +1 -1
- package/dist/{chunk-DZWH2RV6.mjs → chunk-DFFLVEZ5.mjs} +1 -1
- package/dist/{chunk-YTN73JYH.mjs → chunk-DTZXEQXZ.mjs} +1 -1
- package/dist/{chunk-7R743WZ6.mjs → chunk-EHU67PIM.mjs} +1 -1
- package/dist/{chunk-VX2HE7O5.mjs → chunk-FBYTX6K7.mjs} +1 -1
- package/dist/{chunk-RMDYKRWW.mjs → chunk-KFYQTOGB.mjs} +1 -1
- package/dist/{chunk-GA3UMQMZ.mjs → chunk-O7JNIATG.mjs} +1 -1
- package/dist/{chunk-OHY2N2Q4.mjs → chunk-OL24RDYM.mjs} +3 -3
- package/dist/{chunk-M4Q5Q4I3.mjs → chunk-PA3T6IWL.mjs} +2 -2
- package/dist/{chunk-NL376WAO.mjs → chunk-QISH26J3.mjs} +1 -1
- package/dist/{chunk-PVW6M64S.mjs → chunk-QOCJPBRB.mjs} +1 -1
- package/dist/{chunk-2GWGNMAA.mjs → chunk-R4ZOO4ON.mjs} +1 -1
- package/dist/{chunk-43X7ZR3P.mjs → chunk-RS7DWLEP.mjs} +1 -1
- package/dist/{chunk-ROKB75YP.mjs → chunk-VT2JJ5C2.mjs} +1 -1
- package/dist/{chunk-DK7IWUMK.mjs → chunk-WYATCUOM.mjs} +3 -3
- package/dist/{chunk-CV65NY6Y.mjs → chunk-YFIYCS7F.mjs} +1 -1
- package/dist/{chunk-O7WJSSQR.mjs → chunk-YKYVCQRS.mjs} +3346 -93
- package/dist/{chunk-GU5PJRPM.mjs → chunk-ZQJPHZKI.mjs} +1 -1
- package/dist/{collection-add-products-XUMV6XR7.mjs → collection-add-products-FU2BS3D3.mjs} +4 -4
- package/dist/{collection-detail-ONRBKJLN.mjs → collection-detail-VJE7XHLV.mjs} +4 -4
- package/dist/{collection-list-XCC4SIPJ.mjs → collection-list-IGA6SCNF.mjs} +4 -4
- package/dist/{customer-detail-FR6J37ZC.mjs → customer-detail-MOV2T3LF.mjs} +6 -6
- package/dist/{customer-group-add-customers-DM4VLTNB.mjs → customer-group-add-customers-XMR2WBXX.mjs} +6 -6
- package/dist/{customer-group-detail-YSKSNETG.mjs → customer-group-detail-6T7OXGQD.mjs} +6 -6
- package/dist/{customer-group-list-XBCD4FZH.mjs → customer-group-list-AJEAF5D2.mjs} +3 -3
- package/dist/{customer-list-TG4D4QOT.mjs → customer-list-UI5EQDII.mjs} +6 -6
- package/dist/{customers-add-customer-group-Q7FMR2Y5.mjs → customers-add-customer-group-QVTVSQYM.mjs} +4 -4
- package/dist/{inventory-create-3XONKYMZ.mjs → inventory-create-ANYUM4P5.mjs} +1 -1
- package/dist/{inventory-detail-6A6GOLB6.mjs → inventory-detail-ZPSEMYI2.mjs} +4 -4
- package/dist/{inventory-list-2CJLAK3X.mjs → inventory-list-RXJPSVZE.mjs} +4 -4
- package/dist/{inventory-stock-S3ZYYCMZ.mjs → inventory-stock-FD4ZM4BB.mjs} +2 -2
- package/dist/{location-fulfillment-providers-WM6DT252.mjs → location-fulfillment-providers-7ZUJAGNY.mjs} +4 -4
- package/dist/{location-sales-channels-WLVTMU4Z.mjs → location-sales-channels-P3QJTFDT.mjs} +5 -5
- package/dist/{location-service-zone-create-3FWF3DG5.mjs → location-service-zone-create-J43WN6G4.mjs} +5 -5
- package/dist/{location-service-zone-manage-areas-YFEAZUUN.mjs → location-service-zone-manage-areas-6ZPMKMSX.mjs} +5 -5
- package/dist/{location-service-zone-shipping-option-create-MJPH3WKX.mjs → location-service-zone-shipping-option-create-ZJ4GIBTJ.mjs} +2 -2
- package/dist/{location-service-zone-shipping-option-pricing-6IRNPWJY.mjs → location-service-zone-shipping-option-pricing-CR4BVYG3.mjs} +2 -2
- package/dist/{order-create-claim-GUYTLVPB.mjs → order-create-claim-SCDJGM46.mjs} +4 -4
- package/dist/{order-create-edit-ODIN6GRW.mjs → order-create-edit-JIE3HDHP.mjs} +4 -4
- package/dist/{order-create-exchange-ZT5RBRKL.mjs → order-create-exchange-LQU4YN7F.mjs} +4 -4
- package/dist/{order-create-return-E2KILJX2.mjs → order-create-return-52GHGW5Z.mjs} +4 -4
- package/dist/{order-detail-HFJONELJ.mjs → order-detail-PVPGEWGY.mjs} +2 -2
- package/dist/{order-export-4MZUPMGD.mjs → order-export-LE363ZLB.mjs} +3 -3
- package/dist/{order-list-ACSFGIPD.mjs → order-list-GRLQWN4L.mjs} +7 -7
- package/dist/{price-list-configuration-IHPSUNZJ.mjs → price-list-configuration-6S3MLNXQ.mjs} +5 -5
- package/dist/{price-list-create-YHXRQSC3.mjs → price-list-create-MFRUQADC.mjs} +7 -7
- package/dist/{price-list-detail-FR3FQR3H.mjs → price-list-detail-Q5VG5VGW.mjs} +5 -5
- package/dist/{price-list-list-YSEM6IAI.mjs → price-list-list-DG5YEZ44.mjs} +4 -4
- package/dist/{price-list-prices-add-GJVI47OY.mjs → price-list-prices-add-SDU5YZAT.mjs} +6 -6
- package/dist/{price-list-prices-edit-E4Q5TQPM.mjs → price-list-prices-edit-5USQR4D4.mjs} +2 -2
- package/dist/{product-attributes-QD3BWV5V.mjs → product-attributes-EFIRUBRO.mjs} +2 -2
- package/dist/{product-create-E2GZYQX4.mjs → product-create-K6EWZHIT.mjs} +7 -7
- package/dist/{product-create-variant-KEBN5OR7.mjs → product-create-variant-ERKHTEJZ.mjs} +1 -1
- package/dist/{product-detail-QBGGKRZ2.mjs → product-detail-DKPZDEIY.mjs} +4 -4
- package/dist/{product-edit-YP4KOQ4T.mjs → product-edit-55YXTIGO.mjs} +2 -2
- package/dist/{product-export-WUZYHPS5.mjs → product-export-5AD7NELI.mjs} +3 -3
- package/dist/{product-image-variants-edit-Y363J5NG.mjs → product-image-variants-edit-M6QF2RLE.mjs} +4 -4
- package/dist/{product-list-DNTS7WUN.mjs → product-list-EUWZIFTM.mjs} +7 -7
- package/dist/{product-organization-H557PLLB.mjs → product-organization-N3VBRXF4.mjs} +2 -2
- package/dist/{product-prices-JOG6IIQ7.mjs → product-prices-4C36AG4R.mjs} +1 -1
- package/dist/{product-sales-channels-ANCFZZ5S.mjs → product-sales-channels-PPXUG4KT.mjs} +5 -5
- package/dist/{product-stock-NYUFMEVG.mjs → product-stock-VEGE6SUZ.mjs} +2 -2
- package/dist/{product-tag-detail-EHBB3WUB.mjs → product-tag-detail-I3MBZX7U.mjs} +10 -10
- package/dist/{product-tag-list-LSW5FFVN.mjs → product-tag-list-JUWSOMB7.mjs} +10 -10
- package/dist/{product-type-detail-3VB6AWUW.mjs → product-type-detail-RKHT5NBL.mjs} +4 -4
- package/dist/{product-type-list-FD3TGPNP.mjs → product-type-list-QQKAHBJ3.mjs} +6 -6
- package/dist/{product-variant-detail-43T33AQP.mjs → product-variant-detail-XAYG5CKE.mjs} +4 -4
- package/dist/{profile-detail-BMC7IZBY.mjs → profile-detail-FRZ74HAF.mjs} +1 -1
- package/dist/{profile-edit-YZCUGEXF.mjs → profile-edit-ZNXO6WME.mjs} +1 -1
- package/dist/{promotion-detail-VJB55PJK.mjs → promotion-detail-QC36KXB3.mjs} +3 -3
- package/dist/{promotion-list-TMWKPLMJ.mjs → promotion-list-L22GJE3P.mjs} +4 -4
- package/dist/{refund-reason-list-URYYYEK6.mjs → refund-reason-list-OJYYEYJE.mjs} +8 -8
- package/dist/{region-add-countries-7U4J5RW6.mjs → region-add-countries-2VAVXMJQ.mjs} +4 -4
- package/dist/{region-create-IUGX33M5.mjs → region-create-NA7Y2LN4.mjs} +4 -4
- package/dist/{region-detail-D3JBW34A.mjs → region-detail-3BARMXUE.mjs} +4 -4
- package/dist/{region-list-JAQXIBYD.mjs → region-list-V4R2REMH.mjs} +4 -4
- package/dist/{reservation-list-2DN3YHIJ.mjs → reservation-list-B47DXTA7.mjs} +5 -5
- package/dist/{return-reason-list-IFFIDA5O.mjs → return-reason-list-SCBGTOEI.mjs} +10 -10
- package/dist/{sales-channel-add-products-VH5T3GDA.mjs → sales-channel-add-products-F7YV4MO5.mjs} +4 -4
- package/dist/{sales-channel-detail-I2ZHVXMG.mjs → sales-channel-detail-MXIPZCGA.mjs} +4 -4
- package/dist/{sales-channel-list-3FV4S2NN.mjs → sales-channel-list-RLGL7FM3.mjs} +5 -5
- package/dist/{shipping-option-type-list-ZMZMXFME.mjs → shipping-option-type-list-DIOX7VG7.mjs} +5 -5
- package/dist/{shipping-profiles-list-H3CBZKRH.mjs → shipping-profiles-list-WRPIJBZZ.mjs} +4 -4
- package/dist/{store-add-currencies-ZFS3WZHG.mjs → store-add-currencies-OX2WXFMS.mjs} +4 -4
- package/dist/{store-add-locales-IZOZP5C6.mjs → store-add-locales-VJ4RJ7UI.mjs} +4 -4
- package/dist/{store-detail-4IBAEVSD.mjs → store-detail-JSNPOB2F.mjs} +4 -4
- package/dist/{tax-region-detail-O2T7BI3V.mjs → tax-region-detail-2AE2EFI3.mjs} +13 -13
- package/dist/{tax-region-province-detail-2W7RXAM5.mjs → tax-region-province-detail-4ERSEQFF.mjs} +13 -13
- package/dist/{tax-region-tax-override-create-7IM4ZVPH.mjs → tax-region-tax-override-create-PHCGEF7V.mjs} +11 -11
- package/dist/{tax-region-tax-override-edit-3ZT5IZYR.mjs → tax-region-tax-override-edit-SMRPSILC.mjs} +12 -12
- package/dist/{translation-list-IAKEB7MY.mjs → translation-list-S5Z6PG2R.mjs} +8 -5
- package/dist/translations-edit-HUNKY7CO.mjs +708 -0
- package/dist/{user-invite-XB635N26.mjs → user-invite-GAGIM5DO.mjs} +4 -4
- package/dist/{user-list-YYUOQKQY.mjs → user-list-YTZQNYSO.mjs} +4 -4
- package/dist/{workflow-execution-list-IZVF2XMJ.mjs → workflow-execution-list-C3EJMVSZ.mjs} +4 -4
- package/package.json +9 -9
- package/src/components/data-grid/components/data-grid-cell-container.tsx +16 -4
- package/src/components/data-grid/components/data-grid-readonly-cell.tsx +16 -3
- package/src/components/data-grid/components/data-grid-root.tsx +19 -4
- package/src/components/data-grid/components/data-grid-text-cell.tsx +79 -9
- package/src/i18n/languages.ts +7 -0
- package/src/i18n/translations/index.ts +4 -0
- package/src/i18n/translations/zhTW.json +3249 -0
- package/src/routes/translations/translation-list/translation-list.tsx +9 -8
- package/src/routes/translations/translations-edit/components/translations-edit-form/translations-edit-form.tsx +388 -90
- package/src/routes/translations/translations-edit/translations-edit.tsx +0 -1
- package/dist/translations-edit-QKLE4L5B.mjs +0 -458
|
@@ -45,7 +45,8 @@ export const TranslationList = () => {
|
|
|
45
45
|
entity_types: Object.keys(translatable_fields ?? {}),
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
|
-
enabled:
|
|
48
|
+
enabled:
|
|
49
|
+
!!translatable_fields && !!store && store.supported_locales?.length > 0,
|
|
49
50
|
}
|
|
50
51
|
)
|
|
51
52
|
|
|
@@ -56,7 +57,7 @@ export const TranslationList = () => {
|
|
|
56
57
|
const hasLocales = (store?.supported_locales ?? []).length > 0
|
|
57
58
|
|
|
58
59
|
const translatableEntities: TranslatableEntity[] = useMemo(() => {
|
|
59
|
-
if (!translatable_fields
|
|
60
|
+
if (!translatable_fields) {
|
|
60
61
|
return []
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -66,7 +67,10 @@ export const TranslationList = () => {
|
|
|
66
67
|
!["product_option", "product_option_value"].includes(entity)
|
|
67
68
|
)
|
|
68
69
|
.map(([entity, fields]) => {
|
|
69
|
-
const entityStatistics = statistics[entity]
|
|
70
|
+
const entityStatistics = statistics?.[entity] ?? {
|
|
71
|
+
translated: 0,
|
|
72
|
+
expected: 0,
|
|
73
|
+
}
|
|
70
74
|
|
|
71
75
|
return {
|
|
72
76
|
label: entity
|
|
@@ -85,9 +89,8 @@ export const TranslationList = () => {
|
|
|
85
89
|
!!store &&
|
|
86
90
|
!isPending &&
|
|
87
91
|
!isTranslationSettingsPending &&
|
|
88
|
-
!isTranslationStatisticsPending &&
|
|
89
92
|
!!translatable_fields &&
|
|
90
|
-
!!statistics
|
|
93
|
+
((!!statistics && !isTranslationStatisticsPending) || !hasLocales)
|
|
91
94
|
|
|
92
95
|
if (!isReady) {
|
|
93
96
|
return <TwoColumnPageSkeleton sidebarSections={2} />
|
|
@@ -122,9 +125,7 @@ export const TranslationList = () => {
|
|
|
122
125
|
) ?? []
|
|
123
126
|
}
|
|
124
127
|
></ActiveLocalesSection>
|
|
125
|
-
{statistics
|
|
126
|
-
<TranslationsCompletionSection statistics={statistics} />
|
|
127
|
-
)}
|
|
128
|
+
<TranslationsCompletionSection statistics={statistics ?? {}} />
|
|
128
129
|
</TwoColumnPage.Sidebar>
|
|
129
130
|
</TwoColumnPage>
|
|
130
131
|
)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { zodResolver } from "@hookform/resolvers/zod"
|
|
2
2
|
import { AdminStoreLocale, HttpTypes } from "@medusajs/types"
|
|
3
|
-
import { Button,
|
|
3
|
+
import { Button, Prompt, Select, toast } from "@medusajs/ui"
|
|
4
4
|
import { ColumnDef } from "@tanstack/react-table"
|
|
5
|
-
import { useMemo, useRef } from "react"
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
6
6
|
import { useForm } from "react-hook-form"
|
|
7
7
|
import { useTranslation } from "react-i18next"
|
|
8
8
|
import { z } from "zod"
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
} from "../../../../../components/modals"
|
|
18
18
|
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
|
19
19
|
import { useBatchTranslations } from "../../../../../hooks/api/translations"
|
|
20
|
-
import { useDocumentDirection } from "../../../../../hooks/use-document-direction"
|
|
21
20
|
|
|
22
21
|
/**
|
|
23
22
|
* Schema for a single locale translation.
|
|
@@ -183,29 +182,119 @@ function transformToBatchPayload(
|
|
|
183
182
|
return payload
|
|
184
183
|
}
|
|
185
184
|
|
|
185
|
+
function hasLocaleChanges(
|
|
186
|
+
currentState: TranslationsFormSchema,
|
|
187
|
+
initialState: TranslationsFormSchema,
|
|
188
|
+
localeCode: string
|
|
189
|
+
): boolean {
|
|
190
|
+
for (const [entityId, entityData] of Object.entries(currentState.entities)) {
|
|
191
|
+
const currentLocale = entityData.locales[localeCode]
|
|
192
|
+
const initialLocale = initialState.entities[entityId]?.locales[localeCode]
|
|
193
|
+
|
|
194
|
+
if (!currentLocale || !initialLocale) {
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const [fieldName, fieldValue] of Object.entries(
|
|
199
|
+
currentLocale.fields
|
|
200
|
+
)) {
|
|
201
|
+
const initialValue = initialLocale.fields[fieldName] ?? ""
|
|
202
|
+
const currentValue = fieldValue ?? ""
|
|
203
|
+
|
|
204
|
+
if (currentValue !== initialValue) {
|
|
205
|
+
return true
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return false
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function transformSingleLocaleToBatchPayload(
|
|
214
|
+
currentState: TranslationsFormSchema,
|
|
215
|
+
initialState: TranslationsFormSchema,
|
|
216
|
+
entityType: string,
|
|
217
|
+
localeCode: string
|
|
218
|
+
): Required<HttpTypes.AdminBatchTranslations> {
|
|
219
|
+
const payload: Required<HttpTypes.AdminBatchTranslations> = {
|
|
220
|
+
create: [],
|
|
221
|
+
update: [],
|
|
222
|
+
delete: [],
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (const [entityId, entityData] of Object.entries(currentState.entities)) {
|
|
226
|
+
const localeTranslations = entityData.locales[localeCode]
|
|
227
|
+
if (!localeTranslations) continue
|
|
228
|
+
|
|
229
|
+
const initial = initialState.entities[entityId]?.locales[localeCode]
|
|
230
|
+
const hasContent = Object.values(localeTranslations.fields).some(
|
|
231
|
+
(v) => v !== undefined && v.trim() !== ""
|
|
232
|
+
)
|
|
233
|
+
const hadContent =
|
|
234
|
+
initial &&
|
|
235
|
+
Object.values(initial.fields).some(
|
|
236
|
+
(v) => v !== undefined && v.trim() !== ""
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if (!localeTranslations.id && hasContent) {
|
|
240
|
+
payload.create.push({
|
|
241
|
+
reference_id: entityId,
|
|
242
|
+
reference: entityType,
|
|
243
|
+
locale_code: localeTranslations.locale_code,
|
|
244
|
+
translations: localeTranslations.fields,
|
|
245
|
+
})
|
|
246
|
+
} else if (localeTranslations.id && hasContent) {
|
|
247
|
+
const hasChanged =
|
|
248
|
+
!initial ||
|
|
249
|
+
JSON.stringify(localeTranslations.fields) !==
|
|
250
|
+
JSON.stringify(initial.fields)
|
|
251
|
+
|
|
252
|
+
if (hasChanged) {
|
|
253
|
+
payload.update.push({
|
|
254
|
+
id: localeTranslations.id,
|
|
255
|
+
translations: localeTranslations.fields,
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
} else if (localeTranslations.id && !hasContent && hadContent) {
|
|
259
|
+
payload.delete.push(localeTranslations.id)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return payload
|
|
264
|
+
}
|
|
265
|
+
|
|
186
266
|
const columnHelper = createDataGridHelper<
|
|
187
267
|
TranslationRow,
|
|
188
268
|
TranslationsFormSchema
|
|
189
269
|
>()
|
|
190
270
|
|
|
271
|
+
const FIELD_COLUMN_WIDTH = 150
|
|
272
|
+
|
|
191
273
|
function useTranslationsGridColumns({
|
|
192
274
|
entities,
|
|
193
275
|
translatableFields,
|
|
194
276
|
availableLocales,
|
|
195
|
-
|
|
277
|
+
selectedLocale,
|
|
278
|
+
dynamicColumnWidth,
|
|
196
279
|
}: {
|
|
197
280
|
entities: { id: string; [key: string]: string }[]
|
|
198
281
|
translatableFields: string[]
|
|
199
282
|
availableLocales: AdminStoreLocale[]
|
|
200
|
-
|
|
283
|
+
selectedLocale: string
|
|
284
|
+
dynamicColumnWidth: number
|
|
201
285
|
}) {
|
|
202
286
|
const { t } = useTranslation()
|
|
203
287
|
|
|
204
288
|
const columns: ColumnDef<TranslationRow>[] = useMemo(() => {
|
|
205
|
-
|
|
289
|
+
const selectedLocaleData = availableLocales.find(
|
|
290
|
+
(l) => l.locale_code === selectedLocale
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
const baseColumns = [
|
|
206
294
|
columnHelper.column({
|
|
207
295
|
id: "field",
|
|
208
296
|
name: "field",
|
|
297
|
+
size: FIELD_COLUMN_WIDTH,
|
|
209
298
|
header: undefined,
|
|
210
299
|
cell: (context) => {
|
|
211
300
|
const row = context.row.original
|
|
@@ -233,7 +322,7 @@ function useTranslationsGridColumns({
|
|
|
233
322
|
columnHelper.column({
|
|
234
323
|
id: "original",
|
|
235
324
|
name: "original",
|
|
236
|
-
size:
|
|
325
|
+
size: dynamicColumnWidth,
|
|
237
326
|
header: t("general.original"),
|
|
238
327
|
disableHiding: true,
|
|
239
328
|
cell: (context) => {
|
|
@@ -253,41 +342,29 @@ function useTranslationsGridColumns({
|
|
|
253
342
|
}
|
|
254
343
|
|
|
255
344
|
return (
|
|
256
|
-
<DataGrid.ReadonlyCell context={context}>
|
|
345
|
+
<DataGrid.ReadonlyCell context={context} isMultiLine>
|
|
257
346
|
{entity[row.field_name]}
|
|
258
347
|
</DataGrid.ReadonlyCell>
|
|
259
348
|
)
|
|
260
349
|
},
|
|
261
350
|
}),
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
if (selectedLocaleData) {
|
|
354
|
+
baseColumns.push(
|
|
355
|
+
columnHelper.column({
|
|
356
|
+
id: selectedLocaleData.locale_code,
|
|
357
|
+
name: selectedLocaleData.locale.name,
|
|
358
|
+
size: dynamicColumnWidth,
|
|
359
|
+
header: () => selectedLocaleData.locale.name,
|
|
268
360
|
cell: (context) => {
|
|
269
361
|
const row = context.row.original
|
|
270
362
|
|
|
271
363
|
if (isEntityRow(row)) {
|
|
272
|
-
return
|
|
273
|
-
<DataGrid.ReadonlyCell
|
|
274
|
-
context={context}
|
|
275
|
-
></DataGrid.ReadonlyCell>
|
|
276
|
-
)
|
|
364
|
+
return <DataGrid.ReadonlyCell context={context} isMultiLine />
|
|
277
365
|
}
|
|
278
366
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (useModal) {
|
|
282
|
-
return (
|
|
283
|
-
<DataGrid.ExpandableTextCell
|
|
284
|
-
context={context}
|
|
285
|
-
fieldLabel={row.field_name}
|
|
286
|
-
/>
|
|
287
|
-
)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return <DataGrid.TextCell context={context} />
|
|
367
|
+
return <DataGrid.TextCell context={context} isMultiLine />
|
|
291
368
|
},
|
|
292
369
|
field: (context) => {
|
|
293
370
|
const row = context.row.original
|
|
@@ -296,13 +373,22 @@ function useTranslationsGridColumns({
|
|
|
296
373
|
return null
|
|
297
374
|
}
|
|
298
375
|
|
|
299
|
-
return `entities.${row.reference_id}.locales.${
|
|
376
|
+
return `entities.${row.reference_id}.locales.${selectedLocaleData.locale_code}.fields.${row.field_name}`
|
|
300
377
|
},
|
|
301
378
|
type: "text",
|
|
302
379
|
})
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
380
|
+
)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return baseColumns
|
|
384
|
+
}, [
|
|
385
|
+
t,
|
|
386
|
+
translatableFields,
|
|
387
|
+
availableLocales,
|
|
388
|
+
selectedLocale,
|
|
389
|
+
entities,
|
|
390
|
+
dynamicColumnWidth,
|
|
391
|
+
])
|
|
306
392
|
|
|
307
393
|
return columns
|
|
308
394
|
}
|
|
@@ -313,7 +399,6 @@ type TranslationsEditFormProps = {
|
|
|
313
399
|
entityType: string
|
|
314
400
|
availableLocales: AdminStoreLocale[]
|
|
315
401
|
translatableFields: string[]
|
|
316
|
-
modalFields?: string[]
|
|
317
402
|
fetchNextPage: () => void
|
|
318
403
|
hasNextPage: boolean
|
|
319
404
|
isFetchingNextPage: boolean
|
|
@@ -326,7 +411,6 @@ export const TranslationsEditForm = ({
|
|
|
326
411
|
entityType,
|
|
327
412
|
availableLocales,
|
|
328
413
|
translatableFields,
|
|
329
|
-
modalFields = [],
|
|
330
414
|
fetchNextPage,
|
|
331
415
|
hasNextPage,
|
|
332
416
|
isFetchingNextPage,
|
|
@@ -334,7 +418,36 @@ export const TranslationsEditForm = ({
|
|
|
334
418
|
}: TranslationsEditFormProps) => {
|
|
335
419
|
const { t } = useTranslation()
|
|
336
420
|
const { handleSuccess, setCloseOnEscape } = useRouteModal()
|
|
337
|
-
|
|
421
|
+
|
|
422
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
423
|
+
const [dynamicColumnWidth, setDynamicColumnWidth] = useState(400)
|
|
424
|
+
|
|
425
|
+
useEffect(() => {
|
|
426
|
+
const calculateColumnWidth = () => {
|
|
427
|
+
if (containerRef.current) {
|
|
428
|
+
const containerWidth = containerRef.current.offsetWidth
|
|
429
|
+
const availableWidth = containerWidth - FIELD_COLUMN_WIDTH - 12
|
|
430
|
+
const columnWidth = Math.max(300, Math.floor(availableWidth / 2))
|
|
431
|
+
setDynamicColumnWidth(columnWidth)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
calculateColumnWidth()
|
|
436
|
+
|
|
437
|
+
const resizeObserver = new ResizeObserver(calculateColumnWidth)
|
|
438
|
+
if (containerRef.current) {
|
|
439
|
+
resizeObserver.observe(containerRef.current)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return () => resizeObserver.disconnect()
|
|
443
|
+
}, [])
|
|
444
|
+
|
|
445
|
+
const [selectedLocale, setSelectedLocale] = useState<string>(
|
|
446
|
+
availableLocales[0]?.locale_code ?? ""
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
const [showUnsavedPrompt, setShowUnsavedPrompt] = useState(false)
|
|
450
|
+
const [pendingLocale, setPendingLocale] = useState<string | null>(null)
|
|
338
451
|
|
|
339
452
|
const entities = useMemo(() => references, [references])
|
|
340
453
|
const totalCount = useMemo(
|
|
@@ -363,6 +476,131 @@ export const TranslationsEditForm = ({
|
|
|
363
476
|
|
|
364
477
|
const { mutateAsync, isPending } = useBatchTranslations(entityType)
|
|
365
478
|
|
|
479
|
+
const handleLocaleChange = useCallback(
|
|
480
|
+
(newLocale: string) => {
|
|
481
|
+
if (newLocale === selectedLocale) {
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const currentValues = form.getValues()
|
|
486
|
+
const hasChanges = hasLocaleChanges(
|
|
487
|
+
currentValues,
|
|
488
|
+
initialState.current,
|
|
489
|
+
selectedLocale
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
if (hasChanges) {
|
|
493
|
+
setPendingLocale(newLocale)
|
|
494
|
+
setShowUnsavedPrompt(true)
|
|
495
|
+
} else {
|
|
496
|
+
setSelectedLocale(newLocale)
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
[selectedLocale, form]
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
const saveCurrentLocale = useCallback(async () => {
|
|
503
|
+
const currentValues = form.getValues()
|
|
504
|
+
const payload = transformSingleLocaleToBatchPayload(
|
|
505
|
+
currentValues,
|
|
506
|
+
initialState.current,
|
|
507
|
+
entityType,
|
|
508
|
+
selectedLocale
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
if (
|
|
512
|
+
payload.create.length === 0 &&
|
|
513
|
+
payload.update.length === 0 &&
|
|
514
|
+
payload.delete.length === 0
|
|
515
|
+
) {
|
|
516
|
+
return true
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
try {
|
|
520
|
+
const BATCH_SIZE = 150
|
|
521
|
+
const totalItems =
|
|
522
|
+
payload.create.length + payload.update.length + payload.delete.length
|
|
523
|
+
const batchCount = Math.ceil(totalItems / BATCH_SIZE)
|
|
524
|
+
|
|
525
|
+
for (let i = 0; i < batchCount; i++) {
|
|
526
|
+
let currentBatchAvailable = BATCH_SIZE
|
|
527
|
+
const currentBatch: HttpTypes.AdminBatchTranslations = {
|
|
528
|
+
create: [],
|
|
529
|
+
update: [],
|
|
530
|
+
delete: [],
|
|
531
|
+
}
|
|
532
|
+
if (payload.create.length > 0) {
|
|
533
|
+
currentBatch.create = payload.create.splice(0, currentBatchAvailable)
|
|
534
|
+
currentBatchAvailable -= currentBatch.create.length
|
|
535
|
+
}
|
|
536
|
+
if (payload.update.length > 0) {
|
|
537
|
+
currentBatch.update = payload.update.splice(0, currentBatchAvailable)
|
|
538
|
+
currentBatchAvailable -= currentBatch.update.length
|
|
539
|
+
}
|
|
540
|
+
if (payload.delete.length > 0) {
|
|
541
|
+
currentBatch.delete = payload.delete.splice(0, currentBatchAvailable)
|
|
542
|
+
currentBatchAvailable -= currentBatch.delete.length
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
await mutateAsync(currentBatch)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const updatedInitialState = { ...initialState.current }
|
|
549
|
+
for (const entityId of Object.keys(currentValues.entities)) {
|
|
550
|
+
if (updatedInitialState.entities[entityId]?.locales[selectedLocale]) {
|
|
551
|
+
updatedInitialState.entities[entityId].locales[selectedLocale] = {
|
|
552
|
+
...currentValues.entities[entityId].locales[selectedLocale],
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
initialState.current = updatedInitialState
|
|
557
|
+
form.reset(currentValues)
|
|
558
|
+
|
|
559
|
+
return true
|
|
560
|
+
} catch (error) {
|
|
561
|
+
toast.error(
|
|
562
|
+
error instanceof Error ? error.message : "Failed to save translations"
|
|
563
|
+
)
|
|
564
|
+
return false
|
|
565
|
+
}
|
|
566
|
+
}, [form, entityType, selectedLocale, mutateAsync])
|
|
567
|
+
|
|
568
|
+
const handleSaveAndSwitch = useCallback(async () => {
|
|
569
|
+
const success = await saveCurrentLocale()
|
|
570
|
+
if (success && pendingLocale) {
|
|
571
|
+
toast.success(
|
|
572
|
+
t("translations.edit.localeChangesSaved", {
|
|
573
|
+
defaultValue: "Changes saved successfully",
|
|
574
|
+
})
|
|
575
|
+
)
|
|
576
|
+
setSelectedLocale(pendingLocale)
|
|
577
|
+
}
|
|
578
|
+
setShowUnsavedPrompt(false)
|
|
579
|
+
setPendingLocale(null)
|
|
580
|
+
}, [saveCurrentLocale, pendingLocale, t])
|
|
581
|
+
|
|
582
|
+
const handleCancelSwitch = useCallback(() => {
|
|
583
|
+
setShowUnsavedPrompt(false)
|
|
584
|
+
setPendingLocale(null)
|
|
585
|
+
}, [])
|
|
586
|
+
|
|
587
|
+
const handleSave = useCallback(
|
|
588
|
+
async (closeOnSuccess: boolean = false) => {
|
|
589
|
+
const success = await saveCurrentLocale()
|
|
590
|
+
if (success) {
|
|
591
|
+
toast.success(
|
|
592
|
+
t("translations.edit.successToast", {
|
|
593
|
+
defaultValue: "Translations updated successfully",
|
|
594
|
+
})
|
|
595
|
+
)
|
|
596
|
+
if (closeOnSuccess) {
|
|
597
|
+
handleSuccess()
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
[saveCurrentLocale, t, handleSuccess]
|
|
602
|
+
)
|
|
603
|
+
|
|
366
604
|
const handleSubmit = form.handleSubmit(async (values) => {
|
|
367
605
|
const payload = transformToBatchPayload(
|
|
368
606
|
values,
|
|
@@ -428,65 +666,125 @@ export const TranslationsEditForm = ({
|
|
|
428
666
|
entities,
|
|
429
667
|
translatableFields,
|
|
430
668
|
availableLocales,
|
|
431
|
-
|
|
669
|
+
selectedLocale,
|
|
670
|
+
dynamicColumnWidth,
|
|
432
671
|
})
|
|
433
672
|
|
|
673
|
+
const selectedLocaleDisplay = availableLocales.find(
|
|
674
|
+
(l) => l.locale_code === selectedLocale
|
|
675
|
+
)?.locale.name
|
|
676
|
+
|
|
434
677
|
return (
|
|
435
678
|
<RouteFocusModal.Form form={form}>
|
|
436
|
-
<KeyboundForm
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
{entityType
|
|
447
|
-
.split("_")
|
|
448
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
449
|
-
.join(" ")}
|
|
450
|
-
</ProgressTabs.Trigger>
|
|
451
|
-
</ProgressTabs.List>
|
|
452
|
-
</div>
|
|
453
|
-
</RouteFocusModal.Header>
|
|
454
|
-
<RouteFocusModal.Body className="size-full overflow-hidden">
|
|
455
|
-
<ProgressTabs.Content
|
|
456
|
-
value={entityType}
|
|
457
|
-
className="size-full overflow-y-auto"
|
|
679
|
+
<KeyboundForm
|
|
680
|
+
onSubmit={handleSubmit}
|
|
681
|
+
className="flex h-full flex-col overflow-hidden"
|
|
682
|
+
>
|
|
683
|
+
<RouteFocusModal.Header>
|
|
684
|
+
<div className="-my-2 flex w-full items-center justify-between border-l px-4">
|
|
685
|
+
<Select
|
|
686
|
+
value={selectedLocale}
|
|
687
|
+
onValueChange={handleLocaleChange}
|
|
688
|
+
size="small"
|
|
458
689
|
>
|
|
459
|
-
<
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
<
|
|
477
|
-
<
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
690
|
+
<Select.Trigger className="bg-ui-bg-base w-[200px]">
|
|
691
|
+
<Select.Value>{selectedLocaleDisplay}</Select.Value>
|
|
692
|
+
</Select.Trigger>
|
|
693
|
+
<Select.Content>
|
|
694
|
+
{availableLocales.map((locale) => (
|
|
695
|
+
<Select.Item
|
|
696
|
+
key={locale.locale_code}
|
|
697
|
+
value={locale.locale_code}
|
|
698
|
+
>
|
|
699
|
+
{locale.locale.name}
|
|
700
|
+
</Select.Item>
|
|
701
|
+
))}
|
|
702
|
+
</Select.Content>
|
|
703
|
+
</Select>
|
|
704
|
+
</div>
|
|
705
|
+
</RouteFocusModal.Header>
|
|
706
|
+
<RouteFocusModal.Body className="size-full overflow-hidden">
|
|
707
|
+
<div ref={containerRef} className="size-full">
|
|
708
|
+
<DataGrid
|
|
709
|
+
columns={columns}
|
|
710
|
+
data={rows}
|
|
711
|
+
getSubRows={(row) => {
|
|
712
|
+
if (isEntityRow(row)) {
|
|
713
|
+
return row.subRows
|
|
714
|
+
}
|
|
715
|
+
}}
|
|
716
|
+
state={form}
|
|
717
|
+
onEditingChange={(editing) => setCloseOnEscape(!editing)}
|
|
718
|
+
totalRowCount={totalCount}
|
|
719
|
+
onFetchMore={fetchNextPage}
|
|
720
|
+
isFetchingMore={isFetchingNextPage}
|
|
721
|
+
hasNextPage={hasNextPage}
|
|
722
|
+
/>
|
|
723
|
+
</div>
|
|
724
|
+
</RouteFocusModal.Body>
|
|
725
|
+
<RouteFocusModal.Footer>
|
|
726
|
+
<div className="flex items-center justify-end gap-x-2">
|
|
727
|
+
<RouteFocusModal.Close asChild>
|
|
728
|
+
<Button size="small" variant="secondary">
|
|
729
|
+
{t("actions.cancel")}
|
|
485
730
|
</Button>
|
|
486
|
-
</
|
|
487
|
-
|
|
488
|
-
|
|
731
|
+
</RouteFocusModal.Close>
|
|
732
|
+
<Button
|
|
733
|
+
size="small"
|
|
734
|
+
type="button"
|
|
735
|
+
variant="secondary"
|
|
736
|
+
onClick={() => handleSave(false)}
|
|
737
|
+
isLoading={isPending}
|
|
738
|
+
>
|
|
739
|
+
{t("actions.saveChanges", { defaultValue: "Save changes" })}
|
|
740
|
+
</Button>
|
|
741
|
+
<Button
|
|
742
|
+
size="small"
|
|
743
|
+
type="button"
|
|
744
|
+
onClick={() => handleSave(true)}
|
|
745
|
+
isLoading={isPending}
|
|
746
|
+
>
|
|
747
|
+
{t("actions.saveAndClose", { defaultValue: "Save and close" })}
|
|
748
|
+
</Button>
|
|
749
|
+
</div>
|
|
750
|
+
</RouteFocusModal.Footer>
|
|
489
751
|
</KeyboundForm>
|
|
752
|
+
|
|
753
|
+
<Prompt open={showUnsavedPrompt} variant="confirmation">
|
|
754
|
+
<Prompt.Content>
|
|
755
|
+
<Prompt.Header>
|
|
756
|
+
<Prompt.Title>
|
|
757
|
+
{t("translations.unsavedChanges.title", {
|
|
758
|
+
defaultValue: "Unsaved changes",
|
|
759
|
+
})}
|
|
760
|
+
</Prompt.Title>
|
|
761
|
+
<Prompt.Description>
|
|
762
|
+
{t("translations.unsavedChanges.description", {
|
|
763
|
+
defaultValue:
|
|
764
|
+
"You have unsaved changes for this locale. Save them before switching?",
|
|
765
|
+
})}
|
|
766
|
+
</Prompt.Description>
|
|
767
|
+
</Prompt.Header>
|
|
768
|
+
<Prompt.Footer>
|
|
769
|
+
<Button
|
|
770
|
+
size="small"
|
|
771
|
+
variant="secondary"
|
|
772
|
+
onClick={handleCancelSwitch}
|
|
773
|
+
type="button"
|
|
774
|
+
>
|
|
775
|
+
{t("actions.close")}
|
|
776
|
+
</Button>
|
|
777
|
+
<Button
|
|
778
|
+
size="small"
|
|
779
|
+
onClick={handleSaveAndSwitch}
|
|
780
|
+
type="button"
|
|
781
|
+
isLoading={isPending}
|
|
782
|
+
>
|
|
783
|
+
{t("actions.saveChanges", { defaultValue: "Save changes" })}
|
|
784
|
+
</Button>
|
|
785
|
+
</Prompt.Footer>
|
|
786
|
+
</Prompt.Content>
|
|
787
|
+
</Prompt>
|
|
490
788
|
</RouteFocusModal.Form>
|
|
491
789
|
)
|
|
492
790
|
}
|
|
@@ -79,7 +79,6 @@ export const TranslationsEdit = () => {
|
|
|
79
79
|
entityType={reference!}
|
|
80
80
|
availableLocales={store?.supported_locales ?? []}
|
|
81
81
|
translatableFields={translatable_fields[reference!]}
|
|
82
|
-
modalFields={translatable_fields[reference!]}
|
|
83
82
|
fetchNextPage={fetchNextPage}
|
|
84
83
|
hasNextPage={hasNextPage}
|
|
85
84
|
isFetchingNextPage={isFetchingNextPage}
|