From a2210ea5e7f6ac0492eea97bf7d57c7482a9f9f0 Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Tue, 6 Jan 2026 17:40:26 +0200 Subject: [PATCH] docs: docs for next release (#14456) --- www/apps/api-reference/markdown/store.mdx | 8 +- .../api-routes/localization/page.mdx | 211 +++++++ .../data-models/write-migration/page.mdx | 62 +- .../fundamentals/generated-types/page.mdx | 26 +- .../module-links/index-module/page.mdx | 33 + .../fundamentals/module-links/query/page.mdx | 54 ++ www/apps/book/generated/edit-dates.mjs | 9 +- www/apps/book/generated/sidebar.mjs | 14 +- www/apps/book/public/llms-full.txt | 571 +++++++++++++++++- www/apps/book/sidebar.mjs | 5 + .../translation/custom-data-models/page.mdx | 327 ++++++++++ .../app/commerce-modules/translation/page.mdx | 134 +++- .../translation/storefront/page.mdx | 4 +- www/apps/resources/generated/edit-dates.mjs | 7 +- www/apps/resources/generated/files-map.mjs | 4 + .../generated-commerce-modules-sidebar.mjs | 8 + www/apps/resources/sidebars/translation.mjs | 5 + .../app/settings/translations/page.mdx | 14 +- .../docs-generator/src/classes/kinds/oas.ts | 2 +- 19 files changed, 1434 insertions(+), 64 deletions(-) create mode 100644 www/apps/book/app/learn/fundamentals/api-routes/localization/page.mdx create mode 100644 www/apps/resources/app/commerce-modules/translation/custom-data-models/page.mdx diff --git a/www/apps/api-reference/markdown/store.mdx b/www/apps/api-reference/markdown/store.mdx index 542edadc6e..489452c28e 100644 --- a/www/apps/api-reference/markdown/store.mdx +++ b/www/apps/api-reference/markdown/store.mdx @@ -1497,9 +1497,9 @@ Refer to [this guide](!docs!/learn/customization/extend-features/extend-create-p ]} /> -Medusa's Store APIs support localization. Currently, only product-related information can be localized, such as product titles and descriptions. +Medusa's Store APIs support localization of core and custom data models. Refer to the [Translation Module](!resources!/commerce-modules/translation) guide to learn about supported core data models, and how to [configure translations for custom data models](!resources!/commerce-modules/translation/custom-data-models). -By default, the Store APIs return data in the default locale configured in your Medusa application, and falls back to the original data of a resource. +By default, the Store APIs returns the original data without localization. @@ -1512,8 +1512,8 @@ By default, the Store APIs return data in the default locale configured in your You can set the locale of a request using one of the following methods: - Use the JS SDK's `setLocale` method. The JS SDK will automatically include the locale in the `x-medusa-locale` header of subsequent requests. -- Pass the `locale` query parameter to the [List Products API route](#products_getproducts). -- Set the `x-medusa-locale` header in the API request to the [List Products API route](#products_getproducts). +- Pass the `locale` query parameter to the relevant API route. By default, all `/store` API routes support the `locale` query parameter. +- Set the `x-medusa-locale` header in the API request to the relevant API route. By default, all `/store` API routes support the `x-medusa-locale` header. Locales are in the [IETF BCP 47](https://gist.github.com/typpo/b2b828a35e683b9bf8db91b5404f1bd1) format, such as `en-US` for American English, or `fr-FR` for French. diff --git a/www/apps/book/app/learn/fundamentals/api-routes/localization/page.mdx b/www/apps/book/app/learn/fundamentals/api-routes/localization/page.mdx new file mode 100644 index 0000000000..dce92bb1a9 --- /dev/null +++ b/www/apps/book/app/learn/fundamentals/api-routes/localization/page.mdx @@ -0,0 +1,211 @@ +import { CodeTabs, CodeTab, Prerequisites } from "docs-ui" + +export const metadata = { + title: `${pageNumber} Localization in API Routes`, + keywords: ["translation api-routes"] +} + +# {metadata.title} + +In this chapter, you'll learn how to handle localization in API routes of your Medusa application to serve content in different languages. + + + +## Overview + +Localization in API routes allows you to serve translated content based on the user's preferred language. The Medusa application provides built-in support for handling locale information in API requests and retrieving localized data. + +When a locale is specified in a request, you can use it to retrieve translated versions of your data models' fields, providing a seamless multilingual experience for your users. + +Learn more about translation, how to manage translations, and how to translate custom data models in the [Translation Module documentation](!resources!/commerce-modules/translation). + +--- + +## Routes with Localization Enabled by Default + +The Medusa application automatically supports retrieving localized content from all routes under the `/store` prefix, including both core and custom store API routes. + +For example, the following store routes have localization enabled by default: + +- `/store/products` -> Get products with translated fields +- `/store/collections` -> Get collections with translated fields +- `/store/categories` -> Get categories with translated fields + +Refer to the [Translation Module](!resources!/commerce-modules/translation#supported-module-translations) documentation for a list of supported core data models with localization support. + +### Apply Localization to Custom Routes + +If you're creating custom API routes outside the `/store` prefix, you must manually apply the `applyLocale` [middleware](../middlewares/page.mdx) to enable localization support. + +To apply the `applyLocale` middleware to all HTTP methods for a route, add it to the `src/api/middlewares.ts` file: + +export const allMethodsHighlights = [ + ["7", "applyLocale", "Apply the middleware to the route"], +] + +```ts title="src/api/middlewares.ts" highlights={allMethodsHighlights} +import { applyLocale, defineMiddlewares } from "@medusajs/framework/http" + +export default defineMiddlewares({ + routes: [ + { + matcher: "/custom*", + middlewares: [applyLocale], + }, + ], +}) +``` + +This applies the `applyLocale` middleware to all routes matching `/custom*`, regardless of the HTTP method. + +Alternatively, you can apply the middleware only to specific HTTP methods using the `method` property: + +export const specificMethodHighlights = [ + ["7", "method", "Apply the middleware only to `GET` requests"], +] + +```ts title="src/api/middlewares.ts" highlights={specificMethodHighlights} +import { applyLocale, defineMiddlewares } from "@medusajs/framework/http" + +export default defineMiddlewares({ + routes: [ + { + matcher: "/custom*", + method: ["GET"], + middlewares: [applyLocale], + }, + ], +}) +``` + + + +Learn more about middlewares in the [Middlewares](../middlewares/page.mdx) chapter. + + + +--- + +## How to Pass Locale in API Requests + +You can pass the locale in API requests to routes that support localization using either of the following methods: + +1. The `locale` query parameter +2. The `x-medusa-locale` request header + +The query parameter takes priority over the header if both are provided. + +The locale must follow the [IETF BCP 47 standard](https://gist.github.com/typpo/b2b828a35e683b9bf8db91b5404f1bd1), such as `en-US` for English (United States) or `fr-FR` for French (France). + + + +Refer to the [JS SDK reference](!resources!/js-sdk#localization-with-js-sdk) for details on how to pass locale. + + + +For example: + + + + +```bash +curl "http://localhost:9000/store/products?locale=fr-FR" \ +-H 'x-publishable-api-key: {your_publishable_api_key}' +``` + + + + +```bash +curl "http://localhost:9000/store/products" \ +-H 'x-publishable-api-key: {your_publishable_api_key}' \ +-H 'x-medusa-locale: fr-FR' +``` + + + + +The above examples retrieve products with their fields translated to French (France) if translations are available. If no translations exist for the requested locale, the original content stored in the data model is returned. + + + +Store API routes require a publishable API key in the request header. Learn more in the [Store API reference](!api!/store#publishable-api-key). + + + +--- + +## Access Request Locale in API Routes + +After applying the `applyLocale` middleware, you can access the request's locale from the `locale` property of the `MedusaRequest` object. + +For example: + +export const accessLocaleHighlights = [ + ["10", "req.locale", "Access the request's locale"], +] + +```ts title="src/api/custom/route.ts" highlights={accessLocaleHighlights} +import type { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const locale = req.locale + + // use locale to retrieve localized data... +} +``` + +The `req.locale` property contains the locale value from either the query parameter or the request header. If no locale is specified in the request, `req.locale` is `undefined`. + +### Retrieve Localized Data with Query + +To retrieve data models with translated fields, pass the `locale` option to [Query](/learn/fundamentals/module-links/query) when querying your data. + +For example, to retrieve products with translated names and descriptions: + +export const queryHighlights = [ + ["10", "locale", "Pass the request locale to retrieve localized data"], +] + +```ts title="src/api/store/products/route.ts" highlights={queryHighlights} +import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const query = req.scope.resolve("query") + + const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title", "description"], + options: { + locale: req.locale, + }, + }) + + res.json({ products }) +} +``` + +In this example, the products are retrieved with their `title` and `description` fields translated to the locale specified in the request. + +Learn more in the [Query](../../module-links/query/page.mdx#retrieve-localized-data) chapter. + +### Retrieve Localized Data for Custom Models + +You can also retrieve localized data for custom data models. Learn more in the [Translate Custom Data Models](!resources!/commerce-modules/translation/custom-data-models) guide. \ No newline at end of file diff --git a/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx b/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx index cc8f89cce2..6473922db7 100644 --- a/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx +++ b/www/apps/book/app/learn/fundamentals/data-models/write-migration/page.mdx @@ -200,43 +200,43 @@ const Post = model.define("post", { id: model.id().primaryKey(), title: model.text(), author: model.belongsTo(() => Author, { - mappedBy: "posts" - }) + mappedBy: "posts", + }), }) const Author = model.define("author", { id: model.id().primaryKey(), name: model.text(), posts: model.hasMany(() => Post, { - mappedBy: "author" - }) + mappedBy: "author", + }), }) ``` To create a migration that reflects this relationship in the database, you can create a migration file as follows: ```ts title="src/modules/blog/migrations/Migration202507021200_create_author_and_post.ts" -import { Migration } from "@medusajs/framework/mikro-orm/migrations"; +import { Migration } from "@medusajs/framework/mikro-orm/migrations" export class Migration20251230112505 extends Migration { override async up(): Promise { - this.addSql(`create table if not exists "author" ("id" text not null, "name" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "author_pkey" primary key ("id"));`); - this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_author_deleted_at" ON "author" ("deleted_at") WHERE deleted_at IS NULL;`); + this.addSql(`create table if not exists "author" ("id" text not null, "name" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "author_pkey" primary key ("id"));`) + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_author_deleted_at" ON "author" ("deleted_at") WHERE deleted_at IS NULL;`) - this.addSql(`create table if not exists "post" ("id" text not null, "title" text not null, "author_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "post_pkey" primary key ("id"));`); - this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_author_id" ON "post" ("author_id") WHERE deleted_at IS NULL;`); - this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_deleted_at" ON "post" ("deleted_at") WHERE deleted_at IS NULL;`); + this.addSql(`create table if not exists "post" ("id" text not null, "title" text not null, "author_id" text not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "post_pkey" primary key ("id"));`) + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_author_id" ON "post" ("author_id") WHERE deleted_at IS NULL;`) + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_deleted_at" ON "post" ("deleted_at") WHERE deleted_at IS NULL;`) - this.addSql(`alter table if exists "post" add constraint "post_author_id_foreign" foreign key ("author_id") references "author" ("id") on update cascade;`); + this.addSql(`alter table if exists "post" add constraint "post_author_id_foreign" foreign key ("author_id") references "author" ("id") on update cascade;`) } override async down(): Promise { - this.addSql(`alter table if exists "post" drop constraint if exists "post_author_id_foreign";`); + this.addSql(`alter table if exists "post" drop constraint if exists "post_author_id_foreign";`) - this.addSql(`drop table if exists "author" cascade;`); + this.addSql(`drop table if exists "author" cascade;`) - this.addSql(`drop table if exists "post" cascade;`); + this.addSql(`drop table if exists "post" cascade;`) } } @@ -251,16 +251,16 @@ Consider you have an existing `Post` data model, and you added a new `published_ You can create a migration file to add this new column as follows: ```ts title="src/modules/blog/migrations/Migration202507021300_add_published_at_to_post.ts" -import { Migration } from "@medusajs/framework/mikro-orm/migrations"; +import { Migration } from "@medusajs/framework/mikro-orm/migrations" export class Migration20251230113025 extends Migration { override async up(): Promise { - this.addSql(`alter table if exists "post" add column if not exists "published_at" timestamptz null;`); + this.addSql(`alter table if exists "post" add column if not exists "published_at" timestamptz null;`) } override async down(): Promise { - this.addSql(`alter table if exists "post" drop column if exists "published_at";`); + this.addSql(`alter table if exists "post" drop column if exists "published_at";`) } } @@ -275,16 +275,16 @@ Consider you have an existing `Post` data model, and you removed the `published_ You can create a migration file to remove this column as follows: ```ts title="src/modules/blog/migrations/Migration202507021400_remove_published_at_from_post.ts" -import { Migration } from "@medusajs/framework/mikro-orm/migrations"; +import { Migration } from "@medusajs/framework/mikro-orm/migrations" export class Migration20251230113125 extends Migration { override async up(): Promise { - this.addSql(`alter table if exists "post" drop column if exists "published_at";`); + this.addSql(`alter table if exists "post" drop column if exists "published_at";`) } override async down(): Promise { - this.addSql(`alter table if exists "post" add column if not exists "published_at" timestamptz null;`); + this.addSql(`alter table if exists "post" add column if not exists "published_at" timestamptz null;`) } } @@ -299,16 +299,16 @@ Consider you have an existing `Post` data model with a `title` column, and you r You can create a migration file to rename this column as follows: ```ts title="src/modules/blog/migrations/Migration202507021500_rename_title_to_headline_in_post.ts" -import { Migration } from "@medusajs/framework/mikro-orm/migrations"; +import { Migration } from "@medusajs/framework/mikro-orm/migrations" export class Migration20251230113214 extends Migration { override async up(): Promise { - this.addSql(`alter table if exists "post" rename column "title" to "headline";`); + this.addSql(`alter table if exists "post" rename column "title" to "headline";`) } override async down(): Promise { - this.addSql(`alter table if exists "post" rename column "headline" to "title";`); + this.addSql(`alter table if exists "post" rename column "headline" to "title";`) } } @@ -332,24 +332,24 @@ export const Post = model.define("post", { id: model.id().primaryKey(), headline: model.text().index(), author: model.belongsTo(() => Author, { - mappedBy: "posts" - }) + mappedBy: "posts", + }), }) ``` You can create a migration file to create this index as follows: ```ts title="src/modules/blog/migrations/Migration202507021600_create_index_on_headline_in_post.ts" -import { Migration } from "@medusajs/framework/mikro-orm/migrations"; +import { Migration } from "@medusajs/framework/mikro-orm/migrations" export class Migration20251230113322 extends Migration { override async up(): Promise { - this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_headline" ON "post" ("headline") WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_headline" ON "post" ("headline") WHERE deleted_at IS NULL;`) } override async down(): Promise { - this.addSql(`drop index if exists "IDX_post_headline";`); + this.addSql(`drop index if exists "IDX_post_headline";`) } } @@ -364,16 +364,16 @@ Consider you have an existing `Post` data model with an index on the `headline` You can create a migration file to drop this index as follows: ```ts title="src/modules/blog/migrations/Migration202507021700_drop_index_on_headline_in_post.ts" -import { Migration } from "@medusajs/framework/mikro-orm/migrations"; +import { Migration } from "@medusajs/framework/mikro-orm/migrations" export class Migration20251230113350 extends Migration { override async up(): Promise { - this.addSql(`drop index if exists "IDX_post_headline";`); + this.addSql(`drop index if exists "IDX_post_headline";`) } override async down(): Promise { - this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_headline" ON "post" ("headline") WHERE deleted_at IS NULL;`); + this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_post_headline" ON "post" ("headline") WHERE deleted_at IS NULL;`) } } diff --git a/www/apps/book/app/learn/fundamentals/generated-types/page.mdx b/www/apps/book/app/learn/fundamentals/generated-types/page.mdx index 257eb9ac3a..4f51bc37a1 100644 --- a/www/apps/book/app/learn/fundamentals/generated-types/page.mdx +++ b/www/apps/book/app/learn/fundamentals/generated-types/page.mdx @@ -23,13 +23,35 @@ Medusa automatically generates TypeScript types for: ## How to Trigger Type Generation? -The Medusa application generates these types automatically when you run the application with the `dev` command: + + +As of [Medusa v2.12.4](https://github.com/medusajs/medusa/releases/tag/v2.12.4), types are generated when you run the `build` command. Prior versions only generated types when running the `dev` command. + + + +The Medusa application generates these types automatically when you run the `build` or `dev` commands: ```bash npm2yarn +npm run build +``` + +So, if you add a new data model or module and you don't find it in auto-completion or type checking, you can run the `build` command to regenerate the types. + +### How to Generate Types for Local Plugins? + + + +This feature is available as of [Medusa v2.12.4](https://github.com/medusajs/medusa/releases/tag/v2.12.4). + + + +Local plugins are plugins installed in your Medusa application with the `plugin:develop` command. To generate types for those plugins, run the `dev` command in the Medusa application: + +```bash npm2yarn badgeLabel="Medusa Application" badgeColor="green" npm run dev ``` -So, if you add a new data model or module and you don't find it in auto-completion or type checking, you can run the `dev` command to regenerate the types. +Medusa will copy the generated types under the `.medusa/types` directory of the application to the local plugin's directory. --- diff --git a/www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx b/www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx index 05ee4776bc..0ace6e7281 100644 --- a/www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx +++ b/www/apps/book/app/learn/fundamentals/module-links/index-module/page.mdx @@ -840,3 +840,36 @@ export const retrieveBrandsWorkflow = createWorkflow( ``` This will retrieve all brands that are linked to at least one product. + +### Retrieve Localized Data + + + +To retrieve localized data for data models that have translations, pass an `options.locale` property to the first parameter of the `query.index` method: + +```ts highlights={[["5", "locale", "Pass the locale to retrieve localized data."]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title", "description"], + options: { + locale: "fr-FR", + }, +}) +``` + +The `options.locale` property is a string representing the locale code following the [IETF BCP 47 standard](https://gist.github.com/typpo/b2b828a35e683b9bf8db91b5404f1bd1). + +The returned products will have their `title` and `description` properties in French (`fr-FR`), if translations are available. + +Learn more in the [Translation Module](!resources!/commerce-modules/translation) documentation. \ No newline at end of file diff --git a/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx b/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx index 4bfd1169ef..841f928426 100644 --- a/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx +++ b/www/apps/book/app/learn/fundamentals/module-links/query/page.mdx @@ -854,6 +854,60 @@ In the example above, you retrieve only deleted posts by enabling the `withDelet --- +## Retrieve Localized Data + + + +To retrieve localized data for data models that have translations, pass an `options.locale` property to the first parameter of the `query.graph` method. + + + + +```ts highlights={[["5", "locale", "Pass the locale to retrieve localized data."]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title", "description"], + options: { + locale: "fr-FR", + }, +}) +``` + + + + +```ts highlights={[["5", "locale", "Pass the locale to retrieve localized data."]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title", "description"], + options: { + locale: "fr-FR", + }, +}) +``` + + + + +The `options.locale` property is a string representing the locale code following the [IETF BCP 47 standard](https://gist.github.com/typpo/b2b828a35e683b9bf8db91b5404f1bd1). + +The returned products will have their `title` and `description` properties in French (`fr-FR`), if translations are available. + +Learn more in the [Translation Module](!resources!/commerce-modules/translation) documentation. + +--- + ## Configure Query to Throw Error By default, if Query doesn't find records matching your query, it returns an empty array. You can configure Query to throw an error when no records are found. diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs index 4a3d9ae775..f1fc206ed6 100644 --- a/www/apps/book/generated/edit-dates.mjs +++ b/www/apps/book/generated/edit-dates.mjs @@ -65,7 +65,7 @@ export const generatedEditDates = { "app/learn/fundamentals/module-links/custom-columns/page.mdx": "2025-12-09T13:27:05.446Z", "app/learn/fundamentals/module-links/directions/page.mdx": "2025-03-17T12:52:06.161Z", "app/learn/fundamentals/module-links/page.mdx": "2025-11-28T13:45:06.742Z", - "app/learn/fundamentals/module-links/query/page.mdx": "2025-10-27T09:30:26.957Z", + "app/learn/fundamentals/module-links/query/page.mdx": "2026-01-06T10:34:40.077Z", "app/learn/fundamentals/modules/db-operations/page.mdx": "2025-10-28T16:02:06.265Z", "app/learn/fundamentals/modules/multiple-services/page.mdx": "2025-03-18T15:11:44.632Z", "app/learn/fundamentals/modules/page.mdx": "2025-12-23T07:00:14.586Z", @@ -121,10 +121,10 @@ export const generatedEditDates = { "app/learn/fundamentals/workflows/errors/page.mdx": "2025-04-25T14:26:25.000Z", "app/learn/fundamentals/api-routes/override/page.mdx": "2025-12-22T12:56:06.558Z", "app/learn/fundamentals/module-links/index/page.mdx": "2025-05-23T07:57:58.958Z", - "app/learn/fundamentals/module-links/index-module/page.mdx": "2025-11-26T11:20:25.961Z", + "app/learn/fundamentals/module-links/index-module/page.mdx": "2026-01-06T10:34:35.671Z", "app/learn/introduction/build-with-llms-ai/page.mdx": "2025-12-09T14:17:13.295Z", "app/learn/installation/docker/page.mdx": "2025-12-18T11:18:25.856Z", - "app/learn/fundamentals/generated-types/page.mdx": "2025-07-25T13:17:35.319Z", + "app/learn/fundamentals/generated-types/page.mdx": "2026-01-06T06:38:15.719Z", "app/learn/introduction/from-v1-to-v2/page.mdx": "2025-10-29T11:55:11.531Z", "app/learn/debugging-and-testing/debug-workflows/page.mdx": "2025-07-30T13:45:14.117Z", "app/learn/fundamentals/data-models/json-properties/page.mdx": "2025-07-31T14:25:01.268Z", @@ -137,5 +137,6 @@ export const generatedEditDates = { "app/learn/codemods/replace-imports/page.mdx": "2025-10-09T11:37:44.754Z", "app/learn/fundamentals/admin/translations/page.mdx": "2025-12-16T13:56:21.400Z", "app/learn/configurations/medusa-config/asymmetric-encryption/page.mdx": "2025-10-31T09:53:38.607Z", - "app/learn/best-practices/third-party-sync/page.mdx": "2025-12-03T11:48:58.209Z" + "app/learn/best-practices/third-party-sync/page.mdx": "2025-12-03T11:48:58.209Z", + "app/learn/fundamentals/api-routes/localization/page.mdx": "2026-01-06T10:34:44.853Z" } \ No newline at end of file diff --git a/www/apps/book/generated/sidebar.mjs b/www/apps/book/generated/sidebar.mjs index efc16cb0e7..cba4f7efcd 100644 --- a/www/apps/book/generated/sidebar.mjs +++ b/www/apps/book/generated/sidebar.mjs @@ -694,6 +694,16 @@ export const generatedSidebars = [ "chapterTitle": "3.6.11. Retrieve Custom Links", "number": "3.6.11." }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/learn/fundamentals/api-routes/localization", + "title": "Localization", + "children": [], + "chapterTitle": "3.6.12. Localization", + "number": "3.6.12." + }, { "loaded": true, "isPathHref": true, @@ -701,8 +711,8 @@ export const generatedSidebars = [ "path": "/learn/fundamentals/api-routes/override", "title": "Override API Routes", "children": [], - "chapterTitle": "3.6.12. Override API Routes", - "number": "3.6.12." + "chapterTitle": "3.6.13. Override API Routes", + "number": "3.6.13." } ], "chapterTitle": "3.6. API Routes", diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index 59045bef3a..06b827ad58 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -10390,6 +10390,169 @@ This file adds two API Routes: - A `POST` API route at `http://localhost:9000/hello-world`. +# Localization in API Routes + +In this chapter, you'll learn how to handle localization in API routes of your Medusa application to serve content in different languages. + +### Prerequisites + +- [Medusa v2.12.4 or later](https://github.com/medusajs/medusa/releases/tag/v2.12.4) +- [Translation Module Configured](https://docs.medusajs.com/resources/commerce-modules/translation#configure-translation-module/index.html.md) + +## Overview + +Localization in API routes allows you to serve translated content based on the user's preferred language. The Medusa application provides built-in support for handling locale information in API requests and retrieving localized data. + +When a locale is specified in a request, you can use it to retrieve translated versions of your data models' fields, providing a seamless multilingual experience for your users. + +Learn more about translation, how to manage translations, and how to translate custom data models in the [Translation Module documentation](https://docs.medusajs.com/resources/commerce-modules/translation/index.html.md). + +*** + +## Routes with Localization Enabled by Default + +The Medusa application automatically supports retrieving localized content from all routes under the `/store` prefix, including both core and custom store API routes. + +For example, the following store routes have localization enabled by default: + +- `/store/products` -> Get products with translated fields +- `/store/collections` -> Get collections with translated fields +- `/store/categories` -> Get categories with translated fields + +### Apply Localization to Custom Routes + +If you're creating custom API routes outside the `/store` prefix, you must manually apply the `applyLocale` [middleware](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md) to enable localization support. + +To apply the `applyLocale` middleware to all HTTP methods for a route, add it to the `src/api/middlewares.ts` file: + +```ts title="src/api/middlewares.ts" highlights={allMethodsHighlights} +import { applyLocale, defineMiddlewares } from "@medusajs/framework/http" + +export default defineMiddlewares({ + routes: [ + { + matcher: "/custom*", + middlewares: [applyLocale], + }, + ], +}) +``` + +This applies the `applyLocale` middleware to all routes matching `/custom*`, regardless of the HTTP method. + +Alternatively, you can apply the middleware only to specific HTTP methods using the `method` property: + +```ts title="src/api/middlewares.ts" highlights={specificMethodHighlights} +import { applyLocale, defineMiddlewares } from "@medusajs/framework/http" + +export default defineMiddlewares({ + routes: [ + { + matcher: "/custom*", + method: ["GET"], + middlewares: [applyLocale], + }, + ], +}) +``` + +Learn more about middlewares in the [Middlewares](https://docs.medusajs.com/learn/fundamentals/api-routes/middlewares/index.html.md) chapter. + +*** + +## How to Pass Locale in API Requests + +You can pass the locale in API requests to routes that support localization using either of the following methods: + +1. The `locale` query parameter +2. The `x-medusa-locale` request header + +The query parameter takes priority over the header if both are provided. + +The locale must follow the [IETF BCP 47 standard](https://gist.github.com/typpo/b2b828a35e683b9bf8db91b5404f1bd1), such as `en-US` for English (United States) or `fr-FR` for French (France). + +Refer to the [JS SDK reference](https://docs.medusajs.com/resources/js-sdk#localization-with-js-sdk/index.html.md) for details on how to pass locale. + +For example: + +### Query Parameter + +```bash +curl "http://localhost:9000/store/products?locale=fr-FR" \ +-H 'x-publishable-api-key: {your_publishable_api_key}' +``` + +### Header + +```bash +curl "http://localhost:9000/store/products" \ +-H 'x-publishable-api-key: {your_publishable_api_key}' \ +-H 'x-medusa-locale: fr-FR' +``` + +The above examples retrieve products with their fields translated to French (France) if translations are available. If no translations exist for the requested locale, the original content stored in the data model is returned. + +Store API routes require a publishable API key in the request header. Learn more in the [Store API reference](https://docs.medusajs.com/api/store#publishable-api-key). + +*** + +## Access Request Locale in API Routes + +After applying the `applyLocale` middleware, you can access the request's locale from the `locale` property of the `MedusaRequest` object. + +For example: + +```ts title="src/api/custom/route.ts" highlights={accessLocaleHighlights} +import type { + MedusaRequest, + MedusaResponse, +} from "@medusajs/framework/http" + +export const GET = async ( + req: MedusaRequest, + res: MedusaResponse +) => { + const locale = req.locale + + // use locale to retrieve localized data... +} +``` + +The `req.locale` property contains the locale value from either the query parameter or the request header. If no locale is specified in the request, `req.locale` is `undefined`. + +### Retrieve Localized Data with Query + +To retrieve data models with translated fields, pass the `locale` option to [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) when querying your data. + +For example, to retrieve products with translated names and descriptions: + +```ts title="src/api/store/products/route.ts" highlights={queryHighlights} +import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export const GET = async (req: MedusaRequest, res: MedusaResponse) => { + const query = req.scope.resolve("query") + + const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title", "description"], + options: { + locale: req.locale, + }, + }) + + res.json({ products }) +} +``` + +In this example, the products are retrieved with their `title` and `description` fields translated to the locale specified in the request. + +Learn more in the [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query#retrieve-localized-data/index.html.md) chapter. + +### Retrieve Localized Data for Custom Models + +You can also retrieve localized data for custom data models. Learn more in the [Translate Custom Data Models](https://docs.medusajs.com/resources/commerce-modules/translation/custom-data-models/index.html.md) guide. + + # Middlewares In this chapter, you’ll learn about middlewares and how to create them. @@ -15405,13 +15568,27 @@ Medusa automatically generates TypeScript types for: ## How to Trigger Type Generation? -The Medusa application generates these types automatically when you run the application with the `dev` command: +As of [Medusa v2.12.4](https://github.com/medusajs/medusa/releases/tag/v2.12.4), types are generated when you run the `build` command. Prior versions only generated types when running the `dev` command. + +The Medusa application generates these types automatically when you run the `build` or `dev` commands: ```bash npm2yarn +npm run build +``` + +So, if you add a new data model or module and you don't find it in auto-completion or type checking, you can run the `build` command to regenerate the types. + +### How to Generate Types for Local Plugins? + +This feature is available as of [Medusa v2.12.4](https://github.com/medusajs/medusa/releases/tag/v2.12.4). + +Local plugins are plugins installed in your Medusa application with the `plugin:develop` command. To generate types for those plugins, run the `dev` command in the Medusa application: + +```bash npm2yarn badgeLabel="Medusa Application" badgeColor="green" npm run dev ``` -So, if you add a new data model or module and you don't find it in auto-completion or type checking, you can run the `dev` command to regenerate the types. +Medusa will copy the generated types under the `.medusa/types` directory of the application to the local plugin's directory. *** @@ -16715,6 +16892,31 @@ export const retrieveBrandsWorkflow = createWorkflow( This will retrieve all brands that are linked to at least one product. +### Retrieve Localized Data + +### Prerequisites + +- [Medusa v2.12.4 or later](https://github.com/medusajs/medusa/releases/tag/v2.12.4) +- [Translation Module Configured](https://docs.medusajs.com/resources/commerce-modules/translation#configure-translation-module/index.html.md) + +To retrieve localized data for data models that have translations, pass an `options.locale` property to the first parameter of the `query.index` method: + +```ts highlights={[["5", "options", "Pass the locale to retrieve localized data."]]} +const { data: products } = await query.index({ + entity: "product", + fields: ["id", "title", "description"], + options: { + locale: "fr-FR", + }, +}) +``` + +The `options.locale` property is a string representing the locale code following the [IETF BCP 47 standard](https://gist.github.com/typpo/b2b828a35e683b9bf8db91b5404f1bd1). + +The returned products will have their `title` and `description` properties in French (`fr-FR`), if translations are available. + +Learn more in the [Translation Module](https://docs.medusajs.com/resources/commerce-modules/translation/index.html.md) documentation. + # Link @@ -18284,6 +18486,47 @@ In the example above, you retrieve only deleted posts by enabling the `withDelet *** +## Retrieve Localized Data + +### Prerequisites + +- [Medusa v2.12.4 or later](https://github.com/medusajs/medusa/releases/tag/v2.12.4) +- [Translation Module Configured](https://docs.medusajs.com/resources/commerce-modules/translation#configure-translation-module/index.html.md) + +To retrieve localized data for data models that have translations, pass an `options.locale` property to the first parameter of the `query.graph` method. + +### query.graph + +```ts highlights={[["5", "options", "Pass the locale to retrieve localized data."]]} +const { data: products } = await query.graph({ + entity: "product", + fields: ["id", "title", "description"], + options: { + locale: "fr-FR", + }, +}) +``` + +### useQueryGraphStep + +```ts highlights={[["5", "options", "Pass the locale to retrieve localized data."]]} +const { data: products } = useQueryGraphStep({ + entity: "product", + fields: ["id", "title", "description"], + options: { + locale: "fr-FR", + }, +}) +``` + +The `options.locale` property is a string representing the locale code following the [IETF BCP 47 standard](https://gist.github.com/typpo/b2b828a35e683b9bf8db91b5404f1bd1). + +The returned products will have their `title` and `description` properties in French (`fr-FR`), if translations are available. + +Learn more in the [Translation Module](https://docs.medusajs.com/resources/commerce-modules/translation/index.html.md) documentation. + +*** + ## Configure Query to Throw Error By default, if Query doesn't find records matching your query, it returns an empty array. You can configure Query to throw an error when no records are found. @@ -43176,6 +43419,301 @@ console.log(translations) ``` +# Translate Custom Data Models + +In this chapter, you'll learn how to support translations for your custom data models using the Translation Module. + +### Prerequisites + +- [Medusa v2.12.4 or later](https://github.com/medusajs/medusa/releases/tag/v2.12.4) +- [Translation Module Configured](https://docs.medusajs.com/commerce-modules/translation#configure-translation-module/index.html.md) + +## Summary + +The Translation Module allows you to extend translation capabilities to custom data models in your Medusa application. Then, you can [manage translations from the Medusa Admin](https://docs.medusajs.com/user-guide/settings/translations/index.html.md), and serve translated resources in your configured locales. + +By following this guide, you'll learn how to: + +- Configure the Translation Module to support translations for your custom data models. +- Manage translations for your custom data models from the Medusa Admin. +- Serve translated resources in your configured locales. + +*** + +## Prerequisites: Custom Data Model + +This guide assumes you already have a custom [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) with a data model. The guide will use a Blog Module with the following `Post` data model as an example: + +```ts title="src/modules/blog/models/post.ts" +import { model } from "@medusajs/framework/utils" + +const Post = model.define("post", { + id: model.id().primaryKey(), + title: model.text(), +}) + +export default Post +``` + +The module must also be registered in `medusa-config.ts`. For example: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + // other modules... + { + resolve: "./src/modules/blog", + }, + ], +}) +``` + +*** + +## Step 1: Configure Translatable Entities + +The first step is to configure the Translation Module with the custom entities you want to support translations for. + +Before proceeding, run the `build` command to ensure the generated types are up-to-date: + +```bash npm2yarn +npm run build +``` + +This will allow you to benefit from auto-completion when configuring the Translation Module. + +Next, in `medusa-config.ts`, add the `options.entities` property to the Translation Module configuration: + +```ts title="medusa-config.ts" highlights={configHighlights} +module.exports = defineConfig({ + // ... + modules: [ + // other modules... + { + resolve: "@medusajs/medusa/translation", + options: { + entities: [ + { + type: "post", + fields: ["title"], + }, + ], + }, + }, + ], +}) +``` + +The `options.entities` option is an array of objects indicating the custom data models to support translations for. Each object has the following properties: + +1. `type`: The name of the table where the custom data model is stored. This is the same value passed as the first parameter to `model.define`. +2. `fields`: An array of fields in the custom data model to support translations for. + +*** + +## Step 2: Manage Translations from Medusa Admin + +After configuring the Translation Module, you can manage translations for your custom data models from the Medusa Admin. + +Run the following command to start the Medusa application: + +```bash npm2yarn +npm run dev +``` + +Then, open the Medusa Admin and go to Settings -> Translations. You should see your custom data model in the list of translatable resources. + +![Post data model highlighted in the list of translatable resources in the Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1767687396/Medusa%20Resources/CleanShot_2026-01-06_at_10.12.09_2x_w1hmua.png) + +Click the Edit button for your custom data model to manage translations for its resources. You can manage translations for the configured locales and fields. + +Learn more in the [Translations User Guide](https://docs.medusajs.com/user-guide/settings/translations/index.html.md). + +*** + +## Step 3: Serve Translated Resources + +Finally, you can serve translated resources for your custom data models in your configured locales. This section focuses on returning records with translated fields from API routes. + +### Pass Locale in API Requests + +Medusa supports passing the desired locale in API requests to `/store` routes using either: + +1. The `locale` query parameter. +2. The `x-medusa-locale` header. + +For example, assuming you have a `/store/posts` API route, you can pass the locale in the request as follows: + +### Query Parameter + +```bash +curl "http://localhost:9000/store/posts?locale=fr-FR" \ +-H 'x-publishable-api-key: {your_publishable_api_key}' +``` + +### Request Header + +```bash +curl "http://localhost:9000/store/posts" \ +-H 'x-publishable-api-key: {your_publishable_api_key}' \ +-H 'x-medusa-locale: fr-FR' +``` + +You must pass a publishable API key in the request header to store API routes. Learn more in the [Store API reference](https://docs.medusajs.com/api/store#publishable-api-key). + +If your API route isn't under the `/store` prefix, you must apply the `applyLocale` middleware. For example, add the middleware to the `src/api/middlewares.ts` file: + +```ts title="src/api/middlewares.ts" +import { applyLocale, defineMiddlewares } from "@medusajs/framework/http" + +export default defineMiddlewares({ + routes: [ + { + matcher: "/posts", + middlewares: [applyLocale], + }, + ], +}) +``` + +This allows you to pass the locale in the query parameter or request header for the `/posts` API route. + +### Handle Translations in API Routes + +In your custom API routes, you can retrieve the request's locale from the `locale` property of the `MedusaRequest` object. Pass that property to [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) to retrieve your data models with translated fields. + +For example, to retrieve blog posts with translated titles in the `/store/posts` API route: + +```ts title="src/api/routes/store/posts.ts" highlights={apiRouteHighlights} +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export async function GET( + req: MedusaRequest, + res: MedusaResponse +) { + const query = req.scope.resolve("query") + + const { data: posts } = await query.graph({ + entity: "post", + fields: ["id", "title"], + options: { + locale: req.locale, + }, + }) + + res.json({ posts }) +} +``` + +In this example, the `locale` option is set to `req.locale`. Medusa will set the `title` field of each post to its translated value if a translation is available for the requested locale. Otherwise, it returns the original value stored in the data model. + +*** + +## Step 4: Test the Implementation + +To test the implementation, start the Medusa application if you haven't already: + +```bash npm2yarn +npm run dev +``` + +Then, send a request to your custom API route with the desired locale. For example: + +```bash +curl "http://localhost:9000/store/posts?locale=fr-FR" \ +-H 'x-publishable-api-key: {your_publishable_api_key}' +``` + +This should return the list of blog posts with their French translations for the `title` field if available: + +```json +{ + "posts": [ + { + "id": "post_123", + "title": "Titre de l'Article" + }, + { + "id": "post_456", + "title": "Un Autre Titre" + } + ] +} +``` + +*** + +## Pass Locale to useQueryGraphStep + +If your API route executes a workflow and returns its result, you can retrieve data models with translated fields in the workflow by passing the locale to `useQueryGraphStep`. + +For example: + +```ts title="src/workflows/handle-posts.ts" highlights={workflowHighlights} +import { createWorkflow, WorkflowResponse } from "@medusajs/framework/workflows-sdk" +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +type WorkflowInput = { + locale: string; +} + +export const handlePostsWorkflow = createWorkflow( + "handle-posts", + (input: WorkflowInput) => { + // do something... + + const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + options: { + locale: input.locale, + }, + }) + + return new WorkflowResponse({ + posts, + }) + } +) +``` + +In this example, the workflow accepts a `locale` input parameter. You then retrieve the posts with translated titles by passing the `locale` input to `useQueryGraphStep`. + +The returned posts will have their `title` field set to the translated value if a translation is available for the requested locale. + +You can execute the workflow in your custom API routes and pass the request locale as an input parameter: + +```ts title="src/api/routes/store/posts.ts" +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" +import { handlePostsWorkflow } from "../../workflows/handle-posts" + +export async function GET( + req: MedusaRequest, + res: MedusaResponse +) { + const { result } = await handlePostsWorkflow(req.scope) + .run({ + input: { + locale: req.locale, + }, + }) + + res.json(result) +} +``` + +The returned posts in the API response will have their `title` field set to the translated value if a translation is available for the requested locale. + +*** + +## Manage Translations with the Translation Module Service + +For more complex cases, you can manage translations for your custom data models programmatically using the Translation Module's service. You can create, update, delete, and retrieve translations of any resource using the service's methods. + +Refer to the [Translation Module service reference](https://docs.medusajs.com/references/translation/index.html.md) for a full list of available methods and how to use them. + + # Links between Translation Module and Other Modules This document showcases the module links that Medusa defines between the Translation Module and other Commerce Modules. @@ -43299,7 +43837,7 @@ In this section of the documentation, you will find resources to learn more abou ### Prerequisites - [Medusa v2.12.3 or later](https://github.com/medusajs/medusa/releases/tag/v2.12.3) -- [Translation Feature Flag Enabled](#) +- [Translation Feature Flag Enabled](#configure-translation-module) Refer to the [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/settings/translations/index.html.md) to learn how to manage translations in the dashboard. @@ -43311,6 +43849,7 @@ Refer to the [Module Isolation](https://docs.medusajs.com/docs/learn/fundamental - [Translation and Locale Management](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/translation/concepts/index.html.md): Manage locales and add translations for different resources in your store. - [Multi-Language Support](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/translation/storefront/index.html.md): Manage and serve resources like products in multiple languages to cater to a diverse customer base. +- [Translation for Custom Models](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/translation/custom-data-models/index.html.md): Extend translation capabilities to custom data models in your Medusa application. *** @@ -43471,9 +44010,25 @@ Refer to the [Workflows](https://docs.medusajs.com/docs/learn/fundamentals/workf ## Supported Module Translations -The Translation Module currently supports translations for all data models in the [Product Module](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/product/index.html.md), including products, product variants, and categories. +The Translation Module currently supports translations for the following data models: -Future versions of the Translation Module will include support for all Commerce Modules, as well as custom modules. +|Data Model|Translatable Fields| +|---|---| +|\`CustomerGroup\`|\`name\`| +|\`Product\`|| +|\`ProductCategory\`|| +|\`ProductCollection\`|\`title\`| +|\`ProductOption\`|\`title\`| +|\`ProductOptionValue\`|\`value\`| +|\`ProductTag\`|\`value\`| +|\`ProductType\`|\`value\`| +|\`ProductVariant\`|| +|\`Region\`|\`name\`| +|\`ShippingOption\`|\`name\`| +|\`ShippingOptionType\`|| +|\`TaxRate\`|\`name\`| + +Future versions of the Translation Module will include support for all Commerce Modules. *** @@ -43505,7 +44060,7 @@ You must pass a publishable API key in the request header to store API routes. L ## Retrieve Translations for Resources -Currently, you can retrieve translations using the [Store API routes](https://docs.medusajs.com/api/store) for product-related resources, such as products, product variants, and categories. Future releases will expand translation support to additional resources. +You can retrieve translations using the [Store API routes](https://docs.medusajs.com/api/store) for [supported](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/translation#supported-module-translations/index.html.md) and [custom](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/translation/custom-data-models/index.html.md) data models. Future releases will expand translation support to additional resources. Medusa determines the locale for the request in the following order of priority: @@ -43514,7 +44069,7 @@ Medusa determines the locale for the request in the following order of priority: If translations aren't available for the selected locale, or no locale is selected, the original content stored in the resource's data model is returned. -For example: +For example, to retrieve products with translations in French (France): ```bash curl "http://localhost:9000/store/products?locale=fr-FR" \ @@ -44607,7 +45162,7 @@ The Analytics Module and its providers are available starting [Medusa v2.8.3](ht ### Prerequisites - [PostHog account](https://app.posthog.com/signup) -- [PostHog API Key](https://posthog.com/docs/getting-started/api-key) +- [PostHog API Key](https://posthog.com/docs/api) Add the module into the `provider` object of the Analytics Module: diff --git a/www/apps/book/sidebar.mjs b/www/apps/book/sidebar.mjs index 196a6ad504..a0d8e72095 100644 --- a/www/apps/book/sidebar.mjs +++ b/www/apps/book/sidebar.mjs @@ -366,6 +366,11 @@ export const sidebars = [ path: "/learn/fundamentals/api-routes/retrieve-custom-links", title: "Retrieve Custom Links", }, + { + type: "link", + path: "/learn/fundamentals/api-routes/localization", + title: "Localization", + }, { type: "link", path: "/learn/fundamentals/api-routes/override", diff --git a/www/apps/resources/app/commerce-modules/translation/custom-data-models/page.mdx b/www/apps/resources/app/commerce-modules/translation/custom-data-models/page.mdx new file mode 100644 index 0000000000..f808b4b27d --- /dev/null +++ b/www/apps/resources/app/commerce-modules/translation/custom-data-models/page.mdx @@ -0,0 +1,327 @@ +import { Prerequisites, CodeTabs, CodeTab } from "docs-ui" + +export const metadata = { + title: `Translate Custom Data Models`, +} + +# {metadata.title} + +In this chapter, you'll learn how to support translations for your custom data models using the Translation Module. + + + +## Summary + +The Translation Module allows you to extend translation capabilities to custom data models in your Medusa application. Then, you can [manage translations from the Medusa Admin](!user-guide!/settings/translations), and serve translated resources in your configured locales. + +By following this guide, you'll learn how to: + +- Configure the Translation Module to support translations for your custom data models. +- Manage translations for your custom data models from the Medusa Admin. +- Serve translated resources in your configured locales. + +--- + +## Prerequisites: Custom Data Model + +This guide assumes you already have a custom [module](!docs!/learn/fundamentals/modules) with a data model. The guide will use a Blog Module with the following `Post` data model as an example: + +```ts title="src/modules/blog/models/post.ts" +import { model } from "@medusajs/framework/utils" + +const Post = model.define("post", { + id: model.id().primaryKey(), + title: model.text(), +}) + +export default Post +``` + +The module must also be registered in `medusa-config.ts`. For example: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + // other modules... + { + resolve: "./src/modules/blog", + }, + ], +}) +``` + +--- + +## Step 1: Configure Translatable Entities + +The first step is to configure the Translation Module with the custom entities you want to support translations for. + +Before proceeding, run the `build` command to ensure the generated types are up-to-date: + +```bash npm2yarn +npm run build +``` + +This will allow you to benefit from auto-completion when configuring the Translation Module. + +Next, in `medusa-config.ts`, add the `options.entities` property to the Translation Module configuration: + +export const configHighlights = [ + ["8", "entities", "The custom entities to support translations for."], + ["10", "type", "The name of the table for the custom data model."], + ["11", "fields", "The fields of the custom data model to support translations for."] +] + +```ts title="medusa-config.ts" highlights={configHighlights} +module.exports = defineConfig({ + // ... + modules: [ + // other modules... + { + resolve: "@medusajs/medusa/translation", + options: { + entities: [ + { + type: "post", + fields: ["title"], + }, + ], + }, + }, + ], +}) +``` + +The `options.entities` option is an array of objects indicating the custom data models to support translations for. Each object has the following properties: + +1. `type`: The name of the table where the custom data model is stored. This is the same value passed as the first parameter to `model.define`. +2. `fields`: An array of fields in the custom data model to support translations for. + +--- + +## Step 2: Manage Translations from Medusa Admin + +After configuring the Translation Module, you can manage translations for your custom data models from the Medusa Admin. + +Run the following command to start the Medusa application: + +```bash npm2yarn +npm run dev +``` + +Then, open the Medusa Admin and go to Settings -> Translations. You should see your custom data model in the list of translatable resources. + +![Post data model highlighted in the list of translatable resources in the Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1767687396/Medusa%20Resources/CleanShot_2026-01-06_at_10.12.09_2x_w1hmua.png) + +Click the Edit button for your custom data model to manage translations for its resources. You can manage translations for the configured locales and fields. + +Learn more in the [Translations User Guide](!user-guide!/settings/translations). + +--- + +## Step 3: Serve Translated Resources + +Finally, you can serve translated resources for your custom data models in your configured locales. This section focuses on returning records with translated fields from API routes. + +### Pass Locale in API Requests + +Medusa supports passing the desired locale in API requests to `/store` routes using either: + +1. The `locale` query parameter. +2. The `x-medusa-locale` header. + +For example, assuming you have a `/store/posts` API route, you can pass the locale in the request as follows: + + + +```bash +curl "http://localhost:9000/store/posts?locale=fr-FR" \ +-H 'x-publishable-api-key: {your_publishable_api_key}' +``` + + +```bash +curl "http://localhost:9000/store/posts" \ +-H 'x-publishable-api-key: {your_publishable_api_key}' \ +-H 'x-medusa-locale: fr-FR' +``` + + + + + +You must pass a publishable API key in the request header to store API routes. Learn more in the [Store API reference](!api!/store#publishable-api-key). + + + +If your API route isn't under the `/store` prefix, you must apply the `applyLocale` middleware. For example, add the middleware to the `src/api/middlewares.ts` file: + +```ts title="src/api/middlewares.ts" +import { applyLocale, defineMiddlewares } from "@medusajs/framework/http" + +export default defineMiddlewares({ + routes: [ + { + matcher: "/posts", + middlewares: [applyLocale], + }, + ], +}) +``` + +This allows you to pass the locale in the query parameter or request header for the `/posts` API route. + +### Handle Translations in API Routes + +In your custom API routes, you can retrieve the request's locale from the `locale` property of the `MedusaRequest` object. Pass that property to [Query](!docs!/learn/fundamentals/module-links/query) to retrieve your data models with translated fields. + +For example, to retrieve blog posts with translated titles in the `/store/posts` API route: + +export const apiRouteHighlights = [ + ["13", "locale", "Pass the request locale to the query options."], +] + +```ts title="src/api/routes/store/posts.ts" highlights={apiRouteHighlights} +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export async function GET( + req: MedusaRequest, + res: MedusaResponse +) { + const query = req.scope.resolve("query") + + const { data: posts } = await query.graph({ + entity: "post", + fields: ["id", "title"], + options: { + locale: req.locale, + }, + }) + + res.json({ posts }) +} +``` + +In this example, the `locale` option is set to `req.locale`. Medusa will set the `title` field of each post to its translated value if a translation is available for the requested locale. Otherwise, it returns the original value stored in the data model. + +--- + +## Step 4: Test the Implementation + +To test the implementation, start the Medusa application if you haven't already: + +```bash npm2yarn +npm run dev +``` + +Then, send a request to your custom API route with the desired locale. For example: + +```bash +curl "http://localhost:9000/store/posts?locale=fr-FR" \ +-H 'x-publishable-api-key: {your_publishable_api_key}' +``` + +This should return the list of blog posts with their French translations for the `title` field if available: + +```json +{ + "posts": [ + { + "id": "post_123", + "title": "Titre de l'Article" + }, + { + "id": "post_456", + "title": "Un Autre Titre" + } + ] +} +``` + +--- + +## Pass Locale to useQueryGraphStep + +If your API route executes a workflow and returns its result, you can retrieve data models with translated fields in the workflow by passing the locale to `useQueryGraphStep`. + +For example: + +export const workflowHighlights = [ + ["5", "locale", "Pass the locale to the workflow"], + ["17", "locale", "Pass the request locale to the query options."], +] + +```ts title="src/workflows/handle-posts.ts" highlights={workflowHighlights} +import { createWorkflow, WorkflowResponse } from "@medusajs/framework/workflows-sdk" +import { useQueryGraphStep } from "@medusajs/medusa/core-flows" + +type WorkflowInput = { + locale: string; +} + +export const handlePostsWorkflow = createWorkflow( + "handle-posts", + (input: WorkflowInput) => { + // do something... + + const { data: posts } = useQueryGraphStep({ + entity: "post", + fields: ["id", "title"], + options: { + locale: input.locale, + }, + }) + + return new WorkflowResponse({ + posts, + }) + } +) +``` + +In this example, the workflow accepts a `locale` input parameter. You then retrieve the posts with translated titles by passing the `locale` input to `useQueryGraphStep`. + +The returned posts will have their `title` field set to the translated value if a translation is available for the requested locale. + +You can execute the workflow in your custom API routes and pass the request locale as an input parameter: + +```ts title="src/api/routes/store/posts.ts" +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" +import { handlePostsWorkflow } from "../../workflows/handle-posts" + +export async function GET( + req: MedusaRequest, + res: MedusaResponse +) { + const { result } = await handlePostsWorkflow(req.scope) + .run({ + input: { + locale: req.locale, + }, + }) + + res.json(result) +} +``` + +The returned posts in the API response will have their `title` field set to the translated value if a translation is available for the requested locale. + +--- + +## Manage Translations with the Translation Module Service + +For more complex cases, you can manage translations for your custom data models programmatically using the Translation Module's service. You can create, update, delete, and retrieve translations of any resource using the service's methods. + +Refer to the [Translation Module service reference](/references/translation) for a full list of available methods and how to use them. \ No newline at end of file diff --git a/www/apps/resources/app/commerce-modules/translation/page.mdx b/www/apps/resources/app/commerce-modules/translation/page.mdx index 6c15ad0c67..13d022b633 100644 --- a/www/apps/resources/app/commerce-modules/translation/page.mdx +++ b/www/apps/resources/app/commerce-modules/translation/page.mdx @@ -2,7 +2,7 @@ generate_toc: true --- -import { CodeTabs, CodeTab, ChildDocs, Prerequisites } from "docs-ui" +import { CodeTabs, CodeTab, ChildDocs, Prerequisites, Table } from "docs-ui" export const metadata = { title: `Translation Module`, @@ -20,7 +20,7 @@ In this section of the documentation, you will find resources to learn more abou }, { text: "Translation Feature Flag Enabled", - link: "#" + link: "#configure-translation-module" } ]} /> @@ -43,6 +43,7 @@ Refer to the [Module Isolation](!docs!/learn/fundamentals/modules/isolation) gui - [Translation and Locale Management](./concepts/page.mdx): Manage locales and add translations for different resources in your store. - [Multi-Language Support](./storefront/page.mdx): Manage and serve resources like products in multiple languages to cater to a diverse customer base. +- [Translation for Custom Models](./custom-data-models/page.mdx): Extend translation capabilities to custom data models in your Medusa application. --- @@ -213,9 +214,134 @@ Refer to the [Workflows](!docs!/learn/fundamentals/workflows) documentation to l ## Supported Module Translations -The Translation Module currently supports translations for all data models in the [Product Module](../product/page.mdx), including products, product variants, and categories. +The Translation Module currently supports translations for the following data models: -Future versions of the Translation Module will include support for all Commerce Modules, as well as custom modules. + + + + + Data Model + + + Translatable Fields + + + + + + + `CustomerGroup` ([Customer Module](../customer/page.mdx)) ([+v2.12.4](https://github.com/medusajs/medusa/releases/tag/v2.12.4)) + + + `name` + + + + + `Product` ([Product Module](../product/page.mdx)) + + + - `title` + - `description` + - `material` + - `subtitle` + + + + + `ProductCategory` ([Product Module](../product/page.mdx)) + + + - `name` + - `description` + + + + + `ProductCollection` ([Product Module](../product/page.mdx)) + + + `title` + + + + + `ProductOption` ([Product Module](../product/page.mdx)) + + + `title` + + + + + `ProductOptionValue` ([Product Module](../product/page.mdx)) + + + `value` + + + + + `ProductTag` ([Product Module](../product/page.mdx)) + + + `value` + + + + + `ProductType` ([Product Module](../product/page.mdx)) + + + `value` + + + + + `ProductVariant` ([Product Module](../product/page.mdx)) + + + - `title` + - `material` + + + + + `Region` ([Region Module](../region/page.mdx)) ([+v2.12.4](https://github.com/medusajs/medusa/releases/tag/v2.12.4)) + + + `name` + + + + + `ShippingOption` ([Fulfillment Module](../fulfillment/page.mdx)) ([+v2.12.4](https://github.com/medusajs/medusa/releases/tag/v2.12.4)) + + + `name` + + + + + `ShippingOptionType` ([Fulfillment Module](../fulfillment/page.mdx)) ([+v2.12.4](https://github.com/medusajs/medusa/releases/tag/v2.12.4)) + + + - `label` + - `description` + + + + + `TaxRate` ([Tax Module](../tax/page.mdx)) ([+v2.12.4](https://github.com/medusajs/medusa/releases/tag/v2.12.4)) + + + `name` + + + +
+ +Future versions of the Translation Module will include support for all Commerce Modules. --- diff --git a/www/apps/resources/app/commerce-modules/translation/storefront/page.mdx b/www/apps/resources/app/commerce-modules/translation/storefront/page.mdx index 691647d871..ab99a31fe9 100644 --- a/www/apps/resources/app/commerce-modules/translation/storefront/page.mdx +++ b/www/apps/resources/app/commerce-modules/translation/storefront/page.mdx @@ -47,7 +47,7 @@ You must pass a publishable API key in the request header to store API routes. L ## Retrieve Translations for Resources -Currently, you can retrieve translations using the [Store API routes](!api!/store) for product-related resources, such as products, product variants, and categories. Future releases will expand translation support to additional resources. +You can retrieve translations using the [Store API routes](!api!/store) for [supported](../page.mdx#supported-module-translations) and [custom](../custom-data-models/page.mdx) data models. Future releases will expand translation support to additional resources. Medusa determines the locale for the request in the following order of priority: @@ -56,7 +56,7 @@ Medusa determines the locale for the request in the following order of priority: If translations aren't available for the selected locale, or no locale is selected, the original content stored in the resource's data model is returned. -For example: +For example, to retrieve products with translations in French (France): ```bash curl "http://localhost:9000/store/products?locale=fr-FR" \ diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs index 5b29f2fccf..bfcb4c8b2f 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -6743,8 +6743,8 @@ export const generatedEditDates = { "app/commerce-modules/store/locales/page.mdx": "2025-12-16T07:13:12.389Z", "app/commerce-modules/translation/concepts/page.mdx": "2025-12-16T07:13:50.326Z", "app/commerce-modules/translation/link-to-other-modules/page.mdx": "2025-12-10T13:23:44.262Z", - "app/commerce-modules/translation/page.mdx": "2025-12-10T15:31:44.251Z", - "app/commerce-modules/translation/storefront/page.mdx": "2025-12-16T07:15:02.192Z", + "app/commerce-modules/translation/page.mdx": "2026-01-06T08:32:43.208Z", + "app/commerce-modules/translation/storefront/page.mdx": "2026-01-06T08:50:03.221Z", "app/storefront-development/localization/page.mdx": "2025-12-16T07:18:51.976Z", "app/commerce-modules/translation/js-sdk/page.mdx": "2025-12-10T15:24:57.155Z", "app/commerce-modules/translation/workflows/page.mdx": "2025-12-10T15:24:45.190Z", @@ -6936,5 +6936,6 @@ export const generatedEditDates = { "references/js_sdk/admin/ShippingOptionType/properties/js_sdk.admin.ShippingOptionType.client/page.mdx": "2025-12-17T14:31:14.186Z", "app/how-to-tutorials/how-to/admin/auth/page.mdx": "2025-12-30T06:35:28.828Z", "app/integrations/guides/okta/page.mdx": "2025-12-30T06:35:28.653Z", - "app/nextjs-starter/guides/remove-country-code/page.mdx": "2025-12-30T10:28:10.072Z" + "app/nextjs-starter/guides/remove-country-code/page.mdx": "2025-12-30T10:28:10.072Z", + "app/commerce-modules/translation/custom-data-models/page.mdx": "2026-01-06T08:49:12.704Z" } \ No newline at end of file diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs index a7802720f0..6a91107551 100644 --- a/www/apps/resources/generated/files-map.mjs +++ b/www/apps/resources/generated/files-map.mjs @@ -663,6 +663,10 @@ export const filesMap = [ "filePath": "/www/apps/resources/app/commerce-modules/translation/concepts/page.mdx", "pathname": "/commerce-modules/translation/concepts" }, + { + "filePath": "/www/apps/resources/app/commerce-modules/translation/custom-data-models/page.mdx", + "pathname": "/commerce-modules/translation/custom-data-models" + }, { "filePath": "/www/apps/resources/app/commerce-modules/translation/js-sdk/page.mdx", "pathname": "/commerce-modules/translation/js-sdk" diff --git a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs index 41ee229343..213c2b61d1 100644 --- a/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs +++ b/www/apps/resources/generated/generated-commerce-modules-sidebar.mjs @@ -17223,6 +17223,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = { "title": "Concepts", "children": [] }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/commerce-modules/translation/custom-data-models", + "title": "Translate Custom Models", + "children": [] + }, { "loaded": true, "isPathHref": true, diff --git a/www/apps/resources/sidebars/translation.mjs b/www/apps/resources/sidebars/translation.mjs index 9ef60ef158..f54720806a 100644 --- a/www/apps/resources/sidebars/translation.mjs +++ b/www/apps/resources/sidebars/translation.mjs @@ -22,6 +22,11 @@ export const translationSidebar = [ path: "/commerce-modules/translation/concepts", title: "Concepts", }, + { + type: "link", + path: "/commerce-modules/translation/custom-data-models", + title: "Translate Custom Models", + }, { type: "link", path: "/commerce-modules/translation/storefront", diff --git a/www/apps/user-guide/app/settings/translations/page.mdx b/www/apps/user-guide/app/settings/translations/page.mdx index d106cd3e9f..a857e18775 100644 --- a/www/apps/user-guide/app/settings/translations/page.mdx +++ b/www/apps/user-guide/app/settings/translations/page.mdx @@ -36,21 +36,29 @@ You can set supported locales for your store in the [Store](../store/page.mdx#ma Then, in the storefront, customers can view content in their preferred language based on the available locales. -You can view and manage translations in the Medusa Admin dashboard by going to Settings → Translations. This page allows you to manage translations of resources in bulk. +### View and Manage Translations + +You can view and manage translations in the Medusa Admin dashboard by going to Settings → Translations. This page allows you to manage translations of core and custom resources in bulk. Alternatively, you can manage translations for individual resources directly from their respective pages (for example, from a product's detail page). ![Translations settings page](https://res.cloudinary.com/dza7lstvk/image/upload/v1765888654/User%20Guide/CleanShot_2025-12-16_at_14.36.56_2x_tqvst2.png) +### Translate Custom Resources + +Medusa's translation feature supports translating custom resources defined in your application. For example, if you have a blog feature, you can translate blog posts into multiple languages. + +To translate custom resources, your technical team needs to configure the Translation Module as explained in the [Translation for Custom Models](!resources!/commerce-modules/translation/custom-data-models) guide. + --- ## Manage Translations in Bulk -In the Translations settings page, you can view and manage translations in bulk for various resources in your store, including products and variants. +In the Translations settings page, you can view and manage translations in bulk for various core and custom resources in your store, including products and variants. -Currently, Medusa supports translations for product-related resources only. Future versions will include support for translating more core resources, as well as custom resources. +Refer to the [Translation Module](!resources!/commerce-modules/translation#supported-module-translations) guide for a list of core resources that support translations out-of-the-box. diff --git a/www/utils/packages/docs-generator/src/classes/kinds/oas.ts b/www/utils/packages/docs-generator/src/classes/kinds/oas.ts index 76dd5e910a..088f77c2e3 100644 --- a/www/utils/packages/docs-generator/src/classes/kinds/oas.ts +++ b/www/utils/packages/docs-generator/src/classes/kinds/oas.ts @@ -1377,7 +1377,7 @@ class OasKindGenerator extends FunctionKindGenerator { }), this.getParameterObject({ type: "header", - name: "Content-Language", + name: "x-medusa-locale", description: "The locale in BCP 47 format to retrieve localized content.", required: false,