GithubHelp home page GithubHelp logo

quiibz / next-international Goto Github PK

View Code? Open in Web Editor NEW
1.2K 4.0 52.0 621 KB

Type-safe internationalization (i18n) for Next.js

Home Page: https://next-international.vercel.app

License: MIT License

Shell 0.10% TypeScript 99.90%
i18n library nextjs typescript

next-international's Introduction


Type-safe internationalization (i18n) for Next.js


Features

  • 100% Type-safe: Locales in TS or JSON, type-safe t() & scopedT(), type-safe params, type-safe plurals, type-safe changeLocale()...
  • Small: No dependencies, lazy-loaded
  • Simple: No Webpack configuration, no CLI, no code generation, just pure TypeScript
  • Server and Client, Static Rendering: Lazy-load server and client-side, support for Static Rendering
  • App or Pages Router: With support for React Server Components

Note: You can now build on top of the types used by next-international using international-types!

Try it live on CodeSandbox:

Open with CodeSandbox

Documentation

Check out the documentation at https://next-international.vercel.app.

Contributing

See the contributing guide.

Sponsors

Sponsors

License

MIT

next-international's People

Contributors

apacheex avatar armanaryanpour avatar baptistearno avatar bastibuck avatar cstrnt avatar edmundkorley avatar exio4 avatar gustavewpm avatar ichiwa avatar ivanhofer avatar kristofferso avatar lindesvard avatar mieszkosabo avatar moinulmoin avatar myeljoud avatar nsttt avatar poypoydev avatar quiibz avatar ubinatus avatar willem-jaap avatar yovach avatar zackrw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

next-international's Issues

pass t useI18n hook inside a another component

I would like to know if there's an easy way to pass {t} as a props inside another components
hooks don't work inside loops so I need a way to find the type of t in order to pass it as prop in my other component
here an example:

const { scopedT } = useI18n();
const t = scopedT('table')
buildTableHeader(t);

export const buildHeadIncidents = (t: //What type should I put here): HeadCellItem[] => {
    // const { t } = useI18n(); // I can't call the hook here
    return [
        {
            name: 'title1',
            label: t('mykey'),
        },
        {
            name: 'title2',
            label: t('mykey'),
        },
        {
            name: 'title3',
            label: t('mykey'),
        }
    ];
};

Here is definition of t and scopedT used by next-international

    useI18n: () => {
        t: {
            <Key extends Extract<keyof Locale, string>, Value extends international_types.LocaleValue = Locale[Key]>(key: Key, ...params: international_types.Params<Value>["length"] extends 0 ? [] : [international_types.ParamsObject<Value>]): string;
            <Key_1 extends Extract<keyof Locale, string>, Value_1 extends international_types.LocaleValue = Locale[Key_1]>(key: Key_1, ...params: international_types.Params<Value_1>["length"] extends 0 ? [] : [ReactParamsObject<Value_1>]): React$1.ReactNode;
        };
        scopedT: <Scope extends international_types.Scopes<Locale>>(scope: Scope) => {
            <Key_2 extends international_types.LocaleKeys<Locale, Scope, Extract<keyof Locale, string>>, Value_2 extends international_types.LocaleValue = international_types.ScopedValue<Locale, Scope, Key_2>>(key: Key_2, ...params: international_types.Params<Value_2>["length"] extends 0 ? [] : [international_types.ParamsObject<Value_2>]): string;
            <Key_3 extends international_types.LocaleKeys<Locale, Scope, Extract<keyof Locale, string>>, Value_3 extends international_types.LocaleValue = international_types.ScopedValue<Locale, Scope, Key_3>>(key: Key_3, ...params: international_types.Params<Value_3>["length"] extends 0 ? [] : [ReactParamsObject<Value_3>]): React$1.ReactNode;
        };
    };

App Router: Support for removing default locale from URL

This library is excellent! I'd love to recreate the default functionality of the pages router, where the default locale isn't included in the URL, so e.g. "/about" is en-US, and "/en-GB/about" is en-GB or another locale. Is that possible with the middleware?

Missing license info at npmjs.com

Hi,

while running pnpm licenses ls i mentioned that your packages haven't a license info in the npm registry.

The current output is:

├──────────────────────────────────────────┼─────────────────────────────────────┤
│ international-types                      │ Unknown                             │
├──────────────────────────────────────────┼─────────────────────────────────────┤
│ next-international                       │ Unknown                             │
└──────────────────────────────────────────┴─────────────────────────────────────┘

Would be good if the license info at npmjs could match the specified license ( MIT )

Thanks!

Error: "Locale not found" when set custom basePath in nextjs config

When basePath: '', everything is fine but when I set for example basePath: '/abc', I get an error on route '/abc/en'

 
1 of 1 unhandled error
Next.js is up to date

Unhandled Runtime Error
Error: Locale not found

Call Stack
eval
node_modules/next-international/dist/app/client/index.js (1:3060)
mountMemo
node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js (12161:0)
Object.useMemo
node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js (12817:0)
useMemo
node_modules/next/dist/compiled/react-experimental/cjs/react.development.js (1813:0)
eval
node_modules/next-international/dist/app/client/index.js (1:3000)
e
node_modules/next-international/dist/app/client/index.js (1:1165)
renderWithHooks
node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js (10855:0)
mountIndeterminateComponent
node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js (16491:0)
beginWork$1
node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js (18088:0)
beginWork
node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js (26476:0)
performUnitOfWork
node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js (25327:0)
workLoopSync
node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js (25043:0)
renderRootSync
node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js (24998:0)
recoverFromConcurrentError
node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js (24229:0)
performConcurrentWorkOnRoot
node_modules/next/dist/compiled/react-dom-experimental/cjs/react-dom.development.js (24135:0)
workLoop
node_modules/next/dist/compiled/scheduler-experimental/cjs/scheduler.development.js (261:0)
flushWork
node_modules/next/dist/compiled/scheduler-experimental/cjs/scheduler.development.js (230:0)
MessagePort.performWorkUntilDeadline
node_modules/next/dist/compiled/scheduler-experimental/cjs/scheduler.development.js (537:0)

Nested objects locales not working

It's not possible to access the translations by key with translate function.

It appears to be a problem caused by not using FlattenLocale.

Should look like that:
image

But is currently that:
image

Discussion: support for JSON modules

Finally, a strongly typed i18n package! Thank you.

In my experience, most i18n translations are stored in JSON format. This facilitates easy translation by external services and enables tools like i18n-ally to enhance the translation experience. Comparatively, Typescript files pose more management challenges.

While Typescript natively supports importing JSON modules, it falls short of a feature equivalent to const assertion, thereby compromising some part of its type safety. In the Typescript repository, you can refer to this issue for more insights.

Here's an example:

// en.ts
export default {
  hello: '{world}'
} as const;

// i18n.ts
import en from 'en';

assertType<{
  readonly hello: '{world}';
}>(en); // true

vs.

// en.json
{
  "hello": "{world}"
}

// i18n.ts
import en from 'en.json';

assertType<{
  hello: string;
}>(en);

This, in turn, causes Typescript to be unable to infer template arguments for the JSON object. So the following will work when dealing with native typescript, but not with JSON modules.

// Won't work with JSON modules because `{world}` could not be inferred from the type `string.`
t('hello', {
  world: 'Mars'
})

A potential solution to this problem (if you want to call it that) could be to type the second argument as:

type ParamsObject = {
  world: LocaleValue;
  // Accept any key
  [key: string]: LocaleValue;
}

const example = {
  world: 'Mars',
  nonExistingPram: 'Moon'
} satisfies ParamsObject

This approach ensures you don't compromise type safety when dealing with native Typescript while supporting JSON modules.

Please let me know what your thoughts are.

Pages-dir and App-dir can't work together

Hi,

Thanks for the amazing DX on next-international, and all the help you have been providing!

We are in a middle of a migration from pages to app-dir and need them both to colocate since its a fairly big application and it takes time to migrate 100% over to app-dir.

The repro: https://github.com/aulonm/next-international-repro (updated it with this issue)

The problem we are facing now is when we add [locale] folder to the app-folder, and next.config has the i18n-object (this is needed for pages-dir).

The routing doesn't work as expected.

App-dir has the following routes:

  • /
  • /client
  • /subpage

Pages dir has the following routes:

  • /ssr-ssg

When running the dev-server and navigating to /ssr-ssg it returns the pages-dir, which is correct.
When navigating to /client it returns the root-file in app-dir and I can't get access to the subpages withouth doing something like /en/subpage or /en/client (the client one doesn't work).

Is this a limitation in the package or something that I'm missing?

Bug: nested object locales only returning keys

Hi,

we just included this lib in our new codebase and it works like a charm when using the dot-notation. Since the docs stated we can also use nested objects and we'd prefer this syntax we tried it with the provided examples from the docs but it doesn't work.

Can you confirm? Or are we doing something wrong?

The types are working properly after your latest version #66

[RFC] Add `useScopedT` hook

I often end up having this exact code in my component

function MyComponent() {
  const { scopedT } = usei18n();
  const t = scopedT("myScope");
}

I personally think that this is unnecessary overhead. I would prefer to have an export like useScopedT that takes in a scope and returns a t function with a defined scope

Infinite redirection bug due to language code parsing issue

Issue Description:

I am encountering an infinite redirection bug in my Next.js application, which is caused by a language code parsing issue. The application uses the external library, "next-international," to handle internationalization.

Reproduction Steps:

Set up a Next.js application with internationalization using the "next-international" library.
Use language codes containing a hyphen ("-"), e.g., "en-US" and "fr-FR," to represent different locales.

Expected Behavior:

The application should successfully identify and use the correct locale based on the "Accept-Language" header or the "Next-Locale" cookie.

