Open cglacet opened 6 months ago
I've tested to pass the locale I have to the getScopedI18n
and it seems to work, its a hack that is very ugly as a end user but I wanted to make sure that was the only issue here. So yes, it just seems like the locale isn't correctly set by setStaticParamsLocale
in that particular case.
To test this on my side I did this patch directly in the dist folder your package exposes:
function createGetScopedI18n(locales, config) {
- return function getScopedI18n(scope) {
+ return function getScopedI18n(scope, userLocale = null) {
return __async(this, null, function* () {
- const locale = getLocaleCache();
+ const locale = userLocale ? userLocale : getLocaleCache();
return createT(
{
localeContent: flattenLocale((yield locales[locale]()).default),
fallbackLocale: config.fallbackLocale ? flattenLocale(config.fallbackLocale) : void 0,
locale
},
scope
);
});
};
}
With this change I can now use the following:
// File app/user/[slug]/route.tsx
import { ImageResponse } from "next/og";
import { getScopedI18n } from "locales/server";
export async function GET(_: Request, { params }: { slug: string, locale: string }) {
// @ts-ignore
const t = await getScopedI18n("user-profile", params.locale);
return new ImageResponse(<p>{t("user-name", { slug })}</p>);
}
Ideally I guess it would be better to have setStaticParamsLocale
to work, I tried reading the code but there is too much I don't understand to have any chance in finding the issue.
Another thing I tried that also work (no idea if it makes sense) but I can also set the cookie that is internally used by getLocaleCache
:
cookies().set("Next-Locale", params.locale);
Which also solves the issue but its not ideal to have this strange line of code.
The complete code:
// File app/user/[slug]/route.tsx
import { ImageResponse } from "next/og";
import { getScopedI18n } from "locales/server";
export async function GET(_: Request, { params }: { slug: string, locale: string }) {
cookies().set("Next-Locale", params.locale);
const t = await getScopedI18n("user-profile");
return new ImageResponse(<p>{t("user-name", { slug })}</p>);
}
I've noticed next-intl did use the first strategy (adding an optional argument that can force the locale):
import {NextResponse} from 'next/server';
import {getTranslations} from 'next-intl/server';
export async function GET(request) {
// Example: Receive the `locale` via a search param
const {searchParams} = new URL(request.url);
const locale = searchParams.get('locale');
const t = await getTranslations({locale, namespace: 'Hello'});
return NextResponse.json({title: t('title')});
}
Eventhough they use the same strategy as you for server components, so maybe they had a similar issue?
import {getTranslations} from 'next-intl/server';
export default async function ProfilePage() {
const user = await fetchUser();
const t = await getTranslations('ProfilePage');
return (
<PageLayout title={t('title', {username: user.name})}>
<UserDetails user={user} />
</PageLayout>
);
}
Very interesting use-case. There are two possibilities:
A) params.locale
is undefined, so setStaticParamsLocale
will set an undefined locale
B) setStaticParamsLocale
doesn't work in this case
A is more likely, since your handler is located in app/user/[slug]/route.tsx
, which only have a [slug]
dynamic param but no [locale]
dynamic param. You could do the same as what next-intl recommends, and I think we should also document it properly.
The reason why it doesn't work when building the app without using cookies
or headers
is that Next.js will by default try to statically render the route handler, if it doesn't depends on any dynamic input (specifically cookies, headers, url params...).
For example, this works in dev but not in build:
export async function GET(request: NextRequest) {
const t = await getI18n();
return NextResponse.json({ hello: t('hello') });
}
But this works both in dev and in build:
export async function GET(request: NextRequest) {
request.cookies;
const t = await getI18n();
return NextResponse.json({ hello: t('hello') });
}
Ah, sorry, I made a mistake while modifying the path to post this message, my actual file is in the [locale]
subtree, so A) is not the issue.
I confirm that simply adding a reference to request.cookies
does also solve the issue. That's a bit weird tho, because its still generated statically (at build time). Also the handler without the cookie is still dependent on the params, and both the slug
and locale
are properly set (I added a console log in the GET route and it prints the expected value pairs).
I tried calling setStaticParamsLocale
with this modifications:
// node_modules/next-international/dist/app/server/index.js
var getStaticParamsLocale = () => {
console.log("getStaticParamsLocale", getLocale().current);
getLocale().current;
};
var setStaticParamsLocale = (value) => {
console.log("setStaticParamsLocale", value);
getLocale().current = value;
};
It prints the correct locale in both get
and set
with the request.cookies
line, but get
prints undefined if I remove the request.cookies
.
So I guess you are completely right and all of this is just an issue with Next build process trying to be smart (but failing?).
After reading the doc a bit I think the most likely suggestion you can make in your own doc is to simply include the "force-dynamic"
settings in route handlers that are using translations and generateStaticParams
:
// File app/[locale]/user/[slug]/route.tsx
import { ImageResponse } from "next/og";
import { setStaticParamsLocale } from "next-international/server";
import { getScopedI18n } from "locales/server";
+export const dynamic = "force-dynamic";
export async function GET(_: Request, { params }: { slug: string, locale: string }) {
setStaticParamsLocale(params.locale);
const t = await getScopedI18n("user-profile");
return new ImageResponse(<p>{t("user-name", { slug })}</p>);
}
In case someone else is comming here, my real use case is to generate opengraph images, this use case might be something several other users of your package might need because there is currenly a bug in Next with opengraph image static generation: OpenGraph images are not statically generated for dynamic routes.
The current patch that seems to work fine is to create a route handler to generate the images and manually add a reference to these images in the metadata as suggested here.
I think it would be a good idea to add a documentation page about this matter (export const dynamic = "force-dynamic";
).
EDIT I understand it now, the pages were rendered at build time because I had generateStaticParams
declared, so it built dynamic pages at build time, these page were then all served from cache (which sounds a lot like a static page, but not quite in the sense of Nextjs).
I think I'm still not getting it right, using export const dynamic = "force-dynamic";
does indeed render the page at build time, but the result is not kept in cache. The route needs to be requested once before its added in the cache. I'm still investigating a better solution for this.
For now, the only solution I found that actually render the route at build time and serves a cached version for the first incomming request is to add an optional locale
argument to getI18n
and getScopedI18n
.
Saddly, export const dynamic = "force-dynamic";
might not be the answer.
Another use case here, also inside an app router route : rendering several emails (with react-email), which don't have the same language. What I do for now, which works, but does not seem a proper way to do this :
for (const entry of entries) {
cookies().set('Next-Locale', entry.language)
const t = await getScopedI18n('scope')
const html = await renderAsync(await Email())
const options = {
to: 'sender@email.com',
subject: t('emailSubject'),
html,
}
await sendEmail(options)
}
Using setStaticParamsLocale doesn't work.
Describe the bug Locale doesn't seem to be properly set on route handlers (app directory)
To Reproduce Create a route handler and try to retrieve the translation for it, for example:
If you build a project with this route, you will get the following error:
Expected behavior Translation data should be available. I have the requested locale in my hands so its a bit frustrating to not have access to a variant of
getScopedI18n
where I could simply give the locale as parameter.About
Node
v18.18.2
, dependencies: