npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

next-rosetta

v2.0.2

Published

Next.js + Rosetta with native i18n support

Downloads

390

Readme

next-rosetta 🌎🌍🌏

Add i18n in less than 5 minutes — Built for Next.js 10

demo

Lightweight, simple, easy to integrate, extendable, no custom server required and efficient because it will only download the required translations for your current locale.

See live demo

Supports typed locales via the Template literal and Recursive types. Requires TypeScript >=4.1.0

Note: Currently types is only supported using dot notation. Eg: t("about.title.0.description").

typescript intellisense example

Usage

Install

First step: downloading this dependency.

# with npm
npm install next-rosetta

# with yarn
yarn add next-rosetta

Update next.config.js

Update your next.config.js by adding a i18n section:

// ./next.config.js
module.exports = {
  i18n: {
    locales: ["en", "es"],
    defaultLocale: "en",
  },
};

For more info refer to: https://nextjs.org/docs/advanced-features/i18n-routing

Create locales

Make a directory named i18n on the root of your project. If you are using TypeScript you can define the type schema and create every locale based on that interface. Type safety! Excelente!

// ./i18n/index.tsx
export interface MyLocale {
  locale: string;
  title: string;
  subtitle: string;
  profile: {
    button: string;
  };
  welcome: string;
}
// ./i18n/en.tsx
import type { MyLocale } from ".";

export const table: MyLocale = {
  locale: "English",
  title: "Next.js 10 + Rosetta with native i18n integration",
  subtitle: "Click below to update your current locale 👇",
  profile: {
    button: "Press me!",
  },
  welcome: "Welcome {{name}}! 😃", // with variable replacement
};
// ./i18n/es.tsx
import type { MyLocale } from ".";

export const table: MyLocale = {
  locale: "Español",
  title: "Next.js 10 + Rosetta con integración nativa de i18n",
  subtitle: "Presiona aquí abajo para cambiar tu lenguaje 👇",
  profile: {
    button: "Presióname!",
  },
  welcome: "Bienvenido {{name}}! 👋", // with variable replacement
};

Dealing with long texts? You can use endent or similar libraries.

import endent from "endent";

import type { MyLocale } from ".";

export const table: MyLocale = {
  markdown: endent`
    # Title

    This string will have a correct right indentation.
  `,
}

Add the i18n provider

Import I18nProvider from "next-rosetta" and wrap your app in it. From pageProps take table which is the current locale object and pass it to I18nProvider.

// ./pages/_app.tsx
import type { AppProps } from "next/app";
import { I18nProvider } from "next-rosetta";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <I18nProvider table={pageProps.table}>
      <Component {...pageProps} />
    </I18nProvider>
  );
}

export default MyApp;

Load and render

To import locales you must call this on the server side code (or on the static render):

const locale = "en";
const { table = {} } = await import(`../i18n/${locale}`);

Here is an example if you are using getStaticProps:

// ./pages/index.tsx
import type { GetStaticProps } from "next";
import { useI18n, I18nProps } from "next-rosetta";

// Import typing
import type { MyLocale } from "../i18n";

function HomePage() {
  const { t } = useI18n<MyLocale>();
  return (
    <div>
      <h3>
        {t("title")}
      </h3>
      <p>
        {t("welcome", { name: "John" })}
      </p>
      <button>
        {t("profile.button")}
      </button>
    </div>
  )
}

// You can use I18nProps<T> for type-safety (optional)
export const getStaticProps: GetStaticProps<I18nProps<MyLocale>> = async (context) => {
  const locale = context.locale || context.defaultLocale;
  const { table = {} } = await import(`../i18n/${locale}`); // Import locale
  return { props: { table } }; // Passed to `/pages/_app.tsx`
};

Any component can access the locale translations by using the useI18n hook.

// ./pages/index.tsx
import Link from "next/link";
import { useRouter } from "next/router";
import { useI18n } from "next-rosetta";

// Import typing
import type { MyLocale } from "../i18n";