Actual Behavior:

The "negotiateAcceptLanguage" function does not correctly parse the "Accept-Language" header when the language codes contain a hyphen ("-"). As a result, it retrieves only the first part of the language code and ignores the region-specific part (e.g., "fr" instead of "fr-FR").

Issue Analysis:

The root cause of the bug lies in the "negotiateAcceptLanguage" function's splitting logic for the "Accept-Language" header. When the function attempts to split the header by a comma (","), it only retrieves the first part before the hyphen ("-"). This leads to incorrect locale identification and may cause an infinite redirection loop when handling internationalized routes.

Code Snippet with the Issue:

// File: src/app/middleware/index.ts

function negotiateAcceptLanguage(request: NextRequest) {
    const header = request.headers.get('Accept-Language');
    console.log(header);
    const locale = header?.split(',')?.[0]; // Incorrect splitting logic for language codes with hyphen ("-")
    return locale ?? null;
}

// ... Rest of the code ...

Proposed Solution:

To resolve the infinite redirection issue, the library's parsing logic for language codes should be modified to handle hyphens ("-") correctly. This can be achieved by using a different approach to split the "Accept-Language" header, allowing it to retrieve the complete language code, including the region-specific part.

Additional Information:

Next.js version: 13.4.12
next-international version: ^0.8.1

If it's good for you, I'll be happy to help you by doing the PR, indeed, this would be one of my first contributions to an open source project, especially since I really appreciate your project, because it taught me a lot about how the core of next.js works and it saved me a lot of time.

In all cases, I hope this information helps in resolving the issue. Please let me know if you need any further details or assistance with the bug fix. Thank you!

Best regards,

Usage together with next-auth

Hi!

We're in a process of migrating an older Next.js app with the pages dir to the new App router and are also moving from i18next to next-international.

The app uses Next auth for the authentication layer and besides the login and register pages all other pages need authentication. So we're using the next auth middleware as follows:

import { withAuth } from "next-auth/middleware";

export const config = {
  matcher: ["/((?!api|user|static|favicon.ico).*)", "/"],
};
export default withAuth({
  pages: {
    signIn: "/user/login",
  },
});

Does somebody has an example on how to combine this middleware with the next-international middleware?

Does not show rootPage under [locale] when rewrite is enabled

Issue Description:
We are testing this library and managed to almost make it work perfectly. Love that you have added an option to not show the language in the URL, since we work with domains and not different language-paths.

We also have a different basePath than the /-path. When we navigate to the root which is localhost:3000/medlem it wont use the files that are within app/[locale]/page but it uses app/page. Is there something that I'm doing wrong or does it not work? It should rewrite from /basePath to /basePath/[locale] but it doesn't at the moment.

Reproduction Steps:
Run the server and go to localhost:3000/medlem it shows the default next template code, but it should actually show the text This is the root page with locale turned on which is under app/[locale]/page.tsx. It only shows pages from [locale] when I'm navigating to level 2 which is /[locale]/under in my repro-repo linked:
https://github.com/aulonm/next-international-repro

Expected Behavior:
It should show pages that are under [locale] and not a page outside of the [locale] folder when navigating to the root of the website.

Actual Behavior:
Shows the page from the root of the app-dir

Next.js version: 13.4.13
next-international version: 0.8.1

Best regards,

Force consistency between all locale files

I think it's a good practice would be to have a single source of truth locale file like en.ts and having the other files be based on it:

// en.ts
export default  {
    key: 'value',
} as const
// fr.ts
import type Locale from './en'

export default {
    key: "value"
} satisfies Record<keyof typeof Locale, string>

Anything wrong with that? Would it be worth mentioning in the README?

Locale not found, when using 'rewrite' (urlMappingStrategy)

Hey thanks for this amazing package,

I was looking for a possibility to hide to locale in the url and next-international is working great so far!!

I'm using urlMappingStrategy with 'rewrite', so I don't need the locale in the url.

const I18nMiddleware = createI18nMiddleware(locales , defaultLocale,{ urlMappingStrategy: 'rewrite' })

If I'm only using the package on server components, it is working fine as expected. But when I try to use it in a client component, wrapped with I18nProviderClient, I always get "Error: Locale not found".

When I add the locale parameter to the url, it is working fine.
Seems like it is checking for the parameter, even if hide the locale with 'rewrite'.

Any help is very appreciated 🙂

I am using the App-Router and [email protected]

Thanks!

App Router: Plurals not working with getScopedI18n

Hey!
I was trying to use the plural translations with scoped translations and I've got some issues. For some reason, it only works with useI18n and getI18n.

My dictionary:

export default {
  home: {
    "open-source": {
      "stars#one": "1 star on GitHub",
      "stars#other": "{count} stars on GitHub",
    },
  },
} as const;

My page component:

import { getScopedI18n } from "~/lib/next-international/server";
import { getGitHubStars } from "~/utils/get-github-stars";

export const revalidate = 60; // 1 minute

export default async function Page() {
  const stars = await getGitHubStars();
  const t = await getScopedI18n("home");

  return (
    <main className="container grid items-center animate-in fade-in slide-in-from-bottom-8 duration-really-slow">
      <div className="flex items-center">
        <div className="h-4 w-4 border-y-8 border-l-0 border-r-8 border-solid border-muted border-y-transparent" />
        <div className="flex h-10 items-center rounded-md border border-muted bg-muted px-4 font-medium">
          {t("open-source.stars")}
        </div>
      </div>
    </main>
  );
}

Dependencies version:

{
  "next": "13.4.12",
  "next-international": "^0.7.0",
  "typescript": "5.1.6"
}

Unfortunately, I couldn't find any examples of how to use plural translations with scoped translations. I would be very grateful if you could help me. By the way, thank you and congratulations for the excellent work. I've already used other internationalization libraries and this one, without a doubt, is the best of all.

[ RFC ] : Redirect to locale on re-visit when saved local is not the default

Problem

When the option urlMappingStrategy is set to rewrite instead of redirect, it works perfectly, and it does the necessary behaviour but there is one edge case.

So lets put the case where you have en and fr as locales, being en de default one, once you set /fr, the cookies will be saved with the current locale to fr, but once you open the page again without any locale on URI, it does stay the same. I believe in this case the "correct" behaviour would be redirect the user to correct localised URL, this could bring problems for Google & SEO, where a page at same URL should not be displayed different contents at any given time.

Solution

While we are able to provide our own resolveLocaleFromRequest, i believe a redirect should take place if the previous remembered locale is not the default one on the the default middleware, so in this case we will end up with a few nice scenarios :

  • No Locale ( First hit from the user / crawler ) -> Use the default Locale, not appended to the URL
  • Change locale to other than the default one -> Redirects to the /fr ( example ) and sets the cookies
  • Sub-sequent visits with remembered locale -> If its the default, leave as it, if remembered, redirect to the correct URL

Let me know what you think about this! If you allow me i can take a stab trying to PR the change :)

Error: "Locale not found" in api nextjs routes

Please exclude language support from api nextjs routing.

Despite excluding api nextjs routing in the middleware, when calling the api, I get the following error:

error node_modules/next-international/dist/app/client/index.js (1:3156) @ eval
- error Error: Locale not found

Examples of `international-types` seems outdated

Hello QuiiBz,

The documentation for the international-types library seems to be out of date as the API has changed.
The third example displays an error on the line Value extends LocaleValue = ScopedValue<Locale, undefined, Key>.

Currently, the examples show a function that receives a type where the keys are on the first level but never "scoped".
Is it possible to add an example with scoped and plural values like above ?

import { BaseLocale, FlattenLocale, LocaleValue, ParamsObject, ScopedValue } from "international-types";

const translations = {
  hello: {
    "people#one": "Hello you",
    "people#other": "Hello everyone",
  },

  "coin#one": "1 coin",
  "coin#other": "{count} coins",
} as const;


type TranslationType = typeof translations;

export function translate<
  Locale extends BaseLocale,
  Key extends keyof FlattenLocale<Locale> = keyof FlattenLocale<Locale>,
  Value extends LocaleValue = ScopedValue<Locale, undefined, Key>,
>(k: Key, params: ParamsObject<Value>) {
  // ...
}

translate<TranslationType>("hello.people", { count: 5 });

Thanks for you library and for your great work!

i18n support is not compatible with next export

When running next export
I get the following error

Error: i18n support is not compatible with next export. See here for more info on deploying: https://nextjs.org/docs/deployment

I found this tutorial: https://dev.to/adrai/static-html-export-with-i18n-compatibility-in-nextjs-8cd
the solution was provided from next-i18next, wan we use the same approach for next-international?

Since I couldn't fine anything in the README nor inside the closed issues , do you recommend following this solution or is there an alternative way for next-international?

Cannot find module 'next-international/middleware'

Super happy to see it got /app support.

Uppon checking the example turns out i cannot import the middleware and other objects.

// [lang]/layout
Module '"next-international"' has no exported member 'I18nProviderServer'.

//middleware
Cannot find module 'next-international/middleware' or its corresponding type declarations.

I can surpress the issue by importing from /dist/middleware but then i get another error about it not being a named export there.

Package Version: 0.5 from the latest release tag.

[RFC]: CLI Tool that transforms the language TS files to JSON and vice versa

Problem

At my work we need to have the language files in JSON because they are inserted in some tool by the translation team. Manually converting this is quite a pain and repetitive work.

Idea

I think it would be cool to have a dedicated CLI tool that can convert JSON to TS and vice versa. Maybe it could also add wrap them in defineLocale to ensure typesafety along the files

error - SerializableError: Error serializing `.locale["pages.past.date"]` returned from `getServerSideProps` in "/".

