Next.js Discord

Discord Forum

Question about internationalization

Unanswered
PepeW posted this in #help-forum
Open in Discord
I'm trying to figure out something about internationalization in NextJS.

The official doc is the following: https://nextjs.org/docs/app/building-your-application/routing/internationalization

Because for now I've only one language, I've done the following:
{
  "hello": {
    "world": "salut"
  },
}


// getTranslations.ts
const dictionaries = {
  fr: () => import('./fr.json').then((module) => module.default),
}
export const getTranslations = async () => dictionaries["fr"]?.()


// usage
export default async function Page() {
  const translations = await getTranslations()
  return (
    <div>{translations.hello.world}</div>
  )
}


The problem I have is that I can't really use this code on a client components because it will load all the json client side so it will increase the bundle size.

So instead of using .json file I switched to a .ts file containing a js object with all the translations:
export const translations = {
  hello: {
    world: "salut"
  },
}


// getTranslationsFromObj.ts
import { translations } from "@/locales/fr/fileobj";
export function getTranslationsFromObj() {
  return translations
}


// usage
export default async function Page() {
  const translations = getTranslationsFromObj()
  return (
    <div>{translations.hello.world}</div>
  )
}


Both methods works the same on server components.
My method seems to work well also on client components because I get the translation directly in the html (inspecting the network tab).
Also when I add a lot of translations in my object, the bundle size remains the same (doing a next build).

Because my solution works so well and is so simple I'm scared that I'm missing something. Any thoughts ?

6 Replies

Siberian Flycatcher
Hi 👋

You will load all translations to the JS bundle in both cases.

If you want to keep your bundle size minimal, try to load dictionaries on the server and pass a subset of a dictionary to a client component.

For example:

"use client"

function SubscribeForm({dict}) {
  return (
    <form>
      <label>{dict.username}</label>
      ...
    </form>
  )
}


async function Page({params: {lang}}) {
  const dict = await getDictionary(lang);
  return (
    ...
      <h1>{dict.hello}</h1>
      <SubscribeForm dict={dict.subscribeForm} />
    ...
  )
}
After further research you're right.

I guess I'll stick to what you said and what the doc said but passing translations as props for every client component is kind of annoying.
Siberian Flycatcher
Yep, it is a bit annoying right now.

RSC is a new paradigm. And currently, we have to rely on 3rd party libraries from the previous client-only paradigm, which is causing some friction.

I'd imagine with time, we will have more atomic client primitives directly from Next.js or from the community and less need to create complex, large client components ourselves. In this world, working with dictionaries will be much easier.

For example:

// Client components handling interactivity (loading states, error handling, etc...)
import { Form, FormField, FormSubmit, FormError } from 'next/forms' 

async function Page({params: {lang}}) {
  const dict = await getDictionary(lang);
  return (
    ...
      <h1>{dict.hello}</h1>
      <Form action={...}>
        <FormField name="username">{dict.subscribeForm.username}</FormField>
        <FormError name="username">{dict.subsrribeform.usernameError}</FormError>
        ...
        <FormSubmit
          loadingState={dict.subscribeForm.loading}>
          {dict.subscribeForm.submit}
        </FormSubmit>
      </Form>
    ...
  )
}
Mmmh I found something interesting.

I modified my getTranslations() function to be exactly like the doc and then did a next build. Here's the result:

And when I compared with the result using my getTranslationsFromObj() I found that the builds are the same size.
So I guess reading keys synchronously from an object in a client component doesn't increase the bundle size 🧐
Ok I get it.

Basically my method reading from an object is valid and works inside client components without increasing the bundle size. BUT it only works because for now I only have one locale.

If I have multiple locales, I need to get the lang in the url so I need to use usePathname(). And if I do this, now, the bundle size is increased because my function getTranslationsFromObj() needs to wait for the usePathname() which is executed purely client side.

So to be future proof I'll stick to the official NextJS doc.