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
This commit is contained in:
Leonardo Benini
2025-10-28 15:52:36 +01:00
committed by GitHub
parent 1594c13962
commit 0244f029aa
2 changed files with 22 additions and 5 deletions

View File

@@ -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}`)
}