Skip to content

Instantly share code, notes, and snippets.

@amannn
Last active October 13, 2023 18:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amannn/caf123668386aca41389b34bc68473dd to your computer and use it in GitHub Desktop.
Save amannn/caf123668386aca41389b34bc68473dd to your computer and use it in GitHub Desktop.
Compiler for collecting messages in Client Components

Notes:

  • The compiler looks for occurences of <NextIntlClientProvider messages="auto">
  • For every occurence, the module graph is traversed (static imports as well as lazy references), looking for client entry points.
  • The namespaces within client module graphs are collected (e.g. useTranslations('ns1'))
  • The provider is replaced with something like <NextIntlClientProvider messages={pick(useMessages(), ['ns1', 'ns2'])} />
  • <NextIntlClientProvider messages="auto"> must be rendered in a Server Component when messages="auto" is used
  • The compiler doesn't traverse nested segments. This is important, as it will not pull messages from segments that might not render into parent layouts.

Open questions:

  • When <NextIntlClientProvider messages="auto"> is used in a layout, do we auto detect sibling entry points (page, not-found, …) and traverse those too? Or should the provider be explicitly used in each file and we only follow module references? Consider that error.tsx must be a Client Component, therefore at least for this file it would be helpful if sibling entry points are traversed from a layout. Not sure. It would be helpful to not traverse page.tsx though, as it might not render. Maybe error.tsx is the only exception, as it's strongly coupled to the layout and will mount as part of the error boundary in any case? The other routes load/mount only on demand AFAIK (e.g. not-found).

Example structure:

app
  [locale]
    page.tsx
    layout.tsx
    Navigation.tsx
    C1.tsx
    C2.tsx

In this example the following namespaces are loaded on the provider: C1, C2.

// Usage of the provider, entry point for the compiler
import {NextIntlClientProvider} from 'next-intl';
import Navigation from './Navigation';
export default function LocaleLayout({children}) {
return (
<NextIntlClientProvider messages="auto">
<Navigation />
{children}
</NextIntlClientProvider>
);
}
// Server Component, nothing to do
import {useTranslations} from 'next-intl';
import Link from 'next/link';
export default function Navigation() {
const t = useTranslations('Navigation');
return <Link href="/">{t('home')}</Link>;
}
// Server Component, nothing to do
import {useTranslations} from 'next-intl';
import C1 from './C1';
export default function Page() {
const t = useTranslations('Page');
return <>
<h1>{t('title')}</h1>
<C1 />
</>;
}
// Client Component, start collecting namespaces here
'use client';
import {useState} from 'react';
import {useTranslations} from 'next-intl';
const C2 = lazy(() => import('./C2'));
export default function C1() {
const t = useTranslations('C1');
const [show, setShow] = useState(false);
return <>
<p>{t('title')}</p>
{show && <C2 />
</>;
}
// This component doesn't have a 'use client' banner, but is part of a client module graph
import {useTranslations} from 'next-intl';
export default function C2() {
const t = useTranslations('C2');
return t('title');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment