Using the Extension

By using the Quetzal VSCode/Cursor extension, you should be able to wrap your strings with the t function automatically. However, this page will document manual use.

Import the ‘t’ function

To translate strings in your app, they must be wrapped in Quetzal’s t function.

The t function is how Quetzal knows what to send for translation, and where to put the translated text.

//Next.js
import { useI18n } from "@quetzallabs/i18n"; //Client-side components
import { getI18nUtils } from "@quetzallabs/i18n"; //Server-side code

//React:
import { useI18n } from "@quetzallabs/i18n-react-native"; //Client-side components
import { getI18nUtils } from "@quetzallabs/i18n-react-native"; //Server-side code

export default function Page() {
  const { t } = useI18n(); //For client-side components
  const { t } = await getI18nUtils(); //For server-side code
}

The Quetzal t function is built on top of the next-intl t function, and behaves similarly. Check out the next-intl docs for t function usage instructions.

Wrap Strings

Once you have imported and initialized the t function, you can begin marking strings for translation

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return <p>{t("Here is normal text")}</p>;
}

Output: Here is normal text

Special cases

Dynamic Text

Use placeholders to dynamically insert values into your translated text.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>
      {t("Here's a {dynamic1} with a {dynamic2}", {
        dynamic: 4,
        dynamic2: "four",
      })}
    </p>
  );
}

Output: Here’s a 4

Pluralization

Handle pluralization based on the value passed.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>
      {t(
        "You have {count, plural, =0 {no followers yet} =1 {one follower} other {# followers}}.",
        { count: 4 }
      )}
    </p>
  );
}

Output: You have 4 followers.

Ordinal Numbers

Format ordinal numbers correctly in different languages.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>
      {t(
        "It's your {year, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} birthday!",
        { year: 1 }
      )}
    </p>
  );
}

Output: It’s your 1st birthday!

Multi-case Text

Handle multi-case selections, such as gender-based translations.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>
      {t("{gender, select, female {She} male {He} other {They}} is online.", {
        gender: "male",
      })}
    </p>
  );
}

Output: He is online.

Escaping Curly Braces

Escape curly braces in your text by using single quotes.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return <p>{t("Escape curly braces with single quotes (e.g. '{name'})")}</p>;
}

Output: Escape curly braces with single quotes (e.g. {name})

Rich Text Formatting

Embed rich text or HTML elements within your translations.

import { useI18n } from "@quetzallabs/i18n";
import { ReactNode } from "react";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>
      {t("Please refer to <guidelines>the guidelines</guidelines>.", {
        guidelines: (chunks: ReactNode) => <a href="/guidelines">{chunks}</a>,
      })}
    </p>
  );
}

Output: Please refer to the guidelines.

Embedded HTML Elements

You can embed HTML elements directly within your translated strings.

import { useI18n } from "@quetzallabs/i18n";
import { ReactNode } from "react";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>
      {t("This is <important>important</important>", {
        important: (chunks: ReactNode) => <b>{chunks}</b>,
      })}
    </p>
  );
}

Output: This is important

Regular Numbers

Format plain numbers according to the user’s locale.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return <p>{t(1234.56)}</p>;
}

Output: 1,234.56 (in en-US locale)

Currencies

Format numbers as currencies.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return <p>{t(499.9, { style: "currency", currency: "USD" })}</p>;
}

Output: $499.90

Percentages

Format numbers as percentages.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return <p>{t(0.857, { style: "percent" })}</p>;
}

Output: 85.7%

Custom Number Formats

Apply custom number formatting.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>{t(1234.56, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</p>
  );
}

Output: 1,234.56

ICU Syntax for Numbers

Utilize ICU syntax for formatting numbers within messages.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>
      {t("This product costs {price, number, currency}", {
        price: 32000.99,
        number: {
          currency: {
            style: "currency",
            currency: "EUR",
          },
        },
      })}
    </p>
  );
}

Output: This product costs €32,000.99

Dates

Format dates according to the user’s locale.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>
      {t(new Date(), {
        year: "numeric",
        month: "long",
        day: "numeric",
      })}
    </p>
  );
}

Output: August 12, 2024 (in en-US locale)

Date Ranges

Format a range of dates.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>
      {t([new Date("2020-01-01"), new Date("2020-12-31")], {
        year: "numeric",
        month: "long",
        day: "numeric",
      })}
    </p>
  );
}

Output: January 1, 2020 – December 31, 2020 (in en-US locale)

Lists (Conjunctions)

Format lists using conjunctions.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>
      {t(["HTML", "CSS", "JavaScript"], {
        type: "conjunction",
      })}
    </p>
  );
}

Output: HTML, CSS, and JavaScript

Lists (Disjunctions)

Format lists using disjunctions.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  return (
    <p>
      {t(["HTML", "CSS", "JavaScript"], {
        type: "disjunction",
      })}
    </p>
  );
}

Output: HTML, CSS, or JavaScript

Variable Lists

Format lists generated from variables.

import { useI18n } from "@quetzallabs/i18n";

export default function Page() {
  const { t } = useI18n();

  const users = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
    { id: 3, name: "Charlie" },
  ];

  const items = users.map((user) => (
    <a key={user.id} href={`/user/${user.id}`}>
      {user.name}
    </a>
  ));

  return <p>{t(items)}</p>;
}

Output: Alice, Bob, and Charlie