Hey,

i'm currently creating a simple example to test the new blitz toolkit.

At the same time I want to test your i18n package.

Everything seems to work, but since I added the language switcher and refreshed the page after changing the language from "en" (=default) to "de" I get the following error:

error - SerializableError: Error serializing `.locale["pages.past.date"]` returned from `getServerSideProps` in "/".
Reason: `undefined` cannot be serialized as JSON. Please use `null` or omit this value.
    at isSerializable (<local_path>/node_modules/next/dist/lib/is-serializable-props.js:36:19)
    at <local_path>/node_modules/next/dist/lib/is-serializable-props.js:43:66
    at Array.every (<anonymous>)
    at isSerializable (<local_path>/node_modules/next/dist/lib/is-serializable-props.js:40:39)
    at <local_path>/node_modules/next/dist/lib/is-serializable-props.js:43:66
    at Array.every (<anonymous>)
    at isSerializable (<local_path>/node_modules/next/dist/lib/is-serializable-props.js:40:39)
    at Object.isSerializableProps (<local_path>/node_modules/next/dist/lib/is-serializable-props.js:63:12)
    at Object.renderToHTML (<local_path>/node_modules/next/dist/server/render.js:552:67)
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  page: '/'
}

To be honest, I'm not sure if this is a problem with your package.

You can find the complete source here: https://github.com/noxify/blitz-hackernews


Steps to reproduce:

  1. Install the app
  2. Start the dev server via npm run dev
  3. Open the browser and go to page http://localhost:3000
    • You will see the index page and the language is english
  4. Switch the language to de (=german) ( via the icon in the header navigation )
    • You will see that the language has been changed to german
  5. Reload the page
    • You will see the error

Please let me know in case you need additional information or something else.

No matching version found for international-types@* inside the workspace

Getting this error while trying to pnpm install next-international

 ERR_PNPM_NO_MATCHING_VERSION_INSIDE_WORKSPACE  In ../..: No matching version found for international-types@* inside the workspace

This error happened while installing the dependencies of [email protected]
../..                                    | Progress: resolved 191, reused 190, downloaded 0, added 0

Am I missing something, or is it a bug?

Loading translations client side works how?

Hey, I am currently using next-translate which forces my dynamic pages SSR even if I do not want it because it causes long cold starts in my pages even if they are all client-side.
Therefore, looking for something else. When I use next-international and client side loading of translations, the problem no longer exists. But I would like to understand how client side loading works? Is it using getInitialProps under the hood or else? In what ways it is not good to use or vice versa? Thank you.

On demand / dynamic fetching and reloading of translations

Why

Large commercial websites that serve visitors of multiple nationalities / locales can have a lot of translation strings.

In our case when these strings are created, they are done so in one locale in our CMS, then they are sent to a third party translation provider that in some cases takes time to return the localised versions of the strings.

I haven't tried how next-international handles what I'm suggesting below

Concept

Enable the functionality of dynamically updating the local key:value files, and reloading the translation provider so it has everything available.

The functionality I'm looking for is basically somehow being able to force a reload of assets. I don't think logic that does the fetching and writing of the updated files is something that belongs within this library.

What we have done before, with other i18n libraries is to iterate through the set locales, fetch data from a CMS, and then map the values out and write to a .json. Then trigger a reload of the 18n library so it repopulates the strings.

Error: Uncaught [Error: `useI18n` must be used inside `I18nProvider`]

Hello,
thank you for this awesome library
I'm using this package with Jest and it doesn't seems to work properly

this is my customRender.tsx

import en from 'locales/en';
import { ThemeProvider } from '@mui/material';
import { theme } from 'styles/theme';
import { render, cleanup } from '@testing-library/react';
import { createI18n } from 'next-international';

// I tried different methods to mock next/router
// 1. first attempt
const useRouter = jest.spyOn(require('next/router'), 'useRouter');
useRouter.mockImplementationOnce(() => ({
    locale: 'en',
    defaultLocale: 'en',
    locales: ['en', 'fr'],
}));

// 2. second attempt
jest.mock('next/router', () => ({
  useRouter: () => ({
    locale: 'en',
    defaultLocale: 'en',
    locales: ['en', 'fr'],
  }),
}));

afterEach(() => {
    cleanup()
})

const { I18nProvider } = createI18n<typeof en>({
    en: () => import('locales/en'),
    fr: () => import('locales/fr'),
})

const Providers = ({ children }: { children: any }) => {
    return <I18nProvider locale={en}><ThemeProvider theme={theme}>{children}</ThemeProvider></I18nProvider>
};
const customRender = (ui: any, options = {}) => render(ui, { wrapper: Providers, ...options });
export * from '@testing-library/react';
export { default as userEvent } from '@testing-library/user-event'
export { customRender as render };

I have a component where I use i18n and the test fail all the time

import { cleanup, fireEvent } from '@testing-library/react';
import { render, screen } from 'testing/customRender'

