# Languages

> How midcode finds your site's languages, shows every text per page and per language, writes translations into your own files, and translates what's missing.

- Page: https://midcode.app/docs/data/languages
- From the midcode docs. Every page as Markdown: https://midcode.app/llms.txt

midcode reads the translations your project already has, however it keeps them, and shows every text of the site side by side: the default language on the left, another language on the right. What you type is written into the same file and place the other languages use. Missing texts can be translated on your Mac with Apple Intelligence, or handed to your agent.

A site in one language can be read, edited and translated too. Its translations wait in a file beside the project until the languages are set up in code: see [A site in one language](#a-site-in-one-language).

## What midcode detects

midcode looks for three ways of keeping translations. A project can mix them.

**Catalogs**, one JSON file per language, in a folder called `messages`, `locales`, `locale`, `lang`, `langs`, `i18n`, `translations`, `dictionaries`, `dicts`, `intl` or `content` (and `public/locales`). This is what next-intl, i18next, Paraglide and the Next.js docs' dictionaries use.

```text
messages/en.json
messages/es.json

public/locales/en/common.json
public/locales/es/common.json
```

**A record per text**, in the code: an object whose keys are all language codes and whose values are strings.

```ts
const price = { en: 'Free', es: 'Gratis' }
```

**A dictionary per language**, in the code: the same, with an object of texts under each language.

```ts
const copy = {
  en: { title: 'Design in code', cta: 'Download' },
  es: { title: 'Diseñá en código', cta: 'Descargar' },
}
```

It also reads where the project lists its languages: a type such as `type Lang = 'en' | 'es'`, an array called `locales`, `languages`, `langs` or `supportedLocales`, and a `defaultLocale` property. The default language is that `defaultLocale`, else English if the site has it, else the first one found.

A language code is two letters, with an optional region or script: `en`, `es`, `pt-BR`, `zh-Hant`, `en_US`. One stray `{ en, es }` object is not taken as a setup: without catalogs, midcode needs at least two such objects before it treats the site as multilingual.

Not listed as texts: a link per language (`{ en: '/events', es: '/es/eventos' }`) and values that are only punctuation.

## The language menu

With two or more languages, the top bar shows the current language as a flag and its code. The menu lists the site's languages and **Translations…**.

Picking a language shows the canvas in it, when the site has a URL per language. midcode reads that from the `app` folder:

| The project has | The canvas goes to |
| --- | --- |
| `app/[locale]` or `app/[lang]` | `/es/pricing` for every language, the default included |
| The same, with `localePrefix: 'as-needed'` or `'never'` in its routing file or middleware | `/pricing` for the default, `/es/pricing` for the others |
| A folder per language (`app/es`, `app/(es)/es`) | `/pricing` for the default, `/es/pricing` for the others |
| None of these | The canvas stays in one language |

In the last case the menu says "This site has no URL per language, so the canvas stays in one. The texts are all in Translations."

## The Translations view

Choose **Translations…** in the language menu. The view takes the place of the canvas; `Esc` goes back.

- On the left, the languages; each one but the default shows the share of texts it has. Click one to work on it. Under them, "Where they live" lists the catalogs and how many code files hold translations.
- In the middle, two columns: the default language and the one you picked. Texts are grouped by the page that shows them (Home, `/pricing`…), then **Everywhere** for what many pages or a layout share, **Site settings** for page titles and descriptions (what [Site settings](https://midcode.app/docs/editor/site-settings.md) edits), and **Other** for the rest.
- Inside a group, a block per section: the variable, or the catalog's namespace. Each text shows its key; hover it for the button that opens `file:line` in your code editor.

How a text gets its page: midcode follows the imports from each page file to the file that holds the text. For catalogs, it looks for the files that ask for a namespace (`useTranslations('Hero')`, `getTranslations({ namespace: 'Hero' })`, `useTranslation('common')`). A namespace called `meta`, `metadata`, `seo` or `head` goes to Site settings. A catalog key no file asks for is listed under "Messages".

To change a text, type in its field and leave it, or press `Enter` (`⇧Enter` adds a line). An emptied field is not written. A field marked **Missing** has no text in that language; "Same as English" (with the default language's name) means it still holds the default text. With the default language picked on the left, the single column edits the site's own texts.

**Show** switches between **All** and **To translate** (missing, or the same as the default). **Search** looks in the texts of every language and in the keys.

## Add a language

**Add a language**, under the list on the left, offers 64 languages. Picking one:

- copies each catalog of the default language to the new one (`messages/en.json` → `messages/fr.json`), so it starts with the default texts,
- adds the language's key to every record and dictionary in the code, with the default text for now,
- adds it to the `Lang` type and to the `locales` lists.

A record whose values are the languages' own names (a language switcher's labels) gets the new language's name instead of a copy.

```diff title="lib/i18n.ts"
-export type Lang = 'en' | 'es'
-export const locales = ['en', 'es']
+export type Lang = 'en' | 'es' | 'fr'
+export const locales = ['en', 'es', 'fr']
 
-export const names = { en: 'English', es: 'Español' }
+export const names = { en: 'English', es: 'Español', fr: 'Français' }
```

The new language then shows everything as "Same as English" until it's translated. midcode does not create routes: if the site keeps a folder per language, it says "Its pages (/fr/…) still need routes: ask your agent."

## Translate what's missing

With a language other than the default picked:

- **Translate missing** translates every text that is missing or still the default, on your Mac, with Apple Intelligence. It needs macOS 26 on Apple silicon; without it the button is disabled. Texts go ten at a time, each with a hint of what it is (alt text, page title, its key), and each batch is written as it comes back. The button becomes **Stop**, with the count. Spaces at the edges of the original and a lowercase first letter are kept.
- **Copy for agent** puts a brief on the clipboard: the language, the files the translations live in, the rules (keep placeholders, ICU plurals, tags, URLs and brand names as they are), and one line per text to translate with its `file:line`, key and default text. Paste it to [your agent](https://midcode.app/docs/agents/overview.md).

With the crosshair of the agent's prompt on, clicking a text in this view hands it over: what it says, where it's written, and its translation if it has one.

Read what a model wrote before you publish it.

## What midcode writes

A translation replaces that one string. In a catalog:

```diff title="messages/es.json"
   "hero": {
-    "title": "Design in code"
+    "title": "Diseñá en código"
   }
```

A key the language doesn't have yet is added next to its siblings, with any object it needs on the way:

```diff title="messages/es.json"
   "hero": {
-    "title": "Diseñá en código"
+    "title": "Diseñá en código",
+    "subtitle": "Cada cambio es código"
   }
```

In the code, the string keeps its quotes, and a missing language gets its key at the end of the object:

```diff title="components/Pricing.tsx"
-const price = { en: 'Free', es: 'Gratis' }
+const price = { en: 'Free', es: 'Gratis', fr: 'Gratuit' }
```

Everything written to one file at once is one step of `⌘Z` and one entry in [Publish](https://midcode.app/docs/publish/publish.md), labelled like "Translation (es): 3 in es.json".

## A site in one language

When midcode finds fewer than two languages, the top bar shows a languages button instead of a flag. It says "This site is in one language" and offers:

- **Open the texts**: the Translations view, with the texts as they're written in the markup.
- **Ask your agent to set it up** and **Copy the brief**: a request to move every text into one catalog per language (`messages/<locale>.json`), add a language switcher and `hreflang` alternates, in the way that fits the framework (for Next.js: next-intl, a `[locale]` segment and middleware). Showing another language on the site is a change to its routes and code, so it's the agent's job.

The texts come from your JSX: text between tags, `alt`, `title`, `placeholder`, `aria-label` and similar attributes, and the `title` and `description` of `export const metadata`. The site's language is the `lang` of its `<html>` tag, or English.

Editing a text in the site's own language rewrites it where it's written, as an undoable edit:

```diff title="app/page.tsx"
-<h1>Design in code</h1>
+<h1>Design, in code</h1>
```

**Add a language** works here too, and the new language starts empty. What you translate (by hand, with **Translate missing**, or through **Copy for agent**) is not written into your code. It's kept in `.midcode/translations.json`, by the site's own text:

```json title=".midcode/translations.json"
{
  "source": "en",
  "languages": [
    "es"
  ],
  "texts": {
    "Design in code": {
      "es": "Diseñá en código"
    },
    "Download": {
      "es": "Descargar"
    }
  }
}
```

The same text in several places shares one translation. If you reword the site's text, its translations follow the new wording. The brief for the agent points at this file, so the agent that sets up the languages uses these translations as they are and only translates what's missing. Commit the file if you want them kept with the repository. See [What midcode adds to your project](https://midcode.app/docs/start/project-files.md).

## Shopify themes (next release)

In a Shopify theme the catalogs are the theme's `locales/*.json`. The file named `en.default.json` marks the default language, and the `*.schema.json` files (the theme editor's own texts) are left out. See [Shopify themes](https://midcode.app/docs/shopify/themes.md).

## Limits

- Catalogs are read when they're JSON. One file per language written as `.ts`, YAML or `.po` is not found.
- Records and dictionaries are read from `.ts`, `.tsx`, `.js`, `.jsx` and `.mjs` files under 400 KB. Texts in the script of a `.vue`, `.svelte` or `.astro` file are not.
- Language codes have to be ones midcode knows (the 64 of the list, with any region).
- Grouping by page and switching the canvas read [Next.js](https://midcode.app/docs/frameworks/nextjs.md) routes (`app/` and `pages/`). In other frameworks every text is still listed and editable, under Other or Messages, and the canvas stays in one language.
- A missing item of an array in a catalog can't be added from here: add it in the file.
- **Add a language** adds texts, not routes, middleware or a language switcher.
- A one-language site's texts are read from JSX files only (`.tsx`, `.jsx`, `.js`, `.mjs`). Text that comes from a variable, a prop or a data file isn't listed there; edit it on the [canvas](https://midcode.app/docs/editor/text-and-media.md) or in the [CMS](https://midcode.app/docs/data/cms.md).
- `.midcode/translations.json` is written directly, without a step in `⌘Z`. Publish lists it as a changed file, like any other file of the project.
- Undoing **Add a language** takes back the edits to your code file by file. The catalog it created is emptied, not deleted.
- midcode writes texts as you type them. Placeholders (`{name}`, `%s`) and plural forms are yours to keep right.

To write translations in a shape midcode picks up, see [Code that midcode can edit](https://midcode.app/docs/agents/editable-code.md).
