feat(): Introduce translation module and preliminary application of them (#14189)

* feat(): Translation first steps

* feat(): locale middleware

* feat(): readonly links

* feat(): feature flag

* feat(): modules sdk

* feat(): translation module re export

* start adding workflows

* update typings

* update typings

* test(): Add integration tests

* test(): centralize filters preparation

* test(): centralize filters preparation

* remove unnecessary importy

* fix workflows

* Define StoreLocale inside Store Module

* Link definition to extend Store with supported_locales

* store_locale migration

* Add supported_locales handling in Store Module

* Tests

* Accept supported_locales in Store endpoints

* Add locales to js-sdk

* Include locale list and default locale in Store Detail section

* Initialize local namespace in js-sdk

* Add locales route

* Make code primary key of locale table to facilitate upserts

* Add locales routes

* Show locale code as is

* Add list translations api route

* Batch endpoint

* Types

* New batchTranslationsWorkflow and various updates to existent ones

* Edit default locale UI

* WIP

* Apply translation agnostically

* middleware

* Apply translation agnostically

* fix Apply translation agnostically

* apply translations to product list

* Add feature flag

* fetch translations by batches of 250 max

* fix apply

* improve and test util

* apply to product list

* dont manage translations if no locale

* normalize locale

* potential todo

* Protect translations routes with feature flag

* Extract normalize locale util to core/utils

* Normalize locale on write

* Normalize locale for read

* Use feature flag to guard translations UI across the board

* Avoid throwing incorrectly when locale_code not present in partial updates

* move applyTranslations util

* remove old tests

* fix util tests

* fix(): product end points

* cleanup

* update lock

* remove unused var

* cleanup

* fix apply locale

* missing new dep for test utils

* Change entity_type, entity_id to reference, reference_id

* Remove comment

* Avoid registering translations route if ff not enabled

* Prevent registering express handler for disabled route via defineFileConfig

* Add tests

* Add changeset

* Update test

* fix integration tests, module and internals

* Add locale id plus fixed

* Allow to pass array of reference_id

* fix unit tests

* fix link loading

* fix store route

* fix sales channel test

* fix tests

---------

Co-authored-by: Nicolas Gorga <nicogorga11@gmail.com>
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2025-12-08 19:33:08 +01:00
committed by GitHub
parent fea3d4ec49
commit 6dc0b8bed8
130 changed files with 5649 additions and 112 deletions

View File

@@ -9,4 +9,6 @@ export * from "./order-customer"
export * from "./order-product"
export * from "./order-region"
export * from "./order-sales-channel"
export * from "./product-translation"
export * from "./store-currency"
export * from "./store-locale"

View File

@@ -0,0 +1,148 @@
import { ModuleJoinerConfig } from "@medusajs/framework/types"
import {
FeatureFlag,
MEDUSA_SKIP_FILE,
Modules,
} from "@medusajs/framework/utils"
export const ProductTranslation: ModuleJoinerConfig = {
[MEDUSA_SKIP_FILE]: !FeatureFlag.isFeatureEnabled("translation"),
isLink: true,
isReadOnlyLink: true,
extends: [
{
serviceName: Modules.PRODUCT,
entity: "Product",
relationship: {
serviceName: Modules.TRANSLATION,
entity: "Translation",
primaryKey: "reference_id",
foreignKey: "id",
alias: "translations",
isList: true,
args: {
methodSuffix: "Translations",
},
},
},
{
serviceName: Modules.PRODUCT,
entity: "ProductVariant",
relationship: {
serviceName: Modules.TRANSLATION,
entity: "Translation",
primaryKey: "reference_id",
foreignKey: "id",
alias: "translations",
isList: true,
args: {
methodSuffix: "Translations",
},
},
},
{
serviceName: Modules.PRODUCT,
entity: "ProductCategory",
relationship: {
serviceName: Modules.TRANSLATION,
entity: "Translation",
primaryKey: "reference_id",
foreignKey: "id",
alias: "translations",
isList: true,
args: {
methodSuffix: "Translations",
},
},
},
{
serviceName: Modules.PRODUCT,
entity: "ProductCollection",
relationship: {
serviceName: Modules.TRANSLATION,
entity: "Translation",
primaryKey: "reference_id",
foreignKey: "id",
alias: "translations",
isList: true,
args: {
methodSuffix: "Translations",
},
},
},
{
serviceName: Modules.PRODUCT,
entity: "ProductTag",
relationship: {
serviceName: Modules.TRANSLATION,
entity: "Translation",
primaryKey: "reference_id",
foreignKey: "id",
alias: "translations",
isList: true,
args: {
methodSuffix: "Translations",
},
},
},
{
serviceName: Modules.PRODUCT,
entity: "ProductType",
relationship: {
serviceName: Modules.TRANSLATION,
entity: "Translation",
primaryKey: "reference_id",
foreignKey: "id",
alias: "translations",
isList: true,
args: {
methodSuffix: "Translations",
},
},
},
{
serviceName: Modules.PRODUCT,
entity: "ProductOption",
relationship: {
serviceName: Modules.TRANSLATION,
entity: "Translation",
primaryKey: "reference_id",
foreignKey: "id",
alias: "translations",
isList: true,
args: {
methodSuffix: "Translations",
},
},
},
{
serviceName: Modules.PRODUCT,
entity: "ProductOptionValue",
relationship: {
serviceName: Modules.TRANSLATION,
entity: "Translation",
primaryKey: "reference_id",
foreignKey: "id",
alias: "translations",
isList: true,
args: {
methodSuffix: "Translations",
},
},
},
{
serviceName: Modules.TRANSLATION,
entity: "Translation",
relationship: {
serviceName: Modules.PRODUCT,
entity: "Product",
primaryKey: "id",
foreignKey: "reference_id",
alias: "product",
args: {
methodSuffix: "Products",
},
},
},
],
} as ModuleJoinerConfig

View File

@@ -0,0 +1,28 @@
import { ModuleJoinerConfig } from "@medusajs/framework/types"
import {
FeatureFlag,
MEDUSA_SKIP_FILE,
Modules,
} from "@medusajs/framework/utils"
export const StoreLocales: ModuleJoinerConfig = {
[MEDUSA_SKIP_FILE]: !FeatureFlag.isFeatureEnabled("translation"),
isLink: true,
isReadOnlyLink: true,
extends: [
{
serviceName: Modules.STORE,
entity: "Store",
relationship: {
serviceName: Modules.TRANSLATION,
entity: "Locale",
primaryKey: "code",
foreignKey: "supported_locales.locale_code",
alias: "locale",
args: {
methodSuffix: "Locales",
},
},
},
],
} as ModuleJoinerConfig

View File

@@ -14,6 +14,7 @@ import {
composeLinkName,
composeTableName,
ContainerRegistrationKeys,
isFileSkipped,
Modules,
promiseAll,
simpleHash,
@@ -40,9 +41,9 @@ export const initialize = async (
(mod) => Object.keys(mod)[0]
)
const allLinksToLoad = Object.values(linkDefinitions).concat(
pluginLinksDefinitions ?? []
)
const allLinksToLoad = Object.values(linkDefinitions)
.concat(pluginLinksDefinitions ?? [])
.filter((linkDefinition) => !isFileSkipped(linkDefinition))
await promiseAll(
allLinksToLoad.map(async (linkDefinition) => {