import React from 'react';
import InputField from '.';

afterEach(cleanup);

describe('<InputField />', () => {
    it('handleChange function called', () => {
        const spy = jest.fn();
        render(<InputField field={{ name: 'text', onChange: spy }} label="nom" />);
        let input = screen.getByLabelText('nom') as HTMLInputElement;
        expect(input.value).toBe('');

        fireEvent.change(input, { target: { value: '23' } });
        fireEvent.blur(input, { target: { value: '23' } });
        expect(spy).toHaveBeenCalledTimes(1);
    });
});
// locales.ts
import { createI18n } from 'next-international';
import type Locale from './en';
export const { useI18n, I18nProvider, useChangeLocale, defineLocale, getLocaleProps } = createI18n<typeof Locale>({
  en: () => import('./en'),
  fr: () => import('./fr'),
});

Inside my component I import the hook use18n() from locales
const { t } = useI18n();

When I run my tests I get the following error

TypeError: Cannot destructure property 'locale' of '(0 , B.useRouter)(...)' as it is null.

Can you please provide an example with jest? a wrapper and how to use it inside a component that uses i18n

Thank you so much

Lot of potential but too strongly bounded by nextjs.

I want to use it in a react native project but just because it's using GetStaticProps and Router from next hence it can't be used anywhere except the next js project.

I see a lot of potential in this library if it was not strongly bounded to one library. This can easily work in any react based project.

U can provide optional "no configuration" and "automatic binding" for the next js as an extension for this library.

Thoughts?

Example usage with tests.

Hello!
Been using this library for a while but I'm having a bit of a catch up when trying to make unit tests with it.

On regular i18n I either mock the tests or pass a default locale to use with it. But in this case due to passing props using Next.js pageProps I'm running into some issues.

Here are the few pieces of code I'm currently using to implement my tests.

import { createI18n } from "next-international";
import Locale from "../../locales/en";

const TestWrapper = ({ children }: { children: JSX.Element }) => {
  const { I18nProvider } = createI18n<typeof Locale>({
    en: () => import("../../locales/en"),
  });
 
  return (
      <I18nProvider locale={Locale}>
         {children}
      </I18nProvider>
  );
};
 
export default TestWrapper;

And then I wrap a component with it.

import { render, screen, waitFor } from "@testing-library/react";
import TestWraper from "../utils/testWraper";
import HomePage from "../../pages/[building]/homePage";

describe("Homepage tests", () => {
  beforeEach(() => {
    render(
      <TestWraper>
        <HomePage />
      </TestWraper>,
    );
  });
  it("Render logout button", () => {
    expect(
      screen.getByRole("heading", {
        name: /logout/i,
      }),
    ).toBeInTheDocument();
  });
});

And I get this error.

 console.error
      Error: Uncaught [TypeError: Cannot destructure property 'locale' of '(0 , B.useRouter)(...)' as it is null.]
          at reportException.

It might be handy if there's some sort of example on how to write a basic test with it.

Matching locale with path name initals throws 404

I've been playing around this lib for a couple days now, very easy to implement and play around.

I just reached a scenario where if the pathname initials matches locale name we get 404:

Setup:

  • I have two locales: ["ur","en"].
  • I have routes set as uru, eno

Since uru matches the locale "ur", when switching to this page (mysite.com/uru or mysite.com/eno) I get 404 and same goes for eno since it matches "en", regardless of what current locale is.

here's the Codesandbox

The middleware is somehow confused here when rewriting urls.

Please take a look.

Thanks :)

Default Locale Redirection not Working

For some reason this code still redirects to /en instead of /ar in first visit ( regardless what I insert as default locale )

import { createI18nMiddleware } from 'next-international/middleware';
import { NextRequest } from 'next/server';

const I18nMiddleware = createI18nMiddleware(['ar', 'en'] as const, 'ar');

export function middleware(request: NextRequest) {
    return I18nMiddleware(request);
}

export const config = {
    matcher: ['/((?!_next).*)'],
};

App Router: SSG not working as described in docs

Hello,

I was trying to get SSG working in the app router and did the required things like described in the docs.
I uncommented:
export const generateStaticParams = getStaticParams(); in app/[locale]/page.tsx, as well as:
output: "export" in the next.config.js

when i now run the next build command, I get the following error

> next build

- info Creating an optimized production build  
- info Compiled successfully
- info Skipping validation of types
- info Skipping linting
- info Collecting page data  
- info Generating static pages (3/3)
- info Finalizing page optimization  

Route (app)                                Size     First Load JS
┌ λ /[locale]                              140 B          78.4 kB
├ λ /[locale]/client                       2.52 kB        80.7 kB
├ λ /[locale]/subpage                      140 B          78.4 kB
└ ○ /favicon.ico                           0 B                0 B
+ First Load JS shared by all              78.2 kB
  ├ chunks/437-f84f08f412263f07.js         25.8 kB
  ├ chunks/d909b7fe-b8be5e5e0523de08.js    50.5 kB
  ├ chunks/main-app-314d6406b014043c.js    214 B
  └ chunks/webpack-d41e780fcf98ea6d.js     1.74 kB

