I want to declare i18next
translation keys as property values in an object and type it as a Record<string, SomeType>
in a way that it only accepts values that are valid i18next
translation keys (only leafs as I don't want it to accept keys for sections that have nested keys so that the result is always a string when I pass the key to t()
).
I have this code (using i18next
22.4 and react-i18next
12.2)
import { useTranslation } from 'react-i18next';
import type { TFuncKey } from 'i18next';
// A map of paths to translation keys
// I want TS to complain here if I try to use a string that
// doesn't match a translation key matching a string.
// This below works otherwise but `TFuncKey` is a bit too loose type as
// it also accepts e.g. 'Navigation` which results in an object, not a string.
const pageTitles: Record<string, TFuncKey> = {
'/': 'Navigation.home', // note: plain string values here, not calling `t()`
'/about': 'Navigation.about',
'/other': 'Navigation.other',
};
function useTitle(path: string): string {
const { t } = useTranslation();
return pageTitles[path] ? t(pageTitles[path]) : t('ErrorMessages.error');
// The previous line currently gives a TS error because t(pageTitles[path]) may
// return an object (not matching the return type of string in this method)
}
I can workaround the error by using t(pageTitles[path]) as string
but then it might break at runtime as the TFuncKey
type is a bit too loose and I can accidentally pass a value like ''Navigation''
which won't result in a string when passed to t()
.
If I have the map object inlined like this then the code works as intended:
function useTitle(path: string): string {
const { t } = useTranslation();
return {
'/': t('Navigation.home'), // note: calling `t()` here directly
'/about': t('Navigation.about'),
'/other': t('Navigation.other'),
}[path] ?? t('ErrorMessages.error');
}
but I'd like to refactor the map to be declared elsewhere (outside this React hook) where I can't use useTranslation()
as my actual code is a bit more complex (and has more routes) than the example here. So if possible I'd like to have it more like in the first example if it's possible to fix the typings so that TS is satisfied with it.
I'm not sure if I can use the generics of TFuncKey
or some other type from i18next
to solve my issue.
This problem will be solved just by upgrading to i18next
23.x.
The type TFuncKey
will need to be replaced with ParseKeys
as mentioned in the migration guide (the type was renamed).
I was originally using using i18next 22.4
(and react-i18next
12.2) but after upgrading to i18next
23.4 (and react-i18next
13.0) the code above works just as intended as it will complain if trying to use for example 'Navigation'
as a value in the pageTitles
object.
This is due to the new redesigned types in i18next
23 and the returnObjects
option which seems to globally default to false
(may also be set explicitly as an option per call of t()
).