# General Translation Knowledge Base: Can I do i18n without translation files?
URL: https://generaltranslation.com/en-GB/kb/i18n-without-translation-files.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Yes. With gt-react or gt-next, content stays inline in your components inside a `<T>` component, and translations are generated at build time as output, so there are no keys or per-locale JSON files to write and keep in sync.
Category: Internationalisation
Last updated: 2026-05-07
Authors: Jackie Chen, Ernest McCarter
Published: 2026-05-07

Anyone who&#39;s internationalised a JavaScript app knows the workflow. You install an i18n library, create an `en.json` file, pull every user-facing string out of your components, assign each one a key, and reference the key where the string used to be. Then you duplicate that JSON file for every language you support: `es.json`, `fr.json`, `ja.json`.

At first, it&#39;s fine. Thirty strings, three languages, 90 entries.

Then your app grows. Six months later, you have 400 strings and 12 languages: 4,800 entries across a dozen files. A developer adds a new feature, writes five new strings and forgets to update three of the translation files. Nobody notices until a user in Tokyo sees English fallbacks in a Japanese interface. Someone suggests buying a translation management system.

<Video src="https://assets.gtx.dev/i18n-no-translation.mp4" />

## Video

The video is narrated in English, with English captions.

[Can I do i18n without translation files?](https://caxecubb9c64cezv.public.blob.vercel-storage.com/films/kb-i18n-without-translation-files.mp4) (46 secs)

General Translation lets you stop writing translation files with `gt-react` or `gt-next`. Run `npx gt init`, write the English inside a `<T>` component, then add `npx gt translate` to your build script so every build writes the translations.

## The file management tax

Translation files create a maintenance burden that scales with the number of languages you support. Change a string and you need to update a dozen files. Those files drift out of sync. Keeping them aligned requires tooling, automation or discipline.

The ecosystem of workarounds tells the story: VS Code extensions that auto-generate translation keys, type generators that validate your JSON against TypeScript interfaces, linters for unused keys, CI checks that verify all locales are in sync. You don&#39;t build this kind of tooling for a workflow that works well.

Your UI says one thing, your translation files say another, and a key-based mapping holds the two together:

```tsx
function CheckoutSummary({ itemCount, discount }) {
  const { t } = useTranslation('checkout');

  return (
    <div>
      <h2>{t('summary.title')}</h2>
      <p>{t('summary.item_count', { count: itemCount })}</p>
      {discount && <p>{t('summary.discount_applied', { percent: discount })}</p>}
      <p>{t('summary.total_label')}</p>
      <small>{t('summary.tax_notice')}</small>
    </div>
  );
}
```

To understand what this component renders, you&#39;d have to open `checkout.json`, find the `summary` namespace, and cross-reference five keys. Across hundreds of components, code review becomes an exercise in file-hopping.

ICU message syntax makes this worse. The format that libraries like `i18next`, `react-intl`, and `next-intl` use for plurals, gender, and interpolation is its own mini-language embedded in JSON strings. A misplaced brace in `{count, plural, one {# item} other {# items}}` won&#39;t surface until runtime. Linters exist for this, but most teams skip them. Inline code, by contrast, is checked by your TypeScript compiler and your IDE the moment you type it.

## Translating code directly

What if you didn&#39;t extract strings at all?

```tsx
import { T } from 'gt-react';

function CheckoutSummary({ itemCount, discount }) {
  return (
    <T>
      <div>
        <h2>Order Summary</h2>
        <p>You have {itemCount} items in your cart.</p>
        {discount && <p>{discount}% discount applied!</p>}
        <p>Total (before tax):</p>
        <small>Tax calculated at checkout.</small>
      </div>
    </T>
  );
}
```

The [`<T>`](/docs/react/reference/components/t) component in [`gt-react`](/docs/react/react-quickstart) marks a block of JSX for translation. The English stays in your component. When a user visits in Spanish, the content inside [`<T>`](/docs/react/reference/components/t) is replaced with its Spanish equivalent. Structure and formatting carry over.

There&#39;s no `t('checkout.summary.title')`, no `en.json`, no per-locale JSON files to keep in sync.

## Translations as build output

They&#39;re generated at deploy time. The [GT CLI](/docs/cli/reference/commands/translate) scans your codebase for everything inside [`<T>`](/docs/react/reference/components/t) components and produces translations for every target language. The output goes into a gitignored directory, like compiled CSS or bundled JavaScript.

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["es", "fr", "ja", "de", "ko", "zh"],
  "files": {
    "gt": {
      "output": "public/_gt/[locale].json"
    }
  }
}
```

```txt title=".gitignore"
public/_gt/
```

In development, translations happen on-demand. Change a string, refresh, see it in Japanese immediately. In production, everything is pre-generated and served from a CDN. Translation files still exist on disk, but they&#39;re output artefacts, not something you author or maintain.

## What changes in practice

The difference shows up in day-to-day work more than in architecture diagrams.

A PR that adds a new section to a page reads like a PR that adds a new section to a page. The reviewer sees the actual words, not `checkout.summary.discount_applied_notice`. Code review no longer requires a side-by-side comparison with a JSON file.

Refactoring is less painful. Rename a component, move it, split it into pieces. Translations follow the content because there are no keys to remap. Adding a language is a one-line config change. Deleting a component means its translations quietly stop being generated, rather than leaving orphaned keys across 15 files that nobody wants to tidy up.

## Where i18n libraries are heading

The trend across the ecosystem is towards inline strings. Early i18n libraries such as `i18next` and `react-intl` were built when machine translation wasn&#39;t viable and every string had to be handed off to a human translator. Dictionaries made sense as an interchange format. That constraint has gone, and the developer-experience cost of maintaining parallel string files is increasingly hard to justify.

`next-intl` added [experimental inline messages with automatic extraction](https://next-intl.dev/docs/usage/extraction) alongside its dictionary mode. [Lingui&#39;s Compiler](https://lingui.dev/ref/swc-plugin) extracts messages at build time from inline tagged templates. [Paraglide](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) takes a different route, compiling message files into per-locale tree-shakeable functions. The approaches differ, but in all of them content is moving closer to the component. GT takes this to its logical conclusion: your JSX is the source of truth, and translation is a compile step.

## The trade-off

When you write content inline, you&#39;re writing in your native language. Your component structure, your sentence patterns and your UI flow all reflect how you think in English (or whatever your source language is). A dictionary-based approach such as `next-intl` is more language-agnostic by design, because the component never contains a real sentence in any language – just a key that points elsewhere.

But most developers are already thinking in one language when they build UI. The layout, the copy and the button labels are all conceived in English first. That bias is baked into the design whether the strings are inline or in a JSON file. We think the i18n framework should adapt to how you actually work. Build the app naturally and let the framework handle translation, rather than abstracting content into keys for the sake of language neutrality.

## Getting started

```bash
npx gt@latest init
```

The set-up wizard configures your project, installs dependencies and sets up translation hot reloading for development. See the full walkthrough in the [CLI Quickstart](/docs/cli/quickstart).

Once that&#39;s done, wrap content in [`<T>`](/docs/react/reference/components/t), run your dev server and use the [`<LocaleSelector>`](/docs/react/reference/components/locale-selector) component to switch between languages:

```tsx
import { LocaleSelector } from 'gt-react';

