Next.js Discord

Discord Forum

Internationalization in client components (app directory)

Unanswered
Bracco Italiano posted this in #help-forum
Open in Discord
Bracco ItalianoOP
Hello,

I'm having architectural issues with internationalization.

I'm using Next.js 13 with app directory.

On server components, everything works thanks to a getDictionary function I made.

But for client components, I have multiple choices:
1. Make getDictionnary a client function (currently, it's server only), but
- Clients will download all the translation keys and strings
- Clients will see all the translation strings, including private pages they don't have access to

2. Keep getDictionnary a server-only function and pass the keys as props from server component to client component, but
- Some components have a lot of strings, like forms that have more than 20 strings; it's time consuming and not a good developer experience to pass all the strings in props

What do you recommend? Do you have another idea?

5 Replies

Spectacled bear
Same issue here
Siberian Flycatcher
Some components have a lot of strings, like forms that have more than 20 strings; it's time consuming and not a good developer experience to pass all the strings in props

Is there a reason you want the entire form to be a client component?

Can you do something like this?

import { Form, FormInput } from './client'
  
async function Page({params}) {
  const dict = await getDictionary(params.lang)
  return (
    <Form>
      <label>{dict.name}</label>
      <FormInput />
      <label>{dict.surname}</label>
      <FormInput />  
    </Form>
  )
}
@Siberian Flycatcher > Some components have a lot of strings, like forms that have more than 20 strings; it's time consuming and not a good developer experience to pass all the strings in props Is there a reason you want the entire form to be a client component? Can you do something like this? javascript import { Form, FormInput } from './client' async function Page({params}) { const dict = await getDictionary(params.lang) return ( <Form> <label>{dict.name}</label> <FormInput /> <label>{dict.surname}</label> <FormInput /> </Form> ) }
Bracco ItalianoOP
Thanks for your answer 😊
I'm using zod + react-hook-form, that's why my whole form is client side.
[...]

export function LoginForm({ labels }: LoginFormProps) {
    [...]

    const form = useForm<z.infer<typeof loginSchema>>({
        resolver: zodResolver(loginSchema),
        defaultValues: {
            email: '',
            password: ''
        }
    });

    [...]

    return (
        <Form {...form}>
            <form className="space-y-4" onSubmit={form.handleSubmit(onSubmit)}>
                <FormField
                    control={form.control}
                    name="email"
                    render={({ field }) => (
                        <FormItem>
                            <FormLabel htmlFor="email">{labels.email_input_label}</FormLabel>
                            <FormControl>
                                <Input
                                    id="email"
                                    placeholder={labels.email_input_placeholder}
                                    autoComplete="email"
                                    {...field}
                                />
                            </FormControl>
                            <FormMessage />
                        </FormItem>
                    )}
                />
                <FormField
                    control={form.control}
                    name="password"
                    render={({ field }) => (
                        <FormItem>
                            <div className="flex items-center justify-between">
                                <FormLabel htmlFor="password">
                                    {labels.password_input_label}
                                </FormLabel>
                                <Link
                                    href="/reset-password"
                                    className="text-xs text-primary hover:underline"
                                >
                                    {labels.recover_password_link_label}
                                </Link>
                            </div>

                            <FormControl>
                                <Input
                                    id="password"
                                    type="password"
                                    placeholder={labels.password_input_placeholder}
                                    autoComplete="password"
                                    {...field}
                                />
                            </FormControl>
                            <FormMessage />
                        </FormItem>
                    )}
                />
                <div className="flex justify-center">
                    <Button type="submit">
                        {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
                        {labels.login_button_label}
                    </Button>
                </div>
            </form>
        </Form>
    );
}

Components and this architecture pattern comes from shadcn: https://ui.shadcn.com/docs/components/form

With your example, how do you handle the onSubmit callback?
Spectacled bear
Any update?
You can have an entry in your dictionaries for those form strings and if you're worried about protecting some of them you can sub-divide them into two more objects, a public one and a private one. then based on the auth state only pass one of those two objects or both.

Something like:
// dictionaries/en.json
{
  // ...
  "Form": {
    "public": {
      // ...
    },
    "private": {
      // ...
    }
  },
  // ...
}


Ant then from your server component:
import { type Locale } from './types'
import { Form } from './client'

async function Page({ params }: { params: { lang: Locale } }) {
  const dictionary = await getDictionary(params.lang)
  const user = getUser()

  return (
    <Form
      strings={
        user
          ? { ...dictionary.Form.public, ...dictionary.Form.private }
          : dictionary.Form.public
      }
    />
  )
}