Skip to content

Commit 38f61e9

Browse files
authored
docs: fix documentation about custom i18n types (#11386)
Fixes #9858 # The problems There were several issues with custom i18n typing in the documentation that were not detected because they did not occur in non-strict ts mode. 1. `Config['i18n']['translations']` didn't work, because i18n is an optional property. As described in [#9858](#9858 (comment)), some users were getting around this with `NonNullable<Config['i18n']>['translations']` 2. [The trick being attempted in `i18n`](https://github.com/payloadcms/payload/blob/36e152d69d784c45a60ef925186c15637f7939e6/packages/payload/src/config/types.ts#L1034) to customize and extend the `DefaultTranslationObject` does not work. `i18n?: I18nOptions<{} | DefaultTranslationsObject> // loosen the type here to allow for custom translations`. If you want to verify this, you can use the following code example: ```ts import type { Config } from 'payload' const translation: NonNullable<Config['i18n']>['translations'] = { en: { authentication: { aaaaa: 'aaaaa', // I chose `authentication.aaaa` to appear first in intellisense } }, } translation.en?.authentication // Property 'authentication' does not // exist on type '{} | { authentication: { account: string... // so this option doesn't let you access the keys because of the join with `{}`, // and even if it did, it's not adding `aaaa` as a key. ``` 3. In places where the `t` function is exposed in a callback, you cannot do what the documentation says: `{ t }: { t: TFunction<CustomTranslationsKeys | DefaultTranslationKeys> }` The reason for this is that the callback is exposed as a `LabelFunction` type but without type arguments, and as a default it uses `DefaultTranslationKeys`, which does not allow additional keys. If you want to verify this, you can use the following code example: ```ts // Make sure to test this with ts in strict mode const _labelFn: LabelFunction = ({ t }: { t: TFunction<'extraKey' | DefaultTranslationKeys> }) => "" // Type '"extraKey"' is not assignable to type // '"authentication:account" | ... 441 more ... | "version:versionCount"'. ``` # The solution Point 1 is a documentation issue. We could use `NonNullable`, or expose the `I18nOptions` type, or simply not define the custom translation type (which makes sense because if you put it in the config, ts will warn you anyway). Points 2 and 3 should ideally be corrected at the type level, but it would imply a breaking change. For now, I have corrected them at the documentation level, using an alternative for point 2 and a type cast for point 3. Maybe in payload v4 we should revisit this.
1 parent ac1e3cf commit 38f61e9

File tree

1 file changed

+10
-5
lines changed

1 file changed

+10
-5
lines changed

docs/configuration/i18n.mdx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,15 @@ In order to use [Custom Translations](#custom-translations) in your project, you
188188

189189
Here we create a shareable translations object. We will import this in both our custom components and in our Payload config.
190190

191+
In this example we show how to extend English, but you can do the same for any language you want.
192+
191193
```ts
192194
// <rootDir>/custom-translations.ts
193195

194-
import type { Config } from 'payload'
196+
import { enTranslations } from '@payloadcms/translations/languages/en'
195197
import type { NestedKeysStripped } from '@payloadcms/translations'
196198

197-
export const customTranslations: Config['i18n']['translations'] = {
199+
export const customTranslations = {
198200
en: {
199201
general: {
200202
myCustomKey: 'My custom english translation',
@@ -205,7 +207,7 @@ export const customTranslations: Config['i18n']['translations'] = {
205207
},
206208
}
207209

208-
export type CustomTranslationsObject = typeof customTranslations.en
210+
export type CustomTranslationsObject = typeof customTranslations.en & typeof enTranslations
209211
export type CustomTranslationsKeys = NestedKeysStripped<CustomTranslationsObject>
210212
```
211213
@@ -259,7 +261,10 @@ const field: Field = {
259261
name: 'myField',
260262
type: 'text',
261263
label: (
262-
{ t }: { t: TFunction<CustomTranslationsKeys | DefaultTranslationKeys> }, // The generic passed to TFunction does not automatically merge the custom translations with the default translations. We need to merge them ourselves here
263-
) => t('fields:addLabel'),
264+
{ t: defaultT }
265+
) => {
266+
const t = defaultT as TFunction<CustomTranslationsKeys>
267+
return t('fields:addLabel')
268+
},
264269
}
265270
```

0 commit comments

Comments
 (0)