Asset Goes Here Docs Sign in
ContentsWriting and translations
Request and review

Writing and translations

Beta. Translations, agh strings, the --locale options and the get_strings MCP tool are new and may change, including whether each language stays an asset of its own.

agh tracks writing the way it tracks art: someone requests it, a writer or a tool delivers it, a person reviews it, and accepted files are published to the repository with how they were made. Translations are assets too, each one pointing at the asset it translates.

You wantKindFiles agh reads
A whole piece of writing: a lore entry, quest text, an item description, a store blurbtextMarkdown (.md), plain text (.txt), Ink (.ink), Yarn Spinner (.yarn)
Keyed lines the game looks up: UI labels, item names, barks, dialogue lines by idstringsgettext .po and .pot, .json (flat or nested keys, and ARB descriptions), .csv with a header row, XLIFF 1.2 and 2.0 (.xlf, .xliff)
The same thing in another languagethe source's kindthe source's format

Tag writing by what it is, such as role=dialogue or role=lore, rather than with new kinds. Files in other formats, such as a .docx, are stored and reviewed as they are, without the line-by-line checks.

Request writing with its translations

Give a text or strings request the languages to translate it into:

agh request ui.menu.strings --kind strings --path locale/en/menu.po \
      --brief "Main menu labels" --spec max_length=24 --locales fr,de,pt-BR

agh adds one translation request per language to the same batch: ui.menu.strings.fr, ui.menu.strings.de and ui.menu.strings.pt_br. Languages are BCP 47 tags such as fr, pt-BR or zh-Hans; the id uses the tag lowercased with _. The source is in en unless the request gives --locale.

A translation goes at the source's path with the language swapped in: locale/en/menu.po becomes locale/fr/menu.po, i18n/en.json becomes i18n/fr.json, and text/menu.en.json becomes text/menu.fr.json. A path that doesn't name the language gets it before the extension, so lore/prologue.md becomes lore/prologue.fr.md. For another layout, give --locale-path translations/{locale}/menu.po.

In a request file the same fields are locale, locales, locale_path and translation_of:

assets:
  - id: lore.prologue
    kind: text
    path: lore/prologue.md
    brief: The prologue the narrator reads over the opening.
    locales: [fr, ja]

To add a language later, request the translation on its own, or choose Add a locale… on the source's page in the web app's Library, which shows where each file will go before it asks:

agh request --translation-of ui.menu.strings --locale ja

The translation takes its kind, path, tags and spec from the source, and a brief saying what to translate. A request can override the brief, path or notes.

Waiting for the source

A translation can't be claimed until its source has an accepted file, since there is nothing to translate yet. agh next, agh list --claimable and guest links skip it, and agh show says it is waiting. Approving the batch approves the translations with the source, so they open the moment the source is accepted.

Translate

agh next --locale fr                     # the next translation into French
agh get ui.menu.strings.fr --which source
agh strings ui.menu.strings.fr           # the source's lines, with comments for translators
agh fill ui.menu.strings.fr ./fr.po --provenance ai-assisted --made-by "DeepL, edited by Jeanne"

Over MCP, claim_next_request takes locale, get_strings reads the source line by line, and deliver_text delivers the translation. A translation is delivered in the source's format, with the same keys. Translate the text; keep placeholders such as {name}, %s, {{count}} and markup such as [b] or <color=red> as they are.

Record how it was made: an untouched machine or AI translation is ai, a person's edit of one is ai-assisted, and a translator's own work is human.

What agh checks

A strings file has to be the format its path names. A .po that isn't PO, or a .json that isn't JSON, is refused, since nothing could read it.

Everything else is a warning for the reviewer, the way an image's size is checked against its spec. None of it blocks an acceptance:

For writing, agh compares paragraph by paragraph and says when the counts differ.

Review

agh strings <asset-id> (GET /projects/{project}/assets/{id}/strings, MCP get_strings) shows a file line by line: a source's key, text, comment and flags, or a translation's source text beside its own. --flagged shows only the lines with a flag. A source under review marks the lines that differ from its accepted file.

Accepting and rejecting work as for any asset.

In the web app, Review shows writing as text to read, with Changes comparing it word by word with the file before it and Source showing the file as delivered. A strings file, and any translation, is a table you can narrow to All, Flagged or Changed; it opens on Flagged when anything is flagged. Rejecting a flagged file sends its flags back as the note, unless you write your own.

The Library shows each translation as a language chip on the writing it translates, marked done, in review, out of date, waiting for source or not started. Tick Translations as tiles to see them as tiles of their own.

When the source changes

Accepting a new version of a source keeps its translations in step:

Taking back the source's acceptance (agh review --undo) holds its translations again. A source can't be removed while it has translations; remove them first.

Publishing and the AI disclosure

Translations go in the same pull request as everything else. .agh/provenance.yaml lists each translation's locale and translation_of.

In the AI disclosure, writing in the game's own language counts as story and writing, and translations count as localization, with the counts for each language so the share of AI translation shows per locale.

Guest links for translators

A guest link can reach one language, so a translator hired outside agh sees only those requests: pick the language on Open requests and choose Share with a guest, or send "locale": "fr" on POST /guests. The guest page fetches the source with which=source.

Strings already in your repository (beta)

agh adopt finds strings and writing files already on the branch, including JSON, CSV, Markdown and plain text whose path names a language, such as i18n/en.json, i18n/en-us.json or locale/fr/menu.po. Files at the same path in other languages are adopted as translations of the English one. If it takes a settings file for strings, agh untrack drops it and keeps it out of later adoptions. See agh guide adopt.

For agents: this page as Markdown is at /docs/translations.md, and the CLI prints it with agh guide translations.