Route (pages)                              Size     First Load JS
─ ○ /404                                   182 B          75.8 kB
+ First Load JS shared by all              75.6 kB
  ├ chunks/framework-510ec8ffd65e1d01.js   45 kB
  ├ chunks/main-f3d726c6da438ade.js        28.6 kB
  ├ chunks/pages/_app-4fa603cb0fa6e977.js  196 B
  └ chunks/webpack-d41e780fcf98ea6d.js     1.74 kB

ƒ Middleware                               19.5 kB

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)

StaticGenBailoutError: Page with `dynamic = "error"` couldn't be rendered statically because it used `headers`.
    at staticGenerationBailout (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/661.js:152:15)
    at headers (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/483.js:84:62)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:91:27
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/299.js:7641:23
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:100:21
    at Generator.next (<anonymous>)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:53:31
    at new Promise (<anonymous>)
    at f (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:39:20)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:99:16 {
  code: 'NEXT_STATIC_GEN_BAILOUT'
}
StaticGenBailoutError: Page with `dynamic = "error"` couldn't be rendered statically because it used `headers`.
    at staticGenerationBailout (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/661.js:152:15)
    at headers (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/483.js:84:62)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:91:27
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/299.js:7641:23
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:100:21
    at Generator.next (<anonymous>)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:53:31
    at new Promise (<anonymous>)
    at f (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:39:20)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:99:16 {
  code: 'NEXT_STATIC_GEN_BAILOUT'
}
Error: Locale not found
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/299.js:158:19
    at Object.ye [as useMemo] (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:101:241)
    at exports.useMemo (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react/cjs/react.production.min.js:29:208)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/299.js:156:30
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/299.js:66:17
    at Ue (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:113:273)
    at Z (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:120:91)
    at Ue (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:114:9)
    at Ue (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:119:11)
    at Z (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:120:91)

Error occurred prerendering page "/[locale]/subpage". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Page with `dynamic = "error"` couldn't be rendered statically because it used `headers`.
    at staticGenerationBailout (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/661.js:152:15)
    at headers (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/483.js:84:62)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:91:27
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/299.js:7641:23
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:100:21
    at Generator.next (<anonymous>)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:53:31
    at new Promise (<anonymous>)
    at f (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:39:20)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:99:16

Error occurred prerendering page "/[locale]/client". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Locale not found
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/299.js:158:19
    at Object.ye [as useMemo] (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:101:241)
    at exports.useMemo (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react/cjs/react.production.min.js:29:208)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/299.js:156:30
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/299.js:66:17
    at Ue (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:113:273)
    at Z (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:120:91)
    at Ue (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:114:9)
    at Ue (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:119:11)
    at Z (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.edge.production.min.js:120:91)

Error occurred prerendering page "/[locale]". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Page with `dynamic = "error"` couldn't be rendered statically because it used `headers`.
    at staticGenerationBailout (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/661.js:152:15)
    at headers (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/483.js:84:62)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:91:27
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/299.js:7641:23
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:100:21
    at Generator.next (<anonymous>)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:53:31
    at new Promise (<anonymous>)
    at f (/Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:39:20)
    at /Users/smox/Developer/WebDev/Multilanguage Testing/next-international/.next/server/chunks/973.js:99:16

> Export encountered errors on following paths:
        /[locale]/client/page: /[locale]/client
        /[locale]/page: /[locale]
        /[locale]/subpage/page: /[locale]/subpage
 ELIFECYCLE  Command failed with exit code 1.

I don't know if I simply missed something or what else the source of this error could be.
I would be very thankful if you could help me figure this out.

[ Possible Bug ] Next Images / Middleware Match Patterns

Hey! Thanks for the amazing package at first, really clean, ts, just magic!

I was trying in a fresh project on the latest next canary, while everything looks like working at glance, it seems like images being served from Next Images component are having issues, because the middleware actually looks for the original url path.

So lets say you have : "public/one.png" , thats the actual URL that arrives at the middleware and not "_next/images?url..."

For me it seems like next middleware should exclude public assets by default, but there should be reason why they didnt :p

Im not sure if this an actual upstream issue, but just wanted to mention i able to replicate this on your demo repo.

My suggestion for this would be either include the matches by default on your create middleware so users have a better DX by having stuff ignored out of the box.

This seems related to vercel/next.js#36308 (comment)

The solution for me was updated the following on the middleware.ts

export const config = {
  matcher: '/((?!api|static|.*\\..*|_next).*)',
}

Next Version: ^13.4.17-canary.0
Router: App Router

Invalid typing of useCurrentLocale()

Hi!

