feat(translation,fulfillment,customer,product,region,tax,core-flows,medusa,types): Implement dynamic translation settings management (#14536)
* Add is_active field to translation_settings model * Types * Workflows * Api layer * Tests * Add changeset * Add comment * Hook to create or deactivate translatable entities on startup * Cleanup old code * Configure translatable option for core entities * Validation step and snake case correction * Cleanup * Tests * Comment in PR * Update changeset * Mock DmlEntity.getTranslatableEntities * Move validation to module service layer * Remove validation from remaining workflow * Return object directly * Type improvements * Remove .only from tests * Apply snakeCase * Fix tests * Fix tests * Remove unnecessary map and use set instead * Fix tests * Comments * Include translatable product properties * Avoid race condition in translations tests * Update test
This commit is contained in:
@@ -5,7 +5,7 @@ import { CustomerGroupCustomer } from "@models"
|
||||
const CustomerGroup = model
|
||||
.define("CustomerGroup", {
|
||||
id: model.id({ prefix: "cusgroup" }).primaryKey(),
|
||||
name: model.text().searchable(),
|
||||
name: model.text().searchable().translatable(),
|
||||
metadata: model.json().nullable(),
|
||||
created_by: model.text().nullable(),
|
||||
customers: model.manyToMany(() => Customer, {
|
||||
|
||||
@@ -4,8 +4,8 @@ import { ShippingOption } from "./shipping-option"
|
||||
|
||||
export const ShippingOptionType = model.define("shipping_option_type", {
|
||||
id: model.id({ prefix: "sotype" }).primaryKey(),
|
||||
label: model.text().searchable(),
|
||||
description: model.text().searchable().nullable(),
|
||||
label: model.text().searchable().translatable(),
|
||||
description: model.text().searchable().translatable().nullable(),
|
||||
code: model.text().searchable(),
|
||||
shipping_options: model.hasMany(() => ShippingOption, {
|
||||
mappedBy: "type",
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ShippingProfile } from "./shipping-profile"
|
||||
export const ShippingOption = model
|
||||
.define("shipping_option", {
|
||||
id: model.id({ prefix: "so" }).primaryKey(),
|
||||
name: model.text().searchable(),
|
||||
name: model.text().searchable().translatable(),
|
||||
price_type: model
|
||||
.enum(ShippingOptionPriceType)
|
||||
.default(ShippingOptionPriceType.FLAT),
|
||||
|
||||
@@ -4,8 +4,8 @@ import Product from "./product"
|
||||
const ProductCategory = model
|
||||
.define("ProductCategory", {
|
||||
id: model.id({ prefix: "pcat" }).primaryKey(),
|
||||
name: model.text().searchable(),
|
||||
description: model.text().searchable().default(""),
|
||||
name: model.text().searchable().translatable(),
|
||||
description: model.text().searchable().translatable().default(""),
|
||||
handle: model.text().searchable(),
|
||||
mpath: model.text(),
|
||||
is_active: model.boolean().default(false),
|
||||
|
||||
@@ -4,7 +4,7 @@ import Product from "./product"
|
||||
const ProductCollection = model
|
||||
.define("ProductCollection", {
|
||||
id: model.id({ prefix: "pcol" }).primaryKey(),
|
||||
title: model.text().searchable(),
|
||||
title: model.text().searchable().translatable(),
|
||||
handle: model.text(),
|
||||
metadata: model.json().nullable(),
|
||||
products: model.hasMany(() => Product, {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ProductOption, ProductVariant } from "./index"
|
||||
const ProductOptionValue = model
|
||||
.define("ProductOptionValue", {
|
||||
id: model.id({ prefix: "optval" }).primaryKey(),
|
||||
value: model.text(),
|
||||
value: model.text().translatable(),
|
||||
metadata: model.json().nullable(),
|
||||
option: model
|
||||
.belongsTo(() => ProductOption, {
|
||||
|
||||
@@ -5,7 +5,7 @@ import ProductOptionValue from "./product-option-value"
|
||||
const ProductOption = model
|
||||
.define("ProductOption", {
|
||||
id: model.id({ prefix: "opt" }).primaryKey(),
|
||||
title: model.text().searchable(),
|
||||
title: model.text().searchable().translatable(),
|
||||
metadata: model.json().nullable(),
|
||||
product: model.belongsTo(() => Product, {
|
||||
mappedBy: "options",
|
||||
|
||||
@@ -6,7 +6,7 @@ const ProductTag = model
|
||||
{ tableName: "product_tag", name: "ProductTag" },
|
||||
{
|
||||
id: model.id({ prefix: "ptag" }).primaryKey(),
|
||||
value: model.text().searchable(),
|
||||
value: model.text().searchable().translatable(),
|
||||
metadata: model.json().nullable(),
|
||||
products: model.manyToMany(() => Product, {
|
||||
mappedBy: "tags",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Product } from "@models"
|
||||
const ProductType = model
|
||||
.define("ProductType", {
|
||||
id: model.id({ prefix: "ptyp" }).primaryKey(),
|
||||
value: model.text().searchable(),
|
||||
value: model.text().searchable().translatable(),
|
||||
metadata: model.json().nullable(),
|
||||
products: model.hasMany(() => Product, {
|
||||
mappedBy: "type",
|
||||
|
||||
@@ -5,7 +5,7 @@ import ProductVariantProductImage from "./product-variant-product-image"
|
||||
const ProductVariant = model
|
||||
.define("ProductVariant", {
|
||||
id: model.id({ prefix: "variant" }).primaryKey(),
|
||||
title: model.text().searchable(),
|
||||
title: model.text().searchable().translatable(),
|
||||
sku: model.text().searchable().nullable(),
|
||||
barcode: model.text().searchable().nullable(),
|
||||
ean: model.text().searchable().nullable(),
|
||||
@@ -15,7 +15,7 @@ const ProductVariant = model
|
||||
hs_code: model.text().nullable(),
|
||||
origin_country: model.text().nullable(),
|
||||
mid_code: model.text().nullable(),
|
||||
material: model.text().nullable(),
|
||||
material: model.text().translatable().nullable(),
|
||||
weight: model.number().nullable(),
|
||||
length: model.number().nullable(),
|
||||
height: model.number().nullable(),
|
||||
|
||||
@@ -11,10 +11,10 @@ import ProductVariant from "./product-variant"
|
||||
const Product = model
|
||||
.define("Product", {
|
||||
id: model.id({ prefix: "prod" }).primaryKey(),
|
||||
title: model.text().searchable(),
|
||||
title: model.text().searchable().translatable(),
|
||||
handle: model.text(),
|
||||
subtitle: model.text().searchable().nullable(),
|
||||
description: model.text().searchable().nullable(),
|
||||
subtitle: model.text().searchable().translatable().nullable(),
|
||||
description: model.text().searchable().translatable().nullable(),
|
||||
is_giftcard: model.boolean().default(false),
|
||||
status: model
|
||||
.enum(ProductUtils.ProductStatus)
|
||||
@@ -27,7 +27,7 @@ const Product = model
|
||||
origin_country: model.text().nullable(),
|
||||
hs_code: model.text().nullable(),
|
||||
mid_code: model.text().nullable(),
|
||||
material: model.text().nullable(),
|
||||
material: model.text().translatable().nullable(),
|
||||
discountable: model.boolean().default(true),
|
||||
external_id: model.text().nullable(),
|
||||
metadata: model.json().nullable(),
|
||||
|
||||
@@ -3,7 +3,7 @@ import RegionCountry from "./country"
|
||||
|
||||
export default model.define("region", {
|
||||
id: model.id({ prefix: "reg" }).primaryKey(),
|
||||
name: model.text().searchable(),
|
||||
name: model.text().searchable().translatable(),
|
||||
currency_code: model.text().searchable(),
|
||||
automatic_taxes: model.boolean().default(true),
|
||||
countries: model.hasMany(() => RegionCountry),
|
||||
|
||||
@@ -7,7 +7,7 @@ const TaxRate = model
|
||||
id: model.id({ prefix: "txr" }).primaryKey(),
|
||||
rate: model.float().nullable(),
|
||||
code: model.text().searchable(),
|
||||
name: model.text().searchable(),
|
||||
name: model.text().searchable().translatable(),
|
||||
is_default: model.boolean().default(false),
|
||||
is_combinable: model.boolean().default(false),
|
||||
tax_region: model.belongsTo(() => TaxRegion, {
|
||||
|
||||
@@ -1,15 +1,42 @@
|
||||
import { ITranslationModuleService } from "@medusajs/framework/types"
|
||||
import { Module, Modules } from "@medusajs/framework/utils"
|
||||
import { DmlEntity, Module, Modules } from "@medusajs/framework/utils"
|
||||
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
|
||||
import TranslationModuleService from "@services/translation-module"
|
||||
import { createLocaleFixture, createTranslationFixture } from "../__fixtures__"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
// Set up the mock before module initialization
|
||||
let mockGetTranslatableEntities: jest.SpyInstance
|
||||
|
||||
moduleIntegrationTestRunner<ITranslationModuleService>({
|
||||
moduleName: Modules.TRANSLATION,
|
||||
hooks: {
|
||||
beforeModuleInit: async () => {
|
||||
mockGetTranslatableEntities = jest.spyOn(
|
||||
DmlEntity,
|
||||
"getTranslatableEntities"
|
||||
)
|
||||
mockGetTranslatableEntities.mockReturnValue([
|
||||
{
|
||||
entity: "Product",
|
||||
fields: ["title", "description", "subtitle", "material"],
|
||||
},
|
||||
{ entity: "ProductVariant", fields: ["title", "material"] },
|
||||
{ entity: "ProductCategory", fields: ["name"] },
|
||||
])
|
||||
},
|
||||
},
|
||||
testSuite: ({ service }) => {
|
||||
describe("Translation Module Service", () => {
|
||||
beforeEach(async () => {
|
||||
await service.__hooks?.onApplicationStart?.().catch(() => {})
|
||||
})
|
||||
afterAll(() => {
|
||||
// Restore the mock after all tests complete
|
||||
mockGetTranslatableEntities.mockRestore()
|
||||
})
|
||||
|
||||
it(`should export the appropriate linkable configuration`, () => {
|
||||
const linkable = Module(Modules.TRANSLATION, {
|
||||
service: TranslationModuleService,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import "./types"
|
||||
import { Module } from "@medusajs/framework/utils"
|
||||
import TranslationModuleService from "@services/translation-module"
|
||||
import loadConfig from "./loaders/config"
|
||||
import loadDefaults from "./loaders/defaults"
|
||||
|
||||
export const TRANSLATION_MODULE = "translation"
|
||||
|
||||
export default Module(TRANSLATION_MODULE, {
|
||||
service: TranslationModuleService,
|
||||
loaders: [loadDefaults, loadConfig],
|
||||
loaders: [loadDefaults],
|
||||
})
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import {
|
||||
LoaderOptions,
|
||||
Logger,
|
||||
ModulesSdkTypes,
|
||||
} from "@medusajs/framework/types"
|
||||
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
|
||||
import { TRANSLATABLE_FIELDS_CONFIG_KEY } from "@utils/constants"
|
||||
import { asValue } from "awilix"
|
||||
import { translatableFieldsConfig } from "../utils/translatable-fields"
|
||||
import Settings from "@models/settings"
|
||||
import type { TranslationModuleOptions } from "../types"
|
||||
|
||||
export default async ({
|
||||
container,
|
||||
options,
|
||||
}: LoaderOptions<TranslationModuleOptions>): Promise<void> => {
|
||||
const logger =
|
||||
container.resolve<Logger>(ContainerRegistrationKeys.LOGGER) ?? console
|
||||
const settingsService: ModulesSdkTypes.IMedusaInternalService<
|
||||
typeof Settings
|
||||
> = container.resolve("translationSettingsService")
|
||||
|
||||
const mergedConfig: Record<string, string[]> = translatableFieldsConfig
|
||||
|
||||
const userProvidedFields = options?.entities ?? []
|
||||
for (const field of userProvidedFields) {
|
||||
mergedConfig[field.type] ??= []
|
||||
mergedConfig[field.type] = Array.from(
|
||||
new Set([...(mergedConfig[field.type] ?? []), ...field.fields])
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const existingSettings = await settingsService.list(
|
||||
{},
|
||||
{ select: ["id", "entity_type"] }
|
||||
)
|
||||
const existingByEntityType = new Map(
|
||||
existingSettings.map((s) => [s.entity_type, s.id])
|
||||
)
|
||||
|
||||
const settingsToUpsert = Object.entries(mergedConfig).map(
|
||||
([entityType, fields]) => {
|
||||
const existingId = existingByEntityType.get(entityType)
|
||||
return existingId
|
||||
? { id: existingId, entity_type: entityType, fields }
|
||||
: { entity_type: entityType, fields }
|
||||
}
|
||||
)
|
||||
|
||||
const resp = await settingsService.upsert(settingsToUpsert)
|
||||
logger.debug(`Loaded ${resp.length} translation settings`)
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`Failed to load translation settings, skipping loader. Original error: ${error.message}`
|
||||
)
|
||||
}
|
||||
|
||||
container.register(TRANSLATABLE_FIELDS_CONFIG_KEY, asValue(mergedConfig))
|
||||
}
|
||||
@@ -293,6 +293,16 @@
|
||||
"nullable": false,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"is_active": {
|
||||
"name": "is_active",
|
||||
"type": "boolean",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": false,
|
||||
"default": "true",
|
||||
"mappedType": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Migration } from "@medusajs/framework/mikro-orm/migrations";
|
||||
|
||||
export class Migration20260108122757 extends Migration {
|
||||
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(`alter table if exists "translation_settings" add column if not exists "is_active" boolean not null default true;`);
|
||||
}
|
||||
|
||||
override async down(): Promise<void> {
|
||||
this.addSql(`alter table if exists "translation_settings" drop column if exists "is_active";`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,10 @@ const Settings = model
|
||||
* ["title", "description", "material"]
|
||||
*/
|
||||
fields: model.json(),
|
||||
/**
|
||||
* Wether the entity translatable status is enabled.
|
||||
*/
|
||||
is_active: model.boolean().default(true),
|
||||
})
|
||||
.indexes([
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ import { raw } from "@medusajs/framework/mikro-orm/core"
|
||||
import {
|
||||
Context,
|
||||
CreateTranslationDTO,
|
||||
CreateTranslationSettingsDTO,
|
||||
DAL,
|
||||
FilterableTranslationProps,
|
||||
FindConfig,
|
||||
@@ -9,21 +10,25 @@ import {
|
||||
LocaleDTO,
|
||||
ModulesSdkTypes,
|
||||
TranslationTypes,
|
||||
UpdateTranslationSettingsDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { SqlEntityManager } from "@medusajs/framework/mikro-orm/postgresql"
|
||||
import {
|
||||
arrayDifference,
|
||||
DmlEntity,
|
||||
EmitEvents,
|
||||
InjectManager,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
MedusaErrorTypes,
|
||||
MedusaService,
|
||||
normalizeLocale,
|
||||
toSnakeCase,
|
||||
} from "@medusajs/framework/utils"
|
||||
import Locale from "@models/locale"
|
||||
import Translation from "@models/translation"
|
||||
import Settings from "@models/settings"
|
||||
import { computeTranslatedFieldCount } from "@utils/compute-translated-field-count"
|
||||
import { TRANSLATABLE_FIELDS_CONFIG_KEY } from "@utils/constants"
|
||||
import { filterTranslationFields } from "@utils/filter-translation-fields"
|
||||
|
||||
type InjectedDependencies = {
|
||||
@@ -33,7 +38,6 @@ type InjectedDependencies = {
|
||||
translationSettingsService: ModulesSdkTypes.IMedusaInternalService<
|
||||
typeof Settings
|
||||
>
|
||||
[TRANSLATABLE_FIELDS_CONFIG_KEY]: Record<string, string[]>
|
||||
}
|
||||
|
||||
export default class TranslationModuleService
|
||||
@@ -78,6 +82,55 @@ export default class TranslationModuleService
|
||||
this.settingsService_ = translationSettingsService
|
||||
}
|
||||
|
||||
__hooks = {
|
||||
onApplicationStart: async () => {
|
||||
return this.onApplicationStart_()
|
||||
},
|
||||
}
|
||||
|
||||
protected async onApplicationStart_() {
|
||||
const translatableEntities = DmlEntity.getTranslatableEntities()
|
||||
const translatableEntitiesSet = new Set(
|
||||
translatableEntities.map((entity) => toSnakeCase(entity.entity))
|
||||
)
|
||||
|
||||
const currentTranslationSettings = await this.settingsService_.list()
|
||||
const currentTranslationSettingsSet = new Set(
|
||||
currentTranslationSettings.map((setting) => setting.entity_type)
|
||||
)
|
||||
|
||||
const settingsToUpsert: (
|
||||
| CreateTranslationSettingsDTO
|
||||
| UpdateTranslationSettingsDTO
|
||||
)[] = []
|
||||
|
||||
for (const setting of currentTranslationSettings) {
|
||||
if (
|
||||
!translatableEntitiesSet.has(setting.entity_type) &&
|
||||
setting.is_active
|
||||
) {
|
||||
settingsToUpsert.push({
|
||||
id: setting.id,
|
||||
is_active: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const entity of translatableEntities) {
|
||||
const snakeCaseEntityType = toSnakeCase(entity.entity)
|
||||
const hasCurrentSettings =
|
||||
currentTranslationSettingsSet.has(snakeCaseEntityType)
|
||||
if (!hasCurrentSettings) {
|
||||
settingsToUpsert.push({
|
||||
entity_type: snakeCaseEntityType,
|
||||
fields: entity.fields,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await this.settingsService_.upsert(settingsToUpsert)
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
async getTranslatableFields(
|
||||
entityType?: string,
|
||||
@@ -90,7 +143,8 @@ export default class TranslationModuleService
|
||||
sharedContext
|
||||
)
|
||||
return settings.reduce((acc, setting) => {
|
||||
acc[setting.entity_type] = setting.fields as unknown as string[]
|
||||
acc[toSnakeCase(setting.entity_type)] =
|
||||
setting.fields as unknown as string[]
|
||||
return acc
|
||||
}, {} as Record<string, string[]>)
|
||||
}
|
||||
@@ -377,6 +431,42 @@ export default class TranslationModuleService
|
||||
return Array.isArray(data) ? serialized : serialized[0]
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
@EmitEvents()
|
||||
// @ts-expect-error
|
||||
async createTranslationSettings(
|
||||
data: CreateTranslationSettingsDTO[] | CreateTranslationSettingsDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<
|
||||
| TranslationTypes.TranslationSettingsDTO
|
||||
| TranslationTypes.TranslationSettingsDTO[]
|
||||
> {
|
||||
const dataArray = Array.isArray(data) ? data : [data]
|
||||
|
||||
await this.validateSettings_(dataArray, sharedContext)
|
||||
|
||||
// @ts-expect-error TS can't match union type to overloads
|
||||
return await super.createTranslationSettings(data, sharedContext)
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
@EmitEvents()
|
||||
// @ts-expect-error
|
||||
async updateTranslationSettings(
|
||||
data: UpdateTranslationSettingsDTO | UpdateTranslationSettingsDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<
|
||||
| TranslationTypes.TranslationSettingsDTO[]
|
||||
| TranslationTypes.TranslationSettingsDTO
|
||||
> {
|
||||
const dataArray = Array.isArray(data) ? data : [data]
|
||||
|
||||
await this.validateSettings_(dataArray, sharedContext)
|
||||
|
||||
// @ts-expect-error TS can't match union type to overloads
|
||||
return await super.updateTranslationSettings(data, sharedContext)
|
||||
}
|
||||
|
||||
@InjectManager()
|
||||
async getStatistics(
|
||||
input: TranslationTypes.TranslationStatisticsInput,
|
||||
@@ -492,4 +582,79 @@ export default class TranslationModuleService
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the translation settings to create or update against the translatable entities and their translatable fields configuration.
|
||||
* @param dataToValidate - The data to validate.
|
||||
* @param sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
*/
|
||||
@InjectManager()
|
||||
protected async validateSettings_(
|
||||
dataToValidate: (
|
||||
| CreateTranslationSettingsDTO
|
||||
| UpdateTranslationSettingsDTO
|
||||
)[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const translatableEntities = DmlEntity.getTranslatableEntities()
|
||||
const translatableEntitiesMap = new Map(
|
||||
translatableEntities.map((entity) => [toSnakeCase(entity.entity), entity])
|
||||
)
|
||||
|
||||
const invalidSettings: {
|
||||
entity_type: string
|
||||
is_invalid_entity: boolean
|
||||
invalidFields?: string[]
|
||||
}[] = []
|
||||
|
||||
for (const item of dataToValidate) {
|
||||
let itemEntityType = item.entity_type
|
||||
if (!itemEntityType) {
|
||||
const translationSetting = await this.retrieveTranslationSettings(
|
||||
//@ts-expect-error - if no entity_type, we are on an update
|
||||
item.id,
|
||||
{ select: ["entity_type"] },
|
||||
sharedContext
|
||||
)
|
||||
itemEntityType = translationSetting.entity_type
|
||||
}
|
||||
|
||||
const entity = translatableEntitiesMap.get(itemEntityType)
|
||||
|
||||
if (!entity) {
|
||||
invalidSettings.push({
|
||||
entity_type: itemEntityType,
|
||||
is_invalid_entity: true,
|
||||
})
|
||||
} else {
|
||||
const invalidFields = arrayDifference(item.fields ?? [], entity.fields)
|
||||
if (invalidFields.length) {
|
||||
invalidSettings.push({
|
||||
entity_type: itemEntityType,
|
||||
is_invalid_entity: false,
|
||||
invalidFields,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidSettings.length) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
"Invalid translation settings:\n" +
|
||||
invalidSettings
|
||||
.map(
|
||||
(setting) =>
|
||||
`- ${setting.entity_type} ${
|
||||
setting.is_invalid_entity
|
||||
? "is not a translatable entity"
|
||||
: `doesn't have the following fields set as translatable: ${setting.invalidFields?.join(
|
||||
", "
|
||||
)}`
|
||||
}`
|
||||
)
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const TRANSLATABLE_FIELDS_CONFIG_KEY = "translatableFieldsConfig"
|
||||
@@ -1,41 +0,0 @@
|
||||
export const PRODUCT_TRANSLATABLE_FIELDS = [
|
||||
"title",
|
||||
"description",
|
||||
"material",
|
||||
"subtitle",
|
||||
]
|
||||
export const PRODUCT_VARIANT_TRANSLATABLE_FIELDS = ["title", "material"]
|
||||
export const PRODUCT_TYPE_TRANSLATABLE_FIELDS = ["value"]
|
||||
export const PRODUCT_COLLECTION_TRANSLATABLE_FIELDS = ["title"]
|
||||
export const PRODUCT_CATEGORY_TRANSLATABLE_FIELDS = ["name", "description"]
|
||||
export const PRODUCT_TAG_TRANSLATABLE_FIELDS = ["value"]
|
||||
export const PRODUCT_OPTION_TRANSLATABLE_FIELDS = ["title"]
|
||||
export const PRODUCT_OPTION_VALUE_TRANSLATABLE_FIELDS = ["value"]
|
||||
export const REGION_TRANSLATABLE_FIELDS = ["name"]
|
||||
export const CUSTOMER_GROUP_TRANSLATABLE_FIELDS = ["name"]
|
||||
export const SHIPPING_OPTION_TRANSLATABLE_FIELDS = ["name"]
|
||||
export const SHIPPING_OPTION_TYPE_TRANSLATABLE_FIELDS = ["label", "description"]
|
||||
export const TAX_RATE_TRANSLATABLE_FIELDS = ["name"]
|
||||
|
||||
// export const RETURN_REASON_TRANSLATABLE_FIELDS = [
|
||||
// "value",
|
||||
// "label",
|
||||
// "description",
|
||||
// ]
|
||||
|
||||
export const translatableFieldsConfig = {
|
||||
product: PRODUCT_TRANSLATABLE_FIELDS,
|
||||
product_variant: PRODUCT_VARIANT_TRANSLATABLE_FIELDS,
|
||||
product_type: PRODUCT_TYPE_TRANSLATABLE_FIELDS,
|
||||
product_collection: PRODUCT_COLLECTION_TRANSLATABLE_FIELDS,
|
||||
product_category: PRODUCT_CATEGORY_TRANSLATABLE_FIELDS,
|
||||
product_tag: PRODUCT_TAG_TRANSLATABLE_FIELDS,
|
||||
product_option: PRODUCT_OPTION_TRANSLATABLE_FIELDS,
|
||||
product_option_value: PRODUCT_OPTION_VALUE_TRANSLATABLE_FIELDS,
|
||||
region: REGION_TRANSLATABLE_FIELDS,
|
||||
customer_group: CUSTOMER_GROUP_TRANSLATABLE_FIELDS,
|
||||
shipping_option: SHIPPING_OPTION_TRANSLATABLE_FIELDS,
|
||||
shipping_option_type: SHIPPING_OPTION_TYPE_TRANSLATABLE_FIELDS,
|
||||
tax_rate: TAX_RATE_TRANSLATABLE_FIELDS,
|
||||
// return_reason: RETURN_REASON_TRANSLATABLE_FIELDS,
|
||||
}
|
||||
Reference in New Issue
Block a user