Skip to content

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.

View as Markdown

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.

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.

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

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

TypeScript
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 hasThe 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 theseThe 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 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.

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.

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:

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:

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:

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, 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:

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:

.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.

Shopify themesNext 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.

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 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 or in the CMS.

  • .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.