Thanks for the work here. I was just looking at the code and noticed that the createUseCurrentLocale() function in src/app/client has a return type that does not match the returned function (the config parameter is missing). Is it intentional?

If it is, how can we provide a custom segment name?

Nested object scopes

Hi! I really like this project, definitely my favorite nextjs i18n library that I've seen. Keep it up!

One thing I didn't like about the locale definition was that scopes are defined with dot-separated strings, so I wrote a code snippet that allows me to define a locale in terms of a nested object like so:

export interface OldLocale {
  'pages.landing.title': string;
  'pages.landing.description': string;
}

export interface Locale {
    pages: {
        landing: {
            title: string;
            description: string;
        };
    };
}

The code snippet is:

import { createI18n } from "next-international";
import type { Locale } from "@locales";

const i18n = createI18n<Record<LocaleKeys<Locale>, string>>({
    en: () => import("@locales/en"),
    nl: () => import("@locales/nl"),
});

export const { useI18n, useChangeLocale, I18nProvider, getLocaleStaticProps } = i18n;

type LocaleDefinition = Record<string, string | object>;
type LocaleKeys<T> = keyof {
    [P in keyof T as T[P] extends string ? P : `${string & P}.${string & LocaleKeys<T[P]>}`]: true;
};
type DefinedLocale = Record<LocaleKeys<Locale>, string>;

export function defineLocale(locale: Locale): DefinedLocale {
    const flatten = (l: Locale, prefix = ""): LocaleDefinition =>
        Object.entries(l).reduce(
            (prev, [name, value]) => ({
                ...prev,
                ...(typeof value === "string"
                    ? { [prefix + name]: value }
                    : flatten(value as Locale, `${prefix}${name}.`)),
            }),
            {},
        );
    return flatten(locale) as DefinedLocale;
}

Then I can define a locale like this

import { defineLocale } from "@hooks/useI18n";

export default defineLocale({
    landing: {
        hero: {
            title: "Welcome to my website",
            description: "This is a really cool website!",
        },
    },
});

Type-checking works within a locale definition, and the type system after createI18n seems to be unbothered.

Just thought I'd share my solution, maybe you like it enough to implement it in the library. If not, feel free to close this issue 😃

Export `Locale` types to create custom `Locale` interface

Currently the recommended practice is to create a locale as const, export it, import it as a type & use it for the createI18n generic.

In future to change all other types you need to change that objects structure to change the type which doesn't seem right.

My proposal to make this better is to add the Locale & LocaleValue types in types.ts to the root export such that you can create a general purpose interface or type to manage your locales.

Example

// locales/index.ts
import { createI18n } from 'next-international';

import type { Locale as BaseLocale } from 'next-international';

interface MyCustomLocale extends BaseLocale {
  hello: string;
}

export const {
  defineLocale,
  useI18n,
  // ...
} = createI18n<MyCustomLocale>({
  en: () => import('./en'),
});
// locales/en.ts
import { defineLocale } from '.';

export default defineLocale({
  hello: 'Hello',
})

Object spreading locales breaks variable inference

In an effort to make my locale structure cleaner I have begun splitting it into multiple files / objects that I can import & object spread in the locale index default export.

However there currently seems to be a bug whereby object spreading breaks the variable type inference.

I've created a CodeSandbox to demonstrate the issue

I feel like this may be more of a lower level TypeScript template inference issue but thought I would report it anyway 🙂

Next-Locale cookie defaults to browser language and not the default language

Issue Description:
When I run the website locally it defaults to en in Next-Locale-cookie, even though I set the middleware to only accept nb and sv. Is it possible to add an option to add the default language that I set and not what my browser says my language is in?

Our sites are based on a domain and not a path. So we develop 1 application, but serve two different instances with two different domains. In norwegian and swedish with the domains .no and .se.

Reproduction Steps:
Just run the server and go to localhost:3000/medlem/under and check the console that it adds { Next-Locale: 'en' }. I want this to be nb since I've said that nb is my default language.
https://github.com/aulonm/next-international-repro

Expected Behavior:
The applications should use the default locale set if there is no next-locale in the users cookies.

Actual Behavior:
Defaults to en

Issue Analysis:
I'm guessing it is this part that is doing the heavy lifting: https://github.com/QuiiBz/next-international/blob/main/packages/next-international/src/app/middleware/index.ts#L49-L55

Proposed Solution:
Add an option to the middleware to set the initial language to the default language set and not what language the browser is.

Additional Information:

Next.js version: 13.4.13
next-international version: 0.8.1

Best regards,

App directory server component no working when not wrapped

Test with the new version in app directory in server component

I've encountered issues when a server component is not wrap using another function in the file. It works when its wrap by either a async or non-async component.

import { getI18n} from '../../locales/server';

async function Content() {
  const t = await getI18n();
  return (
    <></>
  );
}

export default function Home() {
  return <Content />;
}

Is this wrapping condition to work a bug or an expected nextjs app directory behavior (if yes why could be interesting)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.