Skip to content

A repository and its locale files ​

The editor translates the JSON locale files of a GitHub repository. These files stay the source of truth. The editor keeps no text of its own.

The files ​

The files of one language are in one directory, under a path that you choose:

<localesPath>/
├── manifest.json        optional, see below
├── fr/                  the source language
│   ├── common.json
│   └── checkout.json
└── en/
    ├── common.json
    └── checkout.json
  • The name of a directory is a language tag, for example fr or pt-BR.
  • The name of a file has only letters, digits, - and _. The editor leaves out a file such as common.v2.json, and the API writes a warning to its log.
  • A file holds nested objects of strings. A key is the dotted path to a string, as i18next reads it: {"cart": {"empty": "…"}} gives cart.empty.
  • A file of the source must exist in the source directory. A file of another language can be absent: all its keys are then missing.

Formats gives the placeholders and the plurals that a text can hold, and the formats that git8n does not read.

Add a repository ​

An admin adds a repository with POST /i18n/repositories. Install the GitHub App on the repository first: The GitHub App tells how.

FieldRequiredWhat it is
installationIdyesThe installation of the App, from GET /i18n/installations.
owner, nameyesThe repository, for example acme and acme-app.
localesPathyesThe directory that holds the directories of the languages.
languagesyesThe languages, the source language first.
branchnoThe base branch, which the editor reads and publishes to. The default is main.
manifestPathnoThe manifest, from the root of the repository. The default is <localesPath>/manifest.json.
publishModenocommit or pull_request: how a publication reaches the base branch. The default is commit.
sh
curl -X POST https://<editor>/i18n/repositories \
  -H 'Content-Type: application/json' \
  -d '{"installationId": 42, "owner": "acme", "name": "acme-app",
       "localesPath": "src/locales", "languages": ["fr", "en"]}'

These settings belong to the repository, so they are in the database and not in the environment of the API. To add a language, change the row. You do not need to deploy again.

The page of every repository ​

/ lists the connected repositories. /repositories/<id> opens the table of one of them, and « g8n git8n » in the header leads back to the list.

The list reads GET /i18n/overview:

ParameterValuesDefault
pagefrom 11
perPage1 to 10020
qa part of owner/namenone
sortname, created, progressname
orderasc, descasc

Each row holds the share of keys published in each language that the account can open, and the open pull request of a repository that publishes by pull request. Both come from GitHub, so the API reads them only for the rows of the page. An order by progress reads every repository that the search keeps, four at a time. The API holds a progress and a pull request for one minute.

A repository that GitHub does not answer stays in the list, with progress: null and the reason in progressError.

GET /i18n/repositories keeps its answer: every row, with no progress. The MCP server and the picker of the table read it.

How a publication reaches the base branch ​

A repository publishes in one of two modes. An admin chooses it in the form « Brancher un dépôt », and changes it later on the page of the settings of the repository, which sends PATCH /i18n/repositories/<id> with {"publishMode": "…"}.

ModeA publication isFor
commitone commit on the base brancha base branch that accepts a push
pull_requestone commit on i18n/publish, carried by one pull requesta base branch whose rules require a pull request

In the mode pull_request:

  • The first publication makes i18n/publish from the base branch, and opens the pull request i18n/publish → base branch.
  • A later publication adds a commit to the same branch, and writes the title and the body of the pull request again. They list every key and language that the pull request carries. Each commit names the reviewer who published.
  • When the base branch moved, the publication merges it into i18n/publish first. If the two changed the same lines, the publication stops with a 409 and forces nothing. Resolve the conflict on GitHub, or close the pull request.
  • After a merge or a close, the next publication starts i18n/publish again from the base branch, and opens a new pull request. A closed pull request drops what it carried: its keys are proposals again.
  • A key in the open pull request leaves the list of proposals. It shows as published in the catalogue after the merge, because the catalogue reads the base branch.
  • A key created from the editor goes into the pull request too.
  • GET /i18n/publication?repository=<id> answers the last pull request, open or not, and whether GitHub can merge it. The header of the editor shows it.

The App needs the permission « Pull requests: write » for this mode: The GitHub App tells how to add it to an App that exists.

Change a text of the source ​

The source language is in the list of languages, first, with « (source) » after its name. An account with writes_source changes its texts as a translator changes a target: it proposes, and the change waits on i18n/<source>. The same account then publishes it. An admin without writes_source reads the source and writes nothing.

  • A proposal of the source changes a key that the source holds. To add a key, use « Nouvelle clé », which writes it at once.
  • A source text cannot be empty.
  • A publication refuses a key whose text changed on the base branch since the proposal, as for a target: the reason is source-changed.
  • The publication follows the mode of the repository: a commit, or the pull request of the publications.

A translation to read again ​

When a source text changes on the base branch, every translation of that key shows under « À revoir » in its language, with the old source beside the new one. The editor computes this from the history of the base branch. No table holds it:

  • a change of the source, after the last change of the translation, puts the key to read again;
  • a new translation of the key, published, clears it;
  • a reviewer can keep the translation as it is: « Garder … telles quelles » sends POST /i18n/reviews. The commit changes no file and carries one trailer per key, Reviewed: <language> <file>:<key>. When it reaches the base branch, the key is up to date.

Only the history since the repository was connected counts, and at most the last 300 commits of the base branch. A source that changed before, or further back, puts nothing to read again. A translation that is missing stays missing.

The settings of a repository ​

