Internationalization
@gtkx/i18n connects React translation APIs to GNU gettext. Your application's applicationId is its gettext domain, and po/ holds its translation catalogs. GTKX uses those catalogs for both the application and its desktop metadata.
Install @gtkx/i18n@1.6.0 and the GNU gettext command-line tools. On Fedora 44:
npm install @gtkx/i18n@1.6.0
sudo dnf install gettext
xgettext --version
msgfmt --versionOther distributions may package the tools separately; both commands must be available when running codegen, development, and builds.
Create a catalog
Add po/LINGUAS at the project root, with one locale per line:
frWrite source messages with t:
import { t } from "@gtkx/i18n";
import { GtkLabel } from "@gtkx/jsx/gtk";
<GtkLabel label={t("Hello, {{name}}!", { name: profile.name })} />;Run codegen:
gtkx codegenGTKX creates po/<applicationId>.pot, initializes the listed PO files, and generates translation types. Translators edit each catalog's msgstr values. Run codegen again after changing messages to update the template and merge those changes into the catalogs. Review fuzzy entries before release; removed messages remain as obsolete entries.
Plurals and context
For a plural message, provide the singular and plural source strings through defaultValue_one and defaultValue_other:
const label = t("{{count}} file", {
count: files.length,
defaultValue_one: "{{count}} file",
defaultValue_other: "{{count}} files",
});Gettext chooses the translated form using the catalog's plural rule. Counts must be non-negative; numeric counts must be safe integers. GTKX extraction requires both source forms and rejects i18next options that cannot be represented by this gettext entry, such as ordinal plurals and zero-specific defaults.
Use context when the same source text has different meanings:
const command = t("Open", { context: "menu command" });A missing contextual entry falls back to the ordinary message. A missing message uses its source text or defaultValue.
Use translations in components
Import the React APIs from @gtkx/i18n so they use the configured gettext backend:
import { useTranslation } from "@gtkx/i18n";
import { GtkLabel } from "@gtkx/jsx/gtk";
function Greeting({ name }: { name: string }) {
const { t } = useTranslation();
return <GtkLabel label={t("Hello, {{name}}!", { name })} />;
}See react-i18next's documentation for its React APIs and i18next's formatting guide for interpolation and formatting. GTKX supplies the catalog integration; gettext still uses one process-wide application domain.
Keep messages extractable
Keep message keys, defaults, contexts, and Trans children static so GTKX can extract them at build time. GTKX follows ESM imports and aliases from @gtkx/i18n and i18next. Dynamic keys and tagged templates cannot be extracted.
Generated declarations under node_modules/.gtkx let TypeScript check message keys and their required interpolation, count, and context options. Run codegen after adding messages; do not edit the declarations.
Develop and build
gtkx dev prepares the catalogs before loading your application. Source saves update the template and translation types before Fast Refresh; they leave translator-owned PO files alone. Run codegen or a build to merge source changes into those files. Saving a PO file or LINGUAS restarts the application because gettext caches catalogs for the lifetime of the process.
gtkx build compiles the catalogs into dist/locale beside the bundle. GTKX initializes localization before the application entry module runs and locates that directory automatically.
Select the process locale before launch:
LANG=fr_FR.UTF-8 LANGUAGE=fr gtkx devGettext translations follow the startup environment. Changing i18next's language does not change libc's active gettext locale; restart the process to change the application's translation language.
Translate package metadata
A normal gtkx deploy refreshes the catalog with application and metadata messages, then compiles and packages the translations. The same PO files supply desktop-entry names, AppStream text, and MIME descriptions.
gtkx deploy --skip-build packages the existing build and recompiles existing catalogs without updating PO files. Every locale in LINGUAS must already have a catalog. Packaged launchers locate the installed catalogs automatically.
See Deploying for the packaging workflow.