Skip to content

Translations

It is very important to make your applications as accessible as possible. Even if the only language you speak as a developer is English the rest of the world might not speak it, but there might be others who will be eager enough to contribute translations for your application. To make their work easy, all you have to do is mark text in code as translatable.

It requires zero setup to start using gettext functions.

tsx
import { createDomain, fmt } from "gnim/i18n"

const { gettext: t, ngettext: n } = createDomain("com.example.MyApp")

function App() {
  const [count, setCount] = createState(0)

  return (
    <Gtk.Button onClicked={() => setCount((c) => c + 1)}>
      <Gtk.Label label={t("Click Me!")} />
      <Gtk.Label
        label={count.as((c) =>
          fmt(n("Clicked once", "Clicked {{count}} times", c), { count: c }),
        )}
      />
    </Gtk.Button>
  )
}

Gettext

GNU gettext is the standard internationalization (i18n) framework used by most Linux desktop applications. It works by extracting translatable strings from source code into .pot template files, which translators then use to create .po files for each language. At runtime, compiled .mo files are loaded based on the user's locale settings.

ts
interface GettextDomain {
  /**
   * @param msgid A string to translate.
   */
  gettext(msgid: string): string
  /**
   * @param msgid1 The singular form of the string to be translated.
   * @param msgid2 The plural form of the string to be translated.
   * @param n The number determining the translation form to use.
   */
  ngettext(msgid1: string, msgid2: string, n: number): string
  /**
   * @param context A context to disambiguate `msgid`.
   * @param msgid A string to translate.
   */
  pgettext(msgctxt: string, msgid: string): string
}

function createDomain(domainName: string): GettextDomain

gettext

The most common function. It marks a string for translation and returns the translated version based on the current locale.

tsx
const { gettext: t } = createDomain("com.example.MyApp")

// Simple translation
<Gtk.Label label={t("Hello, World!")} />

ngettext

Handles plural forms. Different languages have different pluralization rules (some have 2 forms, others have 3 or more). The n parameter determines which form to use.

tsx
const { ngettext: n } = createDomain("com.example.MyApp")

// Plural translation
const message = n("1 item selected", "{{count}} items selected", count)
<Gtk.Label label={fmt(message, { count })} />

pgettext

Provides context for ambiguous strings. The same word might need different translations depending on context (e.g., "Open" as a verb vs. adjective).

tsx
const { pgettext: p } = createDomain("com.example.MyApp")

// "Open" as a verb (action)
<Gtk.Button label={p("action", "Open")} />

// "Open" as an adjective (status)
<Gtk.Label label={p("status", "Open")} />

Extract translatable strings

Usually gettext is aliased to _, ngettext is aliased to _N and pgettext is aliased to _P. However, in JavaScript the underscore prefix is usually a marker for unused symbols, so it's recommended to use aliases that you'd find in other JavaScript internationalization libraries, such as t. These aliases can be configured with the --keyword flag when extracting translatable strings.

sh
xgettext **/*.ts **/*.tsx \
  --output=po/messages.pot \
  --keyword=t \
  --keyword=n:1,2 \
  --keyword=p:1c,2

This produces a .pot template file which can be used to write translations.

Init locale translation

sh
LOCALE="locale" # example: `de`, `es`, `it`, `fr`
msginit \
  --locale=$LOCALE \
  -i po/messages.pot \
  -o po/$LOCALE.po \
  --no-translator

After filling the translations restart the dev server with the chosen application ID and chose locale.

sh
LOCALE="locale" # example: `de`, `es`, `it`, `fr`
COUNTRY="country" # example: `DE`, `ES`, `IT`, `FR`
LANG="${LOCALE}_${COUNTRY}.UTF8" gnim dev src/main.tsx --id com.example.MyApp