An admin opens « Réglages » in the header of the editor, at /repositories/<id>/settings. The page changes the branch, the folder of the locales, the manifest, the languages, the mode of publication and the formats of the messages. It sends PATCH /i18n/repositories/<id> with the fields that changed. An absent field keeps its value.

  • A new branch, folder or manifest is read on GitHub before it is written. If GitHub does not hold it, the API answers 400 and changes nothing.
  • A refusal names its field in errors, as the other validation errors of the API do: { "message": "…", "errors": [{ "field": "branch", "message": "…" }] }.
  • If GitHub fails to answer the check for another reason, the API answers 502 with the reason, and changes nothing.
  • null, or an empty string, gives a format its default back. A null manifest gives back <localesPath>/manifest.json.

The formats of the messages ​

A format is a text with variables between braces. {{ writes {, and }} writes }. A variable that the format of its kind does not list is refused.

FormatField of the APIVariablesDefault
The commit of a proposalproposalCommitTemplatelanguage, count, count_keys, author, repository, branchi18n({language}): {count_keys} proposed by {author}
The commit of a publicationpublicationCommitTemplatelanguage, count, count_keys, action, author, repository, branchi18n({language}): {count_keys} {action} by {author}
The title of the pull requestpullRequestTitleTemplatecount, count_keys, languages, repository, branchi18n: {count_keys} published ({languages})
The body of the pull requestpullRequestBodyTemplatecount, count_keys, languages, keys_list, repository, brancha sentence, a blank line, then {keys_list}
VariableWhat it writes
languagethe language of the commit: en
languagesthe languages of the pull request, in alphabetical order: de, en
counthow many keys: 3
count_keysthe same, with the word: 1 key, 3 keys
actionpublished; created for a key that « Nouvelle clé » added to the source; reviewed for translations kept as they are after their source changed
authorthe name of the account that proposed or published
repositoryowner/name
branchthe base branch
keys_listthe keys of the pull request, one Markdown section for each language

Rules:

  • A commit format and the title are one line. The body takes several.
  • A commit subject longer than 100 characters, or a title longer than 256, is cut and ends with ….
  • The editor adds the lines it reads back under the format, whatever the format says: the Read-at: trailer of a proposal, and one line for each key of a publication (- en common:hello). The title and the body of the pull request are built from these lines, so a format can change the subject freely.
  • A format that a hand wrote in the database and that the API would refuse is not used: the message takes the default format.

The manifest ​

The manifest says, for each file and each language, if the application uses the translation. It is optional. Without a manifest, each file is available in each language.

json
{
  "source": "fr",
  "files": {
    "common": { "en": "available", "de": "available" },
    "checkout": { "en": "partial" },
    "legal": { "en": "disabled" }
  }
}
StateWhat the application doesWhat the editor does
availableIt loads the file.It lists the keys, and a translator writes them.
partialIt does not load the file yet. Users read the source.It lists the keys, as for available.
disabledThe file has no version in this language.It lists no key, and it refuses a write to the file.
  • A file or a language that the manifest does not name is available.
  • The source language is always available. The manifest cannot change it.
  • If source is there, it must be the first language of the repository row. If the two are different, the editor stops the read with an error.
  • If the manifest is not valid JSON or does not match this format, the editor stops the read. It does not read the manifest as empty, because that would enable each file that the manifest disables.

The editor reads the manifest on the base branch. A proposal branch cannot change the state of a file.

A key left empty on purpose ​

A translation can be "" because the language wants no text there, and not because nobody translated it. The manifest says so, per language, with a key named <file>:<key>:

json
{
  "empty": {
    "en": { "woodlock:hints.3_image": { "source": "615c50a46c72" } }
  }
}

source is the first 12 hexadecimal digits of the SHA-256 of the source text when the key was marked, UTF-8 encoded: printf '%s' '/img/box2-b-indice3.png' | shasum -a 256 | cut -c1-12.

The key in the languageWhat the editor does
"", and the hash matchesIt is translated and counts as published. It shows « Vide voulu », counted apart.
"", and the hash differsThe source changed since the mark: the key is « À revoir ».
any other value, or no keyThe mark is ignored. The key is translated or missing, as without it.
  • « Marquer vide voulu » proposes "" and the mark in one batch, on i18n/<language>. A reviewer publishes it as a translation, in the mode of the repository. The mark then reaches the manifest of the base branch.
  • « Retirer « vide voulu » » proposes the key with no mark. A text proposed for a marked key removes the mark too, in the same batch.
  • « Garder » on a marked key « À revoir » writes the hash of the new source.
  • The source language has no mark: a source text says something.
  • The rest of the manifest keeps its bytes. A mark that is removed leaves the manifest as it was before the mark.

An application that reads these files must read empty too. i18next falls back on the source for "" when returnEmptyString is false, so a player would read the source where the language wants nothing.

Keep the manifest out of the directories of the languages. In one of them, the editor reads it as a locale file named manifest.

The author of a commit ​

A proposal and a publication are commits. Their author is the name of the account and an address that does not reach the person: <account ID>@<COMMIT_EMAIL_DOMAIN>.

VariableDefault
COMMIT_EMAIL_DOMAINusers.noreply.invalid

The default domain is reserved by RFC 2606: no mailbox and no GitHub account has it.

Do not set it to users.noreply.github.com. An account ID of the editor is not a GitHub login. GitHub then gives the commit to the GitHub account with that name, if one exists.