feat(core-flows,types,utils,medusa): Translate tax lines (#14359)

* Include locale field for traslations on tax line workflows

* Translate tax lines in getItemTaxLinesStep with new util

* Update tax calculation context, so that we pass locale to third party tax providers if they want to return translated tax rates

* Apply translations to tax lines on product and variant tax middlewares

* Cart management translations tests

* Update tax lines when order locale gets updated

* Add changeset

* Get tranlsated tax lines step

* Fix wording

* Mutate ref directly

* Update order tax lines translations upon order locale change

* Claims translations tests

* Update tax lines upon draft order locale update

* Exchange tests for tax lines translations

* Order edits test for tax line translation

* Add tests for shipping methods tax line translations on various order flows

* Returns shipping method translations tests

* Execute update in parallel

* Use TranslationFeatureFlag.key

* Fix feature flag import

* Add @medusajs/medusa dependency for feature flag usage

* Revert "Add @medusajs/medusa dependency for feature flag usage"

This reverts commit e8897aed0a88f83c1034ac73e817e4222250a2c9.

* Use feature flag string directly

* Fix test

* Parallelize tax line translations application
This commit is contained in:
Nicolas Gorga
2026-01-13 15:12:42 -03:00
committed by GitHub
parent 28fae96cee
commit cec8b8e428
23 changed files with 2032 additions and 164 deletions
@@ -31,6 +31,8 @@ medusaIntegrationTestRunner({
let product: { id: string; variants: { id: string; title: string }[] }
let salesChannel: { id: string }
let shippingProfile: { id: string }
let taxRegion: { id: string }
let taxRate: { id: string; name: string }
beforeAll(async () => {
appContainer = getContainer()
@@ -85,6 +87,59 @@ medusaIntegrationTestRunner({
)
).data.region
// Create tax region and tax rate for tax line translations
taxRegion = (
await api.post(
"/admin/tax-regions",
{
country_code: "us",
provider_id: "tp_system",
},
adminHeaders
)
).data.tax_region
// Create the default tax rate
taxRate = (
await api.post(
"/admin/tax-rates",
{
tax_region_id: taxRegion.id,
name: "US Sales Tax",
rate: 5,
code: "US_SALES_TAX",
is_default: true,
},
adminHeaders
)
).data.tax_rate
// Create translations for tax rate name
await api.post(
"/admin/translations/batch",
{
create: [
{
reference_id: taxRate.id,
reference: "tax_rate",
locale_code: "fr-FR",
translations: {
name: "Taxe de vente US",
},
},
{
reference_id: taxRate.id,
reference: "tax_rate",
locale_code: "de-DE",
translations: {
name: "US Umsatzsteuer",
},
},
],
},
adminHeaders
)
// Create product with description for translation
product = (
await api.post(
@@ -516,6 +571,255 @@ medusaIntegrationTestRunner({
})
})
describe("Tax line translations", () => {
it("should translate tax line descriptions when creating a cart with locale", async () => {
const response = await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
locale: "fr-FR",
shipping_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
storeHeaders
)
expect(response.status).toEqual(200)
expect(response.data.cart.items[0]).toEqual(
expect.objectContaining({
tax_lines: expect.arrayContaining([
expect.objectContaining({
description: "Taxe de vente US",
rate: 5,
}),
]),
})
)
})
it("should use original tax line description when no locale is provided", async () => {
const response = await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
shipping_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
storeHeaders
)
expect(response.status).toEqual(200)
expect(response.data.cart.items[0]).toEqual(
expect.objectContaining({
tax_lines: expect.arrayContaining([
expect.objectContaining({
description: "US Sales Tax",
rate: 5,
}),
]),
})
)
})
it("should translate tax line descriptions when adding items to cart with locale", async () => {
const cartResponse = await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
locale: "de-DE",
shipping_address: shippingAddressData,
},
storeHeaders
)
const cart = cartResponse.data.cart
const addItemResponse = await api.post(
`/store/carts/${cart.id}/line-items`,
{
variant_id: product.variants[0].id,
quantity: 1,
},
storeHeaders
)
expect(addItemResponse.status).toEqual(200)
expect(addItemResponse.data.cart.items[0]).toEqual(
expect.objectContaining({
tax_lines: expect.arrayContaining([
expect.objectContaining({
description: "US Umsatzsteuer",
rate: 5,
}),
]),
})
)
})
it("should re-translate tax line descriptions when cart locale is updated", async () => {
const cartResponse = await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
locale: "fr-FR",
shipping_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
storeHeaders
)
const cart = cartResponse.data.cart
// Verify French translation
expect(cart.items[0].tax_lines[0].description).toEqual(
"Taxe de vente US"
)
// Update locale to German
const updateResponse = await api.post(
`/store/carts/${cart.id}`,
{
locale: "de-DE",
},
storeHeaders
)
expect(updateResponse.status).toEqual(200)
// Fetch updated cart
const updatedCartResponse = await api.get(
`/store/carts/${cart.id}`,
storeHeaders
)
const updatedCart = updatedCartResponse.data.cart
// Verify German translation
expect(updatedCart.items[0]).toEqual(
expect.objectContaining({
tax_lines: expect.arrayContaining([
expect.objectContaining({
description: "US Umsatzsteuer",
rate: 5,
}),
]),
})
)
})
it("should revert to original tax line description when locale has no translation", async () => {
const cartResponse = await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
locale: "fr-FR",
shipping_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
storeHeaders
)
const cart = cartResponse.data.cart
// Verify French translation exists
expect(cart.items[0].tax_lines[0].description).toEqual(
"Taxe de vente US"
)
// Update to locale without translation
const updateResponse = await api.post(
`/store/carts/${cart.id}`,
{
locale: "ja-JP",
},
storeHeaders
)
expect(updateResponse.status).toEqual(200)
// Fetch updated cart
const updatedCartResponse = await api.get(
`/store/carts/${cart.id}`,
storeHeaders
)
const updatedCart = updatedCartResponse.data.cart
// Should revert to original description
expect(updatedCart.items[0]).toEqual(
expect.objectContaining({
tax_lines: expect.arrayContaining([
expect.objectContaining({
description: "US Sales Tax",
rate: 5,
}),
]),
})
)
})
it("should translate tax lines for all items when multiple items are added", async () => {
const cartResponse = await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
locale: "fr-FR",
shipping_address: shippingAddressData,
},
storeHeaders
)
const cart = cartResponse.data.cart
// Add first item
await api.post(
`/store/carts/${cart.id}/line-items`,
{
variant_id: product.variants[0].id,
quantity: 1,
},
storeHeaders
)
// Add second item
const response = await api.post(
`/store/carts/${cart.id}/line-items`,
{
variant_id: product.variants[1].id,
quantity: 1,
},
storeHeaders
)
expect(response.status).toEqual(200)
expect(response.data.cart.items).toHaveLength(2)
// Both items should have translated tax lines
response.data.cart.items.forEach((item: any) => {
expect(item.tax_lines).toEqual(
expect.arrayContaining([
expect.objectContaining({
description: "Taxe de vente US",
rate: 5,
}),
])
)
})
})
})
describe("POST /store/carts/:id/shipping-methods (shipping method translation)", () => {
let shippingOption