function Header() {
  return (
    <header>
      <nav>{/* ... */}</nav>
      <LocaleSelector />
    </header>
  );
}
```

Translations happen on-demand in development, so you can see your app in any language straight away. In production, translations are pre-generated. Add the translate step to your build, and set `GT_PROJECT_ID` and `GT_API_KEY` with your hosting provider:

```json title="package.json"
{
  "scripts": {
    "build": "npx gt translate && next build"
  }
}
```

The [`translate`](/docs/cli/reference/commands/translate) command scans your codebase for all [`<T>`](/docs/react/reference/components/t) usage and generates translations, so every locale is ready when your app builds.

`gt-react` is open source. For Next.js App Router, use [`gt-next`](/docs/react/nextjs-quickstart). For React Native, use [`gt-react-native`](/docs/react/react-native-quickstart).

## Related questions

- [How do I translate conditional text in JSX?](https://generaltranslation.com/en-GB/kb/translate-conditional-jsx.mdx): Rather than translating each side of a ternary separately, wrap the whole sentence in a `<T>` component and express the condition with `<Branch>`, so that the translator sees the full context and can reorder the sentence for each language.
- [How do I handle plurals in React?](https://generaltranslation.com/en-GB/kb/how-to-handle-plurals-in-react.mdx): Don't hard-code plural logic with checks such as n === 1. A small helper is enough for English-only apps, but multilingual apps need locale-aware plural rules, which an i18n component such as `<Plural>` from gt-react or gt-next handles for every language.
- [What is the best localisation software for developers?](https://generaltranslation.com/en-GB/kb/best-localization-software.mdx): Look for one system that covers the whole pipeline, from the i18n library in your code to translation and delivery, instead of stitching together a separate library, translation management system and translation vendor.

## Previous and next questions

- Previous question: [What is the best localisation software for developers?](https://generaltranslation.com/en-GB/kb/best-localization-software.mdx)
- Next question: [Crowdin alternatives: General Translation vs. Crowdin](https://generaltranslation.com/en-GB/kb/general-translation-vs-crowdin.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/en-GB/kb/sitemap.md) for every article.
