Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow overriding locale context for specific components with useTranslation/getTranslations #1474

Closed
rickvdbroek opened this issue Oct 27, 2024 · 5 comments
Labels
enhancement New feature or request unconfirmed Needs triage.

Comments

@rickvdbroek
Copy link

rickvdbroek commented Oct 27, 2024

Is your feature request related to a problem? Please describe.

I've encountered a situation where I need to use a different locale for a specific component. Currently, the locale context applies globally, and there isn’t an easy way to override it for just one component.

Describe the solution you'd like

It would be great if useTranslation could be extended to accept an extra argument that allows specifying a different locale, for example:

export const Component: React.FC = () => {
  // Language coming from browser/user setting
  const tEnglish = useTranslation('namespace', { locale: 'en' });

  // Language coming from project setting, level deeper than user
  const tDutch = useTranslation('namespace', { locale: 'nl' }); 

  return (
    <>
      <div>I am english: {tEnglish('key')}</div>
      <div>I am dutch: {tDutch('key')}</div>
    </>
  );
};

This would let individual components handle a different locale without affecting the overall app's setting.

Describe alternatives you've considered

Note

I am not using routing!

Read every existing issue title

I've checked every issue title in the 16 pages of issues but couldn't find any solution.

Nested providers

// Pseudo code for this submission
export default function Page() {
  return (
    <NextIntlClientProvider locale="nl" messages={locales.nl}>
      <h1>
        <NextIntlClientProvide locale="ru">{component}</NextIntlClientProvider>
      </h1>
    </NextIntlClientProvider>
  );
}

Extending useTranslation hook

import { MessageKeys, NestedKeyOf, NestedValueOf, TranslationValues, useTranslations } from 'next-intl';

type Key = MessageKeys<
  NestedValueOf<{ '!': IntlMessages }, '!._shared.viewer'>,
  NestedKeyOf<NestedValueOf<{ '!': IntlMessages }, '!._shared.viewer'>>
>;

export const useViewerTranslations = () => {
  const locale = useOtherLocale(); // pseudo code
  const t = useTranslations('_shared.viewer', { locale });

  return <TargetKey extends Key>(key: TargetKey, values?: TranslationValues) => {
    return t(key, { ...values, locale });
  };
};

Utilising getTranslations

const t = await getTranslations({ locale: 'nl' });

In next-intl/dist/types/src/server/react-server/getTranslations.d.ts it state that getTranslations receives a locale argument. Tried that but no dice.

@rickvdbroek rickvdbroek added enhancement New feature or request unconfirmed Needs triage. labels Oct 27, 2024
@rickvdbroek rickvdbroek changed the title Allow overriding locale context for specific components with useTranslation Allow overriding locale context for specific components with useTranslation/getTranslations Oct 27, 2024
@amannn
Copy link
Owner

amannn commented Oct 28, 2024

There was some previous discussion about this in #1370.

The gist is:

Server Component

import {getTranslations} from 'next-intl/server';

// Specify an explicit locale
const t = await getTranslations({locale: 'nl'});

This might be the easiest case. The reason why getTranslations accepts a locale, is because we can call i18n/request.ts multiple times in case you have components that require different locales and messages are easily available on the server side.

Client Component

import {NextIntlClientProvider} from 'next-intl';

return (
  <NextIntlClientProvider locale="nl" messages={nlMessages}>
    <Component />
  </NextIntlClientProvider>
)

On the client, we don't have all messages automatically available, as only the ones from the main locale are passed down (typically in your root layout). In case you need a different locale incl. messages for a subtree, you have to wrap those with another NextIntlClientProvider.

useTranslations(…, {locale: 'nl'}) is not supported, since we can't dynamically load messages from another locale on the client side.

@amannn amannn closed this as completed Oct 28, 2024
@rickvdbroek
Copy link
Author

Hi @amannn ! Thanks for the quick response, however I already tried using it on the server side with getTranslations({ locale: '' }) as mentioned in Describe alternatives you've considered. However it doesn't seem to work. This is my setup:

// src/i18n/request.ts
import 'server-only';

import { getRequestConfig } from 'next-intl/server';
import { headers } from 'next/headers';
import { auth } from 'src/auth';
import { SupportedLocale } from 'src/config/locales';
import { Preference } from 'src/schema/Preference';
import { users } from 'src/services/users';

const Language = Preference.pick({ language: true });

export default getRequestConfig(async () => {
  let locale: SupportedLocale = 'en';
  const authUser = await auth();
  const userId = authUser?.user?.id;
  const acceptLanguage = headers().get('Accept-Language') as SupportedLocale | null;
  const parsedHeaderLanguage = Language.safeParse({ language: acceptLanguage });

  if (parsedHeaderLanguage.success) {
    locale = parsedHeaderLanguage.data.language;
  }

  if (userId) {
    const user = await users.get(userId);

    if (!user) throw new Error('For each authenticated user, a user should exist');
    const parsedUserLanguage = Language.safeParse({ language: user.preferred_language });

    if (parsedUserLanguage.error) throw new Error('User has invalid language preference');
    locale = parsedUserLanguage.data.language;
  }

  return {
    locale,
    messages: (await import(`../../messages/${locale}.json`)).default,
  };
});

Maybe I am not understanding the underlying API correctly. Could you give me some additional pointers? In another file I have:

export default function Page() {
  const t = await getTranslations({ locale: 'nl' });
  
  return (
    <div>
      {t('key')} // Still showing in english
    </div>
  );
}

@amannn
Copy link
Owner

amannn commented Oct 28, 2024

Oh right, now I see your point! If you pass locale to getTranslations, it ends up in getRequestConfig as the value that can be received via requestLocale. That's actually an interesting case that the docs don't really cover at this point.

I think this might work for you:

export default getRequestConfig(async ({requestLocale})) => {
  let locale: SupportedLocale = await requestLocale || 'en';

  // ...
});

Can you give that a shot?

See also: Which values can the requestLocale parameter hold?

Edit: Also made a note about this in #663 (comment). Thanks for sharing your use case, I have to admit this is a case I haven't really considered so far!

@rickvdbroek
Copy link
Author

@amannn That did the job! Nice how separated everything is without side-effects. <3

@amannn
Copy link
Owner

amannn commented Oct 28, 2024

That's great to hear!

Thanks for sharing your use case, this seems to be a scenario I haven't really considered so far. Will keep an eye on this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request unconfirmed Needs triage.
Projects
None yet
Development

No branches or pull requests

2 participants