function LocaleSelector() {
  const { locale, locales, asPath } = useRouter(); // Get current locale and locale list
  const { t } = useI18n<MyLocale>();
  // ...
}

For more info regarding rosetta API please refer to: https://github.com/lukeed/rosetta

Example

Here is a more complete example of page inside the /page directory:

// ./pages/index.tsx
import { useI18n, I18nProps } from "next-rosetta";
import { useRouter } from "next/router";
import Head from "next/head";
import Link from "next/link";

import type { MyLocale } from "../i18n"; // Import typing

export default function Home() {
  const { locale, locales, asPath } = useRouter();
  const i18n = useI18n<MyLocale>();
  const { t } = i18n;

  return (
    <div>
      <Head>
        <title>{t("locale")}</title>
      </Head>
      <main>
        <h1>{t("title")}</h1>
        <p>{t("subtitle")}</p>
        <p>{t("welcome", { name: "John" })}</p>
        <ul>
          {locales?.map((loc) => (
            <li key={loc}>
              <Link href={asPath} locale={loc}>
                <a className={loc === locale ? "is-active" : ""}>{loc}</a>
              </Link>
            </li>
          ))}
        </ul>
      </main>
    </div>
  );
}

// Server-side code

import type { GetStaticProps } from "next";

export const getStaticProps: GetStaticProps<I18nProps<MyLocale>> = async (context) => {
  const locale = context.locale || context.defaultLocale;
  const { table = {} } = await import(`../i18n/${locale}`); // Import locale
  return { props: { table } }; // Passed to `/pages/_app.tsx`
};

Example with getServerSideProps

This is compatible with your current server side logic. Here is an example:

// ./pages/posts/[id].tsx
import type { GetServerSideProps } from "next";
import { useI18n, I18nProps } from "next-rosetta";

// Import typing
import type { MyLocale } from "../i18n";

type Props = { post: any };

export default function PostPage({ post, ...props }: Props) {
  const { t } = useI18n<MyLocale>();
  // ...
}

export const getServerSideProps: GetServerSideProps<Props & I18nProps> = async (context) => {
  const locale = context.locale || context.defaultLocale;

  const data = await fetch(`/posts/${context.params["id"]}`).then(res => res.json());

  const { table = {} } = await import(`../../i18n/${locale}`);
  return { props: { table, post: data } };
};

FAQ

Is a JSON locale table supported?

Yes. Just import is as await import(../../i18n/${locale}.json);

React complains about unknown is not a valid children type

If you have this error:

Type 'unknown' is not assignable to type 'ReactNode'.ts

You are probably using a wrong path, you have a typo or you are using arrays as path (t(["foo", "bar"]) won't infer type).

To force a type:

const en = {
  title: "Hello",
}
const { t } = useI18n<typeof en>();
// type is 'unknown'
const text = t("foo") // note 'foo' doesn't exist in locale definition.
// React error
<span>{text}<span>
// type is 'string'
const text = t<string>("foo")
// ok
<span>{text}<span>

How to add a button to change locale?

Create some <Link /> and set the locale prop to change locale. It is important to note you should set the href variable to the current asPath from useRouter.

The difference between router.route and router.asPath is that the first has path value with params (eg: /products/[id]) and asPath has the replaced values.

export default function ChangeLocale() {
  const { locale, locales, asPath } = useRouter();
  const i18n = useI18n<MyLocale>();

  return (
    <div>
      {locales?.map((loc) => {
        const isActive = loc === locale;
        return (
          <Link key={loc} href={asPath} locale={loc}>
            <a>{loc}</a>
          </Link>
        );
      })}
    </div>
  );
}

IDE Autocomplete

IDEs won't autocomplete while typing, only after the path is written you can see the types.

This is a limitation of Typescript, we would require a pre-compilation steps of each possible path to allow this.

TODO

  • Support pluralization.
  • Support function definitions with arguments. Only serializable locales are possible right now.