feat(): Translation settings + user configuration + admin hook and js sdk + dashboard (#14355)

* feat(): Translation settings + user configuration

* feat(): Translation settings + user configuration

* Create gentle-bees-grow.md

* add entities end point

* add entities end point

* add admin hook and js sdk method

* update changeset

* fix tests

* fix tests

* rm unnecessary copy

* update dashboard to use the new resources

* update dashboard to use the new resources

* update dashboard to use the new resources

* allow type inference through interface augmentation in the defineConfig of medusa-config

* allow type inference through interface augmentation in the defineConfig of medusa-config

* exclude id and _id props

---------

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2025-12-19 15:29:48 +01:00
committed by GitHub
co-authored by Oli Juhl
parent 797878af26
commit b21a599d11
26 changed files with 1041 additions and 260 deletions
@@ -161,6 +161,56 @@ export class Translation {
)
}
/**
* This method retrieves a paginated list of entities for a given entity type with only their
* translatable fields.
* It sends a request to the
* Get Translation Entities API route.
*
* @param query - The query parameters including the entity type and pagination configurations.
* @param headers - Headers to pass in the request.
* @returns The paginated list of entities with their translatable fields.
*
* @example
* To retrieve the entities for a given entity type:
*
* ```ts
* sdk.admin.translation.entities({
* type: "product"
* })
* .then(({ data, count, offset, limit }) => {
* console.log(data)
* })
* ```
*
* To configure the pagination, pass the `limit` and `offset` query parameters.
*
* For example, to retrieve only 10 items and skip 10 items:
*
* ```ts
* sdk.admin.translation.entities({
* type: "product",
* limit: 10,
* offset: 10
* })
* .then(({ data, count, offset, limit }) => {
* console.log(data)
* })
* ```
*/
async entities(
query: HttpTypes.AdminTranslationEntitiesParams,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminTranslationEntitiesResponse>(
`/admin/translations/entities`,
{
headers,
query,
}
)
}
/**
* This method retrieves the statistics for the translations for a given entity type or all entity types if no entity type is provided.
* It sends a request to the
+53 -15
View File
@@ -5,11 +5,30 @@ import {
} from "../modules-sdk"
import type { RedisOptions } from "ioredis"
import { ConnectionOptions } from "node:tls"
// @ts-ignore
import type { InlineConfig } from "vite"
import type { Logger } from "../logger"
/**
* Registry for module options types. Modules can augment this interface
* using declaration merging to provide typed options in defineConfig.
*
* @example
* ```ts
* // In @medusajs/translation module:
* declare module "@medusajs/types" {
* interface ModuleOptions {
* "@medusajs/translation": {
* entities?: { type: string; fields: string[] }[]
* }
* }
* }
* ```
*/
export interface ModuleOptions {}
/**
* @interface
*
@@ -1093,17 +1112,6 @@ export type ConfigModule = {
logger?: Logger
}
type InternalModuleDeclarationOverride = InternalModuleDeclaration & {
/**
* Optional key to be used to identify the module, if not provided, it will be inferred from the module joiner config service name.
*/
key?: string
/**
* By default, modules are enabled, if provided as true, this will disable the module entirely.
*/
disable?: boolean
}
type ExternalModuleDeclarationOverride = ExternalModuleDeclaration & {
/**
* key to be used to identify the module, if not provided, it will be inferred from the module joiner config service name.
@@ -1116,11 +1124,41 @@ type ExternalModuleDeclarationOverride = ExternalModuleDeclaration & {
}
/**
* Modules accepted by the defineConfig function
* Generates a union of typed module configs for all known modules in the ModuleOptions registry.
* This enables automatic type inference when using registered module resolve strings.
*/
export type InputConfigModules = Partial<
InternalModuleDeclarationOverride | ExternalModuleDeclarationOverride
>[]
type KnownModuleConfigs = {
[K in keyof ModuleOptions]: Partial<
Omit<InternalModuleDeclaration, "options"> & {
key?: string
disable?: boolean
resolve: K
options?: ModuleOptions[K]
}
>
}[keyof ModuleOptions]
/**
* Generic module config for modules not registered in ModuleOptions.
*/
type GenericModuleConfig = Partial<
Omit<InternalModuleDeclaration, "options"> & {
key?: string
disable?: boolean
resolve?: string
options?: Record<string, unknown>
}
>
/**
* Modules accepted by the defineConfig function.
* Automatically infers options type for known modules registered in ModuleOptions.
*/
export type InputConfigModules = (
| KnownModuleConfigs
| GenericModuleConfig
| ExternalModuleDeclarationOverride
)[]
/**
* The configuration accepted by the "defineConfig" helper
@@ -61,3 +61,28 @@ export interface AdminTranslationSettingsParams {
*/
entity_type?: string
}
/**
* Query parameters for translation entities endpoint.
*/
export interface AdminTranslationEntitiesParams extends FindParams {
/**
* The entity type to retrieve (e.g., "product", "product_variant").
* This determines which table to query and which translatable fields to return.
*
* @example
* "product"
*/
type: string
/**
* Filter by entity ID(s). Can be a single ID or an array of IDs.
*
* @example
* "prod_123"
*
* @example
* ["prod_123", "prod_456"]
*/
id?: string | string[]
}
@@ -99,3 +99,35 @@ export interface AdminTranslationSettingsResponse {
*/
translatable_fields: Record<string, string[]>
}
/**
* Response for translation entities endpoint.
* Returns paginated entities with only their translatable fields and all their translations.
*/
export interface AdminTranslationEntitiesResponse {
/**
* The list of entities with their translatable fields.
* Each entity contains only the fields configured as translatable
* for that entity type in the translation settings, plus all
* translations for all locales.
*/
data: (Record<string, unknown> & {
id: string
translations: AdminTranslation[]
})[]
/**
* The total count of entities.
*/
count: number
/**
* The offset of the current page.
*/
offset: number
/**
* The limit of items per page.
*/
limit: number
}
@@ -80,6 +80,41 @@ export interface TranslationDTO {
deleted_at: Date | string | null
}
/**
* The translation settings details.
*/
export interface TranslationSettingsDTO {
/**
* The ID of the settings record.
*/
id: string
/**
* The entity type these settings apply to (e.g., "product", "product_variant").
*/
entity_type: string
/**
* The translatable fields for this entity type.
*/
fields: string[]
/**
* The date and time the settings were created.
*/
created_at: Date | string
/**
* The date and time the settings were last updated.
*/
updated_at: Date | string
/**
* The date and time the settings were deleted.
*/
deleted_at: Date | string | null
}
/**
* The filters to apply on the retrieved locales.
*/
+12 -8
View File
@@ -704,27 +704,31 @@ export interface ITranslationModuleService extends IModuleService {
): Promise<TranslationStatisticsOutput>
/**
* This method retrieves the translatable fields of a resource. For example,
* product entities have translatable fields such as `title` and `description`.
* This method retrieves the translatable fields of a resource from the database.
* For example, product entities have translatable fields such as `title` and `description`.
*
* @param {string} entityType - Name of the resource's table to get translatable fields for.
* If not provided, returns all translatable fields for all entity types. For example, `product` or `product_variant`.
* @returns {Record<string, string[]>} A mapping of resource names to their translatable fields.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<Record<string, string[]>>} A mapping of resource names to their translatable fields.
*
* @example
* To get translatable fields for all resources:
*
*
* ```ts
* const allFields = translationModuleService.getTranslatableFields()
* const allFields = await translationModuleService.getTranslatableFields()
* // Returns: { product: ["title", "description", ...], product_variant: ["title", ...] }
* ```
*
*
* To get translatable fields for a specific resource:
*
* ```ts
* const productFields = translationModuleService.getTranslatableFields("product")
* const productFields = await translationModuleService.getTranslatableFields("product")
* // Returns: { product: ["title", "description", "subtitle", "status"] }
* ```
*/
getTranslatableFields(entityType?: string): Record<string, string[]>
getTranslatableFields(
entityType?: string,
sharedContext?: Context
): Promise<Record<string, string[]>>
}
@@ -349,11 +349,11 @@ function resolveModules(
...(isObject(moduleConfig)
? moduleConfig
: { disable: !moduleConfig }),
})
} as InputConfigModules[number])
})
} else if (Array.isArray(configModules)) {
const modules_ = (configModules ?? []) as InternalModuleDeclaration[]
modules.push(...modules_)
modules.push(...(modules_ as InputConfigModules))
} else {
throw new Error(
"Invalid modules configuration. Should be an array or object."