From 0244f029aae2fc077d51df277aab5b6564f364a6 Mon Sep 17 00:00:00 2001 From: Leonardo Benini Date: Tue, 28 Oct 2025 15:52:36 +0100 Subject: [PATCH] feat(dashboard): type-safe i18n for UI extensions (#13851) ## Summary **What** Exports the type of medusa's i18n keys from the dashboard package, for autocomplete support. **Why** Currently i18next is not able to provide proper ts autocomplete support when adding translations to projects or plugins. For example when adding a data table or form, I have to go back multiple times to the source i18n files in the medusa repo and search for the exact key name(is it "actions.confirm" or "general.confirm"? etc) and I forget it right after every single time. Even if all medusa components were provided already translated, it's still very convinient to use keys from medusa(if context appropriate) for some custom components, since they are already translated into many languages(eg "yes", "no" and other very basic strings). Hence why ts support for the base keys would be very helpful **How** Modified the generateTypes script to also copy the `en.json` file to the dist folder, and export a `Resources` type with the inferred keys in index.d.ts. This allows users to define their own i18next type definitions including the base "translation" namespace, example below. **Testing** Manual testing --- ## Examples ```ts // In src/admin/i18next.d.ts import type enTranslation from "./i18n/en.json" // custom keys import type { Resources } from "@medusajs/dashboard" // medusa keys declare module "i18next" { interface CustomTypeOptions { fallbackNS: "translation" resources: { translation: Resources["translation"] // all custom namespaces need to merge Resources["translation"] as well // otherwise when falling back to "translation", strings will have type "never" myCustomNs: typeof enTranslation & Resources["translation"] } } } ``` Then, both `useTranslation()` and `useTranslation("myCustomNs")` will have proper autocomplete support --- ## Checklist Please ensure the following before requesting a review: - [x] 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 - [x] The changes are covered by relevant **tests** - [x] I have verified the code works as intended locally - [x] I have linked the related issue(s) if applicable --- .changeset/sour-camels-live.md | 5 +++++ .../admin/dashboard/scripts/generate-types.js | 22 ++++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 .changeset/sour-camels-live.md diff --git a/.changeset/sour-camels-live.md b/.changeset/sour-camels-live.md new file mode 100644 index 0000000000..30d561934c --- /dev/null +++ b/.changeset/sour-camels-live.md @@ -0,0 +1,5 @@ +--- +"@medusajs/dashboard": patch +--- + +feat(dashboard): type-safe i18n for UI extensions diff --git a/packages/admin/dashboard/scripts/generate-types.js b/packages/admin/dashboard/scripts/generate-types.js index 18e154396f..5b9fb7e1bf 100644 --- a/packages/admin/dashboard/scripts/generate-types.js +++ b/packages/admin/dashboard/scripts/generate-types.js @@ -11,12 +11,17 @@ async function generateTypes() { const filePath = path.join(distDir, "index.d.ts") const fileContent = ` - declare function App(props: { - plugins?: any[] - }): JSX.Element +declare function App(props: { + plugins?: any[] +}): JSX.Element - export default App - ` +export default App + +import type enTranslation from "./en.json" +export type Resources = { + translation: typeof enTranslation +} +` // Ensure the dist directory exists if (!fs.existsSync(distDir)) { @@ -26,6 +31,13 @@ async function generateTypes() { // Write the content to the index.d.ts file fs.writeFileSync(filePath, fileContent.trim(), "utf8") + // Copy the canonical en translation for type inference + const enTranslationSrcPath = path.join( + __dirname, "../src/i18n/translations/en.json" + ) + const enTranslationDistPath = path.join(distDir, "en.json") + fs.copyFileSync(enTranslationSrcPath, enTranslationDistPath) + console.log(`File created at ${filePath}`) }