feat(): Translation statistics (#14299)

* chore(): Translation statistics

* chore(): improve statistics performances

* add end point to get statistics

* add tests

* Create spicy-games-unite.md

* feat(): add material and fix tests

* feat(): add translatable api

* feat(): add translatable api

* fix tests

* fix tests

* fix tests

* feedback
This commit is contained in:
Adrien de Peretti
2025-12-15 14:11:49 +01:00
committed by GitHub
parent 0f1566c644
commit ba6ed8d9dd
20 changed files with 1196 additions and 2 deletions
@@ -6,6 +6,7 @@ import {
import {
AdminBatchTranslations,
AdminGetTranslationsParams,
AdminTranslationStatistics,
} from "./validators"
import * as QueryConfig from "./query-config"
import { DEFAULT_BATCH_ENDPOINTS_SIZE_LIMIT } from "../../../utils"
@@ -29,4 +30,14 @@ export const adminTranslationsRoutesMiddlewares: MiddlewareRoute[] = [
},
middlewares: [validateAndTransformBody(AdminBatchTranslations)],
},
{
method: ["GET"],
matcher: "/admin/translations/statistics",
middlewares: [validateAndTransformQuery(AdminTranslationStatistics, {})],
},
{
method: ["GET"],
matcher: "/admin/translations/settings",
middlewares: [],
},
]
@@ -0,0 +1,29 @@
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { HttpTypes, ITranslationModuleService } from "@medusajs/framework/types"
import {
defineFileConfig,
FeatureFlag,
Modules,
} from "@medusajs/framework/utils"
import TranslationFeatureFlag from "../../../../feature-flags/translation"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse<HttpTypes.AdminTranslationSettingsResponse>
) => {
const translationService = req.scope.resolve<ITranslationModuleService>(
Modules.TRANSLATION
)
const translatable_fields = translationService.getTranslatableFields()
res.json({
translatable_fields,
})
}
defineFileConfig({
isDisabled: () => !FeatureFlag.isFeatureEnabled(TranslationFeatureFlag.key),
})
@@ -0,0 +1,75 @@
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { HttpTypes, ITranslationModuleService } from "@medusajs/framework/types"
import {
ContainerRegistrationKeys,
defineFileConfig,
FeatureFlag,
Modules,
promiseAll,
} from "@medusajs/framework/utils"
import TranslationFeatureFlag from "../../../../feature-flags/translation"
export const GET = async (
req: AuthenticatedMedusaRequest<
unknown,
HttpTypes.AdminTranslationStatisticsParams
>,
res: MedusaResponse<HttpTypes.AdminTranslationStatisticsResponse>
) => {
const translationService = req.scope.resolve<ITranslationModuleService>(
Modules.TRANSLATION
)
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
const { locales, entity_types } = req.validatedQuery
// Fetch counts for each entity type in parallel
const entityCounts = await promiseAll(
entity_types.map(async (entityType) => {
const { metadata } = await query
.graph(
{
entity: entityType,
fields: ["id"],
pagination: { take: 1, skip: 0 },
},
{
throwIfKeyNotFound: false,
cache: { enable: true },
}
)
.catch((e) => {
const normalizedMessage = e.message.toLowerCase()
if (
normalizedMessage.includes("service with alias") &&
normalizedMessage.includes("was not found")
) {
return { metadata: { count: 0 } }
}
throw e
})
return { entityType, count: metadata?.count ?? 0 }
})
)
const entities: Record<string, { count: number }> = {}
for (const { entityType, count } of entityCounts) {
entities[entityType] = { count }
}
const statistics = await translationService.getStatistics({
locales,
entities,
})
return res.json({
statistics,
})
}
defineFileConfig({
isDisabled: () => !FeatureFlag.isFeatureEnabled(TranslationFeatureFlag.key),
})
@@ -48,3 +48,19 @@ export const AdminBatchTranslations = createBatchBody(
AdminCreateTranslation,
AdminUpdateTranslation
)
export type AdminTranslationStatisticsType = z.infer<
typeof AdminTranslationStatistics
>
export const AdminTranslationStatistics = z
.object({
locales: z.union([z.string(), z.array(z.string())]),
entity_types: z.union([z.string(), z.array(z.string())]),
})
.transform((data) => ({
// Normalize to arrays for consistent handling
locales: Array.isArray(data.locales) ? data.locales : [data.locales],
entity_types: Array.isArray(data.entity_types)
? data.entity_types
: [data.entity_types],
}))