Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions blog/en-US/t-macro.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
title: "Stop Wrapping Your Strings in Function Calls"
summary: "The new t macro lets you write translated template literals — no ICU syntax, no options objects, no context providers."
date: 2026-03-18
authors: [ernest]
tags: ['gt-react', 'i18n', 'tagged-template', 'macro', 'developer-experience']
---

## The Problem with `t("Hello, {name}", { name })`

If you've ever internationalized a React app, you've written something like this:

```jsx
const gt = useGT();

return <p>{gt("Hello, {name}! You have {count} items.", { name, count })}</p>;
```

It works. But it's clunky. You're writing ICU MessageFormat syntax by hand, duplicating every variable name in the options object, and pulling `t` out of a hook that requires React context. For something that's supposed to be a thin layer over your existing strings, it's a lot of ceremony.

And it gets worse. The moment you need a translation outside a React component — in a utility function, an event handler, a server action — you're reaching for workarounds, because hooks don't work there.

## Template literals should just work

Here's what the same code looks like with the `t` macro:

```jsx
import { t } from "gt-react/browser";

return <p>{t`Hello, ${name}! You have ${count} items.`}</p>;
```

That's it. Standard JavaScript template literal syntax. No ICU placeholders, no options object, no hook, no context provider. You write your string the way you'd write any template literal, and the compiler handles the rest.

At build time, the GT compiler transforms:

```js
t`Hello, ${name}!`
```

into:

```js
t("Hello, {0}!", { "0": name })
```

The tagged template is pure syntactic sugar — the runtime behavior is identical to calling `t()` with a string. But the developer experience is dramatically better.

## No more React context dependency

The bigger change here isn't syntax — it's architecture. The `t` function exported from `gt-react/browser` doesn't use React context. It doesn't need a hook. It doesn't need to be called inside a component.

This means you can use `t` in:

- **Event handlers**: `onClick={() => alert(t`Saved!`)}`
- **Utility functions**: `function formatError(code) { return t`Error: ${code}` }`
- **Constants and config**: `const LABELS = { save: t`Save`, cancel: t`Cancel` }`
- **Anywhere JavaScript runs on the client**

The old pattern — `useGT()` returning a function scoped to React context — was a bottleneck. It forced translation to be a React concern when it's really a string concern.

## Global registration

If you don't want to import `t` in every file, you can register it globally:

```js
// In your app's entry point
import "gt-react/macros";
```

This sets `globalThis.t`, making the tagged template available everywhere without an import. The compiler is smart enough to detect this — if `t` is already imported from a GT source, it won't inject a duplicate import. If it's not, and you've used `t` as a tagged template, the compiler injects the import for you.

## Concatenation works too

The macro expansion isn't limited to tagged templates. It also handles template literals passed as arguments and string concatenation:

```jsx
// Template literal as argument — also transformed
t(`Welcome back, ${user}`)

// String concatenation — also transformed
t("Hello, " + name + "! Welcome.")
```

Both get normalized to the same `t("...", { ... })` call at build time.

## Getting started

### 1. Install the latest version of `gt-react`

```bash
npm install gt-react@latest
```

### 2. Use the `t` macro

Either import directly:

```jsx
import { t } from "gt-react/browser";

export function Greeting({ name }) {
return <p>{t`Hello, ${name}!`}</p>;
}
```

Or register globally and skip imports:

```js
// app/layout.tsx or entry point
import "gt-react/macros";
```

```jsx
// Anywhere in your app
export function Greeting({ name }) {
return <p>{t`Hello, ${name}!`}</p>;
}
```

### 3. That's it

The GT compiler handles the transformation automatically during your build. No additional config is needed — macro expansion is enabled by default.

---

The `t` macro is a small change to the API surface, but it reflects a larger shift: translations should feel native to JavaScript, not like a framework-specific escape hatch. Write your strings naturally. Let the toolchain do the rest.
2 changes: 1 addition & 1 deletion docs-templates/api/components/derive.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The `<Derive>` component tells the CLI tool to dereference a function call and c
Furthermore, `<Derive>` enforces a strict requirement that all possible content permutations must be statically analyzable.
</Callout>

For more information, see the release notes for [gt-next@6.8.0](/blog/gt-next_v6_8_0).
For more information, see the release notes for [gt-next@6.8.0](/devlog/gt-next_v6_8_0).

---

Expand Down
6 changes: 3 additions & 3 deletions docs-templates/api/strings/derive.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ function Component({ gender }) {
```

With `derive`, agreement becomes trivial.
No select statment is required nor do you need to specify every outcome.
No select statement is required nor do you need to specify every outcome.

```jsx copy
import { derive, declareVar, gt } from '__PACKAGE_NAME__';
Expand Down Expand Up @@ -202,5 +202,5 @@ function Component({ gender }) {
## Next steps
* See [`declareVar`](__DOCS_PATH__/api/strings/declare-var) for marking dynamic content within static functions
* See [`decodeVars`](__DOCS_PATH__/api/strings/decode-vars) for extracting original values from declared variables
* See [`<Derive>`](__DOCS_PATH__/api/components/static) for the JSX equivalent
* Read the [release notes](/blog/gt-next_v6_12_0) for more information
* See [`<Derive>`](__DOCS_PATH__/api/components/derive) for the JSX equivalent
* Read the [release notes](/devlog/gt-next_v6_12_0) for more information
2 changes: 1 addition & 1 deletion docs/en-US/next/guides/cache-components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,4 @@ export default withGTConfig(nextConfig, {

## Next steps

- Read the release notes for this feature, [gt-next@6.10.0](/blog/gt-next_v6_10_0), for more information.
- Read the release notes for this feature, [gt-next@6.10.0](/devlog/gt-next_v6_10_0), for more information.
2 changes: 1 addition & 1 deletion docs/en-US/next/guides/ssg.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,5 @@ To fix this issue, make sure that all of your route segment files (so typically
## Further reading

- Check out the [middleware guide](/docs/next/guides/middleware) required for locale routing
- Check out the [release notes](/blog/gt-next_v6_10_0) for migrating from legacy SSG pattern
- Check out the [release notes](/devlog/gt-next_v6_10_0) for migrating from legacy SSG pattern
- See an example app [here](https://github.com/generaltranslation/gt/tree/main/examples/next-ssg)
Loading