chore(): Remove default_locale from StoreLocale (#14300)

## Summary

**What** — What changes are introduced in this PR?

Remove `default_locale` from Store <> Locale relation

**Why** — Why are these changes relevant or necessary?  

*Please provide answer here*

**How** — How have these changes been implemented?

*Please provide answer here*

**Testing** — How have these changes been tested, or how can the reviewer test the feature?

*Please provide answer here*

---

## Examples

Provide examples or code snippets that demonstrate how this feature works, or how it can be used in practice.  
This helps with documentation and ensures maintainers can quickly understand and verify the change.

```ts
// Example usage
```

---

## Checklist

Please ensure the following before requesting a review:

- [ ] I have added a **changeset** for this PR
    - Every non-breaking change should be marked as a **patch**
    - To add a changeset, run `yarn changeset` and follow the prompts
- [ ] The changes are covered by relevant **tests**
- [ ] I have verified the code works as intended locally
- [ ] I have linked the related issue(s) if applicable

---

## Additional Context

Add any additional context, related issues, or references that might help the reviewer understand this PR.


Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Nicolas Gorga
2025-12-15 06:05:46 -03:00
committed by GitHub
parent dd3eb10b1a
commit 8964a03fa1
23 changed files with 63 additions and 226 deletions

View File

@@ -0,0 +1,9 @@
---
"@medusajs/dashboard": patch
"@medusajs/framework": patch
"@medusajs/store": patch
"@medusajs/types": patch
"@medusajs/medusa": patch
---
chore(): Remove default_locale from StoreLocale

View File

@@ -1,11 +1,11 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { MedusaContainer } from "@medusajs/types"
import { Modules, ProductStatus } from "@medusajs/utils"
import {
createAdminUser,
generatePublishableKey,
generateStoreHeaders,
} from "../../../../helpers/create-admin-user"
import { MedusaContainer } from "@medusajs/types"
jest.setTimeout(100000)
@@ -63,7 +63,7 @@ medusaIntegrationTestRunner({
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US", is_default: true },
{ locale_code: "en-US" },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],

View File

@@ -45,7 +45,7 @@ medusaIntegrationTestRunner({
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US", is_default: true },
{ locale_code: "en-US" },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],

View File

@@ -61,7 +61,7 @@ medusaIntegrationTestRunner({
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US", is_default: true },
{ locale_code: "en-US" },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],

View File

@@ -60,7 +60,7 @@ medusaIntegrationTestRunner({
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US", is_default: true },
{ locale_code: "en-US" },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],

View File

@@ -62,7 +62,7 @@ medusaIntegrationTestRunner({
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US", is_default: true },
{ locale_code: "en-US" },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],

View File

@@ -1,10 +1,10 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { MedusaContainer } from "@medusajs/types"
import { Modules } from "@medusajs/utils"
import {
adminHeaders,
createAdminUser,
} from "../../../../helpers/create-admin-user"
import { MedusaContainer } from "@medusajs/types"
import { Modules } from "@medusajs/utils"
jest.setTimeout(100000)
@@ -32,7 +32,7 @@ medusaIntegrationTestRunner({
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US", is_default: true },
{ locale_code: "en-US" },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],

View File

@@ -100,19 +100,10 @@ export const AddLocalesForm = ({ store }: AddLocalesFormProps) => {
new Set([...data.locales, ...preSelectedRows])
) as string[]
let defaultLocale = store.supported_locales?.find(
(l) => l.is_default
)?.locale_code
if (!locales.includes(defaultLocale ?? "")) {
defaultLocale = locales?.[0]
}
await mutateAsync(
{
supported_locales: locales.map((l) => ({
locale_code: l,
is_default: l === defaultLocale,
})),
},
{

View File

@@ -7,7 +7,6 @@ import { Link } from "react-router-dom"
import { ActionMenu } from "../../../../../components/common/action-menu"
import { useSalesChannel, useStockLocation } from "../../../../../hooks/api"
import { useRegion } from "../../../../../hooks/api/regions"
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
type StoreGeneralSectionProps = {
store: AdminStore
@@ -15,14 +14,12 @@ type StoreGeneralSectionProps = {
export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => {
const { t } = useTranslation()
const isTranslationsEnabled = useFeatureFlag("translation")
const { region } = useRegion(store.default_region_id!, undefined, {
enabled: !!store.default_region_id,
})
const defaultCurrency = store.supported_currencies?.find((c) => c.is_default)
const defaultLocale = store.supported_locales?.find((l) => l.is_default)
const { sales_channel } = useSalesChannel(store.default_sales_channel_id!, {
enabled: !!store.default_sales_channel_id,
@@ -88,27 +85,6 @@ export const StoreGeneralSection = ({ store }: StoreGeneralSectionProps) => {
</Text>
)}
</div>
{isTranslationsEnabled && (
<div className="text-ui-fg-subtle grid grid-cols-2 px-6 py-4">
<Text size="small" leading="compact" weight="plus">
{t("store.defaultLocale")}
</Text>
{defaultLocale ? (
<div className="flex items-center gap-x-2">
<Badge size="2xsmall">
{defaultLocale.locale_code?.toUpperCase()}
</Badge>
<Text size="small" leading="compact">
{defaultLocale.locale?.name}
</Text>
</div>
) : (
<Text size="small" leading="compact">
-
</Text>
)}
</div>
)}
<div className="text-ui-fg-subtle grid grid-cols-2 px-6 py-4">
<Text size="small" leading="compact" weight="plus">
{t("store.defaultRegion")}

View File

@@ -60,8 +60,6 @@ export const StoreLocaleSection = ({ store }: StoreLocaleSectionProps) => {
meta: {
storeId: store.id,
supportedLocales: store.supported_locales,
defaultLocaleCode: store.supported_locales?.find((l) => l.is_default)
?.locale_code,
},
})
@@ -165,12 +163,10 @@ const LocaleActions = ({
storeId,
locale,
supportedLocales,
defaultLocaleCode,
}: {
storeId: string
locale: HttpTypes.AdminLocale
supportedLocales: HttpTypes.AdminStoreLocale[]
defaultLocaleCode: string
}) => {
const { mutateAsync } = useUpdateStore(storeId)
const { t } = useTranslation()
@@ -218,7 +214,6 @@ const LocaleActions = ({
icon: <Trash />,
label: t("actions.remove"),
onClick: handleRemove,
disabled: locale.code === defaultLocaleCode,
},
],
},
@@ -267,9 +262,7 @@ const useColumns = () => {
columnHelper.display({
id: "actions",
cell: ({ row, table }) => {
const { supportedLocales, storeId, defaultLocaleCode } = table.options
.meta as {
defaultLocaleCode: string
const { supportedLocales, storeId } = table.options.meta as {
supportedLocales: HttpTypes.AdminStoreLocale[]
storeId: string
}
@@ -279,7 +272,6 @@ const useColumns = () => {
storeId={storeId}
locale={row.original}
supportedLocales={supportedLocales}
defaultLocaleCode={defaultLocaleCode}
/>
)
},

View File

@@ -13,7 +13,6 @@ import { useUpdateStore } from "../../../../../hooks/api/store"
import { useComboboxData } from "../../../../../hooks/use-combobox-data"
import { sdk } from "../../../../../lib/client"
import { useDocumentDirection } from "../../../../../hooks/use-document-direction"
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
type EditStoreFormProps = {
store: HttpTypes.AdminStore
@@ -22,7 +21,6 @@ type EditStoreFormProps = {
const EditStoreSchema = z.object({
name: z.string().min(1),
default_currency_code: z.string().optional(),
default_locale_code: z.string().optional(),
default_region_id: z.string().optional(),
default_sales_channel_id: z.string().optional(),
default_location_id: z.string().optional(),
@@ -30,16 +28,12 @@ const EditStoreSchema = z.object({
export const EditStoreForm = ({ store }: EditStoreFormProps) => {
const { t } = useTranslation()
const isTranslationsEnabled = useFeatureFlag("translation")
const { handleSuccess } = useRouteModal()
const direction = useDocumentDirection()
const form = useForm<z.infer<typeof EditStoreSchema>>({
defaultValues: {
name: store.name,
default_region_id: store.default_region_id || undefined,
default_locale_code:
store.supported_locales?.find((l) => l.is_default)?.locale_code ||
undefined,
default_currency_code:
store.supported_currencies?.find((c) => c.is_default)?.currency_code ||
undefined,
@@ -79,14 +73,10 @@ export const EditStoreForm = ({ store }: EditStoreFormProps) => {
})
const handleSubmit = form.handleSubmit(async (values) => {
const { default_currency_code, default_locale_code, ...rest } = values
const { default_currency_code, ...rest } = values
const normalizedMutation: HttpTypes.AdminUpdateStore = {
...rest,
supported_locales: store.supported_locales?.map((l) => ({
...l,
is_default: l.locale_code === default_locale_code,
})),
supported_currencies: store.supported_currencies?.map((c) => ({
...c,
is_default: c.currency_code === default_currency_code,
@@ -156,40 +146,6 @@ export const EditStoreForm = ({ store }: EditStoreFormProps) => {
)
}}
/>
{isTranslationsEnabled && (
<Form.Field
control={form.control}
name="default_locale_code"
render={({ field: { onChange, ...field } }) => {
return (
<Form.Item>
<Form.Label>{t("store.defaultLocale")}</Form.Label>
<Form.Control>
<Select
dir={direction}
{...field}
onValueChange={onChange}
>
<Select.Trigger ref={field.ref}>
<Select.Value />
</Select.Trigger>
<Select.Content>
{store.supported_locales?.map((locale) => (
<Select.Item
key={locale.locale_code}
value={locale.locale_code}
>
{locale.locale_code}
</Select.Item>
))}
</Select.Content>
</Select>
</Form.Control>
</Form.Item>
)
}}
/>
)}
<Form.Field
control={form.control}
name="default_region_id"

View File

@@ -1,4 +1,4 @@
import { ContainerRegistrationKeys, normalizeLocale } from "@medusajs/utils"
import { normalizeLocale } from "@medusajs/utils"
import type {
MedusaNextFunction,
MedusaRequest,
@@ -35,30 +35,5 @@ export async function applyLocale(
return next()
}
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
const {
data: [store],
} = await query.graph(
{
entity: "store",
fields: ["id", "supported_locales"],
pagination: {
take: 1,
},
},
{
cache: {
enable: true,
},
}
)
if (store?.supported_locales?.length) {
req.locale = store.supported_locales.find(
(locale) => locale.is_default
)?.locale_code
return next()
}
return next()
}

View File

@@ -55,10 +55,6 @@ export interface AdminStoreLocale {
* The ID of the store that the locale belongs to.
*/
store_id: string
/**
* Whether the locale is the default locale for the store.
*/
is_default: boolean
/**
* The locale's details.
*/

View File

@@ -26,10 +26,6 @@ export interface AdminUpdateStoreSupportedLocale {
* The locale's BCP 47 language tag.
*/
locale_code: string
/**
* Whether this locale is the default locale in the store.
*/
is_default?: boolean
}
/**

View File

@@ -40,10 +40,6 @@ export interface StoreLocaleDTO {
* The locale code of the store locale.
*/
locale_code: string
/**
* Whether the locale is the default one for the store.
*/
is_default: boolean
/**
* The store ID associated with the locale.
*/

View File

@@ -14,10 +14,6 @@ export interface CreateStoreLocaleDTO {
* The locale code of the store locale.
*/
locale_code: string
/**
* Whether the locale is the default one for the store.
*/
is_default?: boolean
}
/**

View File

@@ -35,7 +35,6 @@ export const AdminUpdateStore = z.object({
.array(
z.object({
locale_code: z.string(),
is_default: z.boolean().optional(),
})
)
.optional(),

View File

@@ -8,7 +8,7 @@ export const createStoreFixture: StoreTypes.CreateStoreDTO = {
],
supported_locales: [
{ locale_code: "fr-FR" },
{ locale_code: "en-US", is_default: true },
{ locale_code: "en-US" },
],
default_sales_channel_id: "test-sales-channel",
default_region_id: "test-region",

View File

@@ -1,7 +1,7 @@
import { IStoreModuleService } from "@medusajs/framework/types"
import { Module, Modules } from "@medusajs/framework/utils"
import { StoreModuleService } from "@services"
import { moduleIntegrationTestRunner } from "@medusajs/test-utils"
import { StoreModuleService } from "@services"
import { createStoreFixture } from "../__fixtures__"
jest.setTimeout(100000)
@@ -92,19 +92,6 @@ moduleIntegrationTestRunner<IStoreModuleService>({
"There should be a default currency set for the store"
)
})
it("should fail to get created if there is no default locale", async function () {
const err = await service
.createStores({
...createStoreFixture,
supported_locales: [{ locale_code: "en-US" }],
})
.catch((err) => err.message)
expect(err).toEqual(
"There should be a default locale set for the store"
)
})
})
describe("upserting a store", () => {
@@ -160,19 +147,6 @@ moduleIntegrationTestRunner<IStoreModuleService>({
)
})
it("should fail updating locales without a default one", async function () {
const createdStore = await service.createStores(createStoreFixture)
const updateErr = await service
.updateStores(createdStore.id, {
supported_locales: [{ locale_code: "en-US" }],
})
.catch((err) => err.message)
expect(updateErr).toEqual(
"There should be a default locale set for the store"
)
})
it("should fail updating currencies where a duplicate currency code exists", async function () {
const createdStore = await service.createStores(createStoreFixture)
const updateErr = await service
@@ -214,20 +188,6 @@ moduleIntegrationTestRunner<IStoreModuleService>({
expect(updateErr).toEqual("Only one default currency is allowed")
})
it("should fail updating locales where there is more than 1 default locale", async function () {
const createdStore = await service.createStores(createStoreFixture)
const updateErr = await service
.updateStores(createdStore.id, {
supported_locales: [
{ locale_code: "en-US", is_default: true },
{ locale_code: "fr-FR", is_default: true },
],
})
.catch((err) => err.message)
expect(updateErr).toEqual("Only one default locale is allowed")
})
})
describe("deleting a store", () => {

View File

@@ -263,16 +263,6 @@
"nullable": false,
"mappedType": "text"
},
"is_default": {
"name": "is_default",
"type": "boolean",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"default": "false",
"mappedType": "boolean"
},
"store_id": {
"name": "store_id",
"type": "text",

View File

@@ -0,0 +1,13 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20251212161429 extends Migration {
override async up(): Promise<void> {
this.addSql(`alter table if exists "store_locale" drop column if exists "is_default";`);
}
override async down(): Promise<void> {
this.addSql(`alter table if exists "store_locale" add column if not exists "is_default" boolean not null default false;`);
}
}

View File

@@ -4,7 +4,6 @@ import Store from "./store"
const StoreLocale = model.define("StoreLocale", {
id: model.id({ prefix: "stloc" }).primaryKey(),
locale_code: model.text().searchable(),
is_default: model.boolean().default(false),
store: model
.belongsTo(() => Store, {
mappedBy: "supported_locales",

View File

@@ -227,55 +227,48 @@ export default class StoreModuleService
) {
for (const store of stores) {
if (store.supported_currencies?.length) {
StoreModuleService.validateSupportedItems(
store.supported_currencies,
(c) => c.currency_code,
StoreModuleService.validateUnique(
store.supported_currencies.map((currency) => currency.currency_code),
"currency"
)
let seenDefault = false
store.supported_currencies.forEach((currency) => {
if (currency.is_default) {
if (seenDefault) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Only one default currency is allowed`
)
}
seenDefault = true
}
})
if (!seenDefault) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`There should be a default currency set for the store`
)
}
}
// TODO: If we are protecting this module behind a feature flag, we should check if the feature flag is enabled before validating the locales.
if (store.supported_locales?.length) {
StoreModuleService.validateSupportedItems(
store.supported_locales,
(l) => l.locale_code,
StoreModuleService.validateUnique(
store.supported_locales.map((locale) => locale.locale_code),
"locale"
)
}
}
}
private static validateSupportedItems<T extends { is_default?: boolean }>(
items: T[],
getCode: (item: T) => string,
typeName: string
) {
const duplicates = getDuplicates(items.map(getCode))
private static validateUnique = (items: string[], fieldName: string) => {
const duplicates = getDuplicates(items)
if (duplicates.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Duplicate ${typeName} codes: ${duplicates.join(", ")}`
)
}
let seenDefault = false
items.forEach((item) => {
if (item.is_default) {
if (seenDefault) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Only one default ${typeName} is allowed`
)
}
seenDefault = true
}
})
if (!seenDefault) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`There should be a default ${typeName} set for the store`
`Duplicate ${fieldName} codes: ${duplicates.join(", ")}`
)
}
}