I'll do my best to explain what I need.
If you're not already familiar with next-intl
, It's a package that provides internationalization for Next.js apps.
The developer of next-intl working on a beta version of next-intl that supports the app router of next.js. I installed this beta version next-intl@3.0.0-beta.16
. Up to this point, everything is ok.
The problem is that next-intl doesn't work in async components. and devs have to move the calling of the useTranslations
hook to another sync component.
I use async components for everything almost. Instead of moving the all texts into other components. I've built a reusable component <T />
(stands for Text or Translation). and want to pass the translation key
as a prop. like <T k="home.hero.title" />
(k
because key
is already reserved).
Assuming the translation file is like this:
{
"hello-world": "Hello World",
"home": {
"title": "Acme",
"hero": {
"description": "whatever"
}
},
"login": {
"button": "Login"
}
}
components/translation.tsx
import { useTranslations } from "next-intl";
export function T({ k }: {
k: TYPEME_LATER;
}) {
const translate = useTranslations();
return translate(k);
}
To keep you on the scene, my all question is about the TYPEME_LATER
.
import { useTranslations } from "next-intl";
type TranslationKey = Parameters<ReturnType<typeof useTranslations>>[0];
export function T({ k }: {
k: TranslationKey;
}) {}
But this way, Typescript suggests only nested keys like title
, hero.description
, button
, and hello-world
(take a quick look at the previous JSON snippet). but, by this way the next-intl won't find these keys, because it misses the scope home
, login
.
import { useTranslations } from "next-intl";
type useTranslationsParam = Parameters<typeof useTranslations>[0];
type TranslationKey = Parameters<ReturnType<typeof useTranslations>>[0];
export function T({
scope,
k,
}: {
scope: useTranslationsParam;
k: TranslationKey;
}) {
const translate = useTranslations(scope);
return translate(k);
}
like the magic, that worked as expected. Now I can do this anywhere:
export default async function Page() {
return <>
<T scope="home" k="title" />
</>
}
It renders as expected. but if you go back to the TranslationKey
type declaration, You can see that it accepts any key regardless of the scope. This might lead to a bug when writing a key that doesn't exist in the scope.
My question is I want to make the k
suggest only the keys within the scope. or if there is another approach let me know.
Thanks in advance for your interest. Your help is appreciated.
next-intl@3.1
came with support async components using await getTranslations(namespace?)
export async function Page() {
const t = await getTranslations("home");
return <div>{t("title")}</div>
}
For anyone trying to build the same concept, this is my end implementation:
import { useTranslations } from "next-intl";
interface Props {
/** Message key */
path: Paths<IntlMessages>;
}
export function Text({ path: key }: Props) {
const translate = useTranslations();
return translate(key);
}
type Paths<Schema, Path extends string = ""> = Schema extends string
? Path
: Schema extends object
? {
[K in keyof Schema & string]: Paths<
Schema[K],
`${Path}${Path extends "" ? "" : "."}${K}`
>;
}[keyof Schema & string]
: never;