The translations amaGama imports are listed in a sources file,
amagama/updater/sources.toml unless UPDATER_SOURCES names
another. Each [[block]] is a set of files fetched and imported together,
such as one GNOME release:
[[block]]
name = "gnome-51"
title = "GNOME"
version = "51"
source = "damned-lies"
release = "gnome-51"
licence = "GPL-2.0-or-later"
A sources file can include others, named relative to it, whose blocks follow its own; block names must be unique across them all:
include = ["weblate-fedora.toml"]
The included Weblate files are written by weblate_sources; transifex_sources writes blocks the same way from repositories’ Transifex configurations, and discover_sources from repositories it finds by the localisation configuration they commit.
A [defaults] table gives settings for every block in its file (not in the
files it includes); a block’s own settings take precedence. name and
versions can’t be defaults:
[defaults]
source = "git"
min_age_days = 60
nameThe block’s fixed reference: it names the block’s directory in the cache and its imports, so never rename or reuse one. Blocks are read in the order they appear in the file.
titleThe name shown to people, such as GNOME or Legacy OSS. Titles are
translated, so keep release numbers in version.
versionOptional release, such as "51"; shown after the title (GNOME 51).
versionsInstead of version, a list of releases: the block stands for one block
per release, with {version} replaced in every setting, so adding a
release is one more item. The name must contain {version}. An item can
be a table giving values that differ by release:
[[block]]
name = "gnome-{version}"
title = "GNOME"
source = "damned-lies"
release = "gnome-{version}"
versions = ["50", "51"]
[[block]]
name = "libreoffice-{version}"
title = "LibreOffice"
source = "git"
url = "https://git.libreoffice.org/translations"
files = ["source/{lang}/**/*.po"]
versions = [
{ version = "26.2", ref = "libreoffice-26-2" },
{ version = "26.8", ref = "libreoffice-26-8" },
]
sourceHow to fetch the files; the other keys depend on it (below).
licenceOptional SPDX licence expression, checked as
for build_tmdb.
filesOptional filemasks choosing which of the block’s files to import, relative
to its directory. * matches within a directory, ** any number of
directories, and {lang} the target language (a code such as af,
pt_BR, sr@latin or b+sr+Latn), which then overrides the
language the source gives:
files = ["addons/*/i18n/{lang}.po"]
files = ["translations/*_{lang}.ts"]
files = ["**/locale/{lang}/LC_MESSAGES/django.po"]
Without it every file is imported. A block whose masks match no file fails, rather than loading nothing.
excludeOptional masks, without {lang}, of files to leave out, such as
["**/qqq.json"].
keepOptional masks, without {lang}, of files the import needs beside the
translations but that aren’t imported themselves, such as the
.grdp parts a Chromium .grd includes.
languagesOptional table of a source’s language codes to amaGama’s, applied before
the normalising below; map a code to "" to leave it out:
languages = { no = "nb", xxx = "" }
formatOptional reader for the block’s files, when their extension doesn’t say:
po, mo, ts, tmx, tbx, xliff, csv or
nextcloud-json (Nextcloud’s l10n/<lang>.json, keyed by the English
text). Without it the extension picks the reader.
Monolingual formats hold one language per file, keyed by an id, and need a
template: android (strings.xml), php, properties
(Java’s ISO-8859-1; properties-utf8 and properties-utf16 for files in
those encodings), joomla-ini, json (key to message), fluent and
dtd; and xliff-monolingual, XLIFF whose <source> is the
message’s id and whose template’s <target> is the English (Symfony’s).
For android, {lang} must be a locale qualifier alone (af,
pt-rBR, b+sr+Latn): directories that add another qualifier, such
as values-af-watch or values-fr-feminine, and the
pseudo-locales en-rXA, ar-rXB and en-rXC are left out.
xtb is Chromium’s: each .xtb translation is keyed by a
fingerprint of the English message, which is in a .grd (and the
.grdp parts it includes, which keep must keep). Each
.grd lists its .xtb files and their languages, so without
a template that list pairs them:
format = "xtb"
files = ["**/*.xtb"]
keep = ["**/*.grdp"]
A template pairs by path instead, with files giving the language:
format = "xtb"
files = ["*/strings/translations/*_{lang}.xtb"]
template = "*/strings/*.grd"
keep = ["**/*.grdp"]
templateFor a monolingual format, the English file each translation pairs with by
id: a path with the same wildcards as files, each taking what it
matched in the translation’s path:
files = ["res/values-{lang}/strings.xml"]
template = "res/values/strings.xml"
files = ["{lang}/*.php"]
template = "en/*.php"
The template is kept but not imported itself. Messages with no English, and plurals, are left out; a translation whose template is missing is listed as unreadable.
columns and header_rowFor format = "csv": what each column holds, from "source",
"target" and "location", with "" for a column to ignore; and
whether the first row is a header to skip, or the header row’s first cell,
so that the rows before it (a notice, say) are skipped too. Files are then
read as plain comma-separated values:
format = "csv"
columns = ["", "source", "target"]
header_row = true
xliff_statesFor XLIFF files: the target states that count as translated, from XLIFF
1.2’s new, needs-translation, needs-review-translation,
translated, final, signed-off and the like, x- custom
states, and "none" for a target without a state. A unit with
approved="yes" always counts. Without it, translated, final and
signed-off count:
xliff_states = ["none", "translated", "final", "signed-off"]
segmentOptional true to also import each multi-sentence message as sentence
pairs, so a single sentence of a long message can match: worthwhile for
documentation. A message is split only when its English and translation
have the same number of sentences and no inline tag crosses a sentence
(an element around the whole message, such as LibreOffice’s <ahelp>,
is set aside first). The whole message is imported too.
weightOptional number from 0 to 10, default 1: how suggestions from the block
rank in lookups, by quality times weight, unless a client gives its own
weight. A translation also found in a block
of higher weight takes that weight. Set it in a file’s [defaults] to
weight all of an included file’s blocks.
tagsOptional list grouping blocks apart from the file they’re in, so a server
can build a set of them. A file’s [defaults]
tags add to each block’s own. A block has at most one tier, how it was
chosen:
core: by hand (sources.toml)
platform: from a translation platform’s listing (weblate_sources)
discovered: by a search of repositories (discover_sources,
transifex_sources)
and a content tag, ui, docs or terminology; without one it is
docs if segment is set, else ui. trial marks blocks being
tried out:
tags = ["terminology", "trial"]
disabledOptional reason to leave the block out: it isn’t fetched, imported or listed, but keeps its name. The commands that write blocks keep it when they regenerate the file:
disabled = "duplicates Damned Lies module meld"
min_age_days and max_age_daysBoth count days since the block was last fetched; min_age_days is a
floor and max_age_days a ceiling.
min_age_days (default 0): until the block’s copy is this old, it isn’t
checked at all, so its server gets no request; this keeps many small
sources from being fetched more often than they’re worth. Blocks first
fetched together would all come due on one day, so a block’s first
refetch comes after a fraction of min_age_days taken from a hash of its
name. 0 checks the block on every run.
max_age_days (default 30): a copy this old is fetched again, though
nothing says it changed. For a source with one download per language
(damned-lies, translationproject, ubuntu-langpacks, http,
pontoon, wordpress, drupal, moodle, joomla, svn) it
applies to each language, and the downloads are spread out: a run takes its share of
the languages for the days since the last fetch (rounded up; a day’s share
the first time), languages never fetched first, then those fetched longest
ago. With 57 languages a daily run fetches 2, so the first fetch takes
max_age_days too, and the block isn’t due again until
max_age_days / 57 days later. Such a source is refreshed by age, not by
its fingerprint, so its server never gets every language at once. 0 turns that off: the whole block is fetched whenever its
fingerprint changes, or on every run without one.
An old release that hardly changes can have a longer max_age_days than
the current ones, as an item of versions.
Within min_age_days a block isn’t touched, even past max_age_days.
exclude_idsOptional patterns of message ids to leave out, such as ["@metadata*"]
(MediaWiki), ["*.description"] (Signal) or a TMX tuid prefix
(["foundation-website-content:*"]). JSON ids are matched without their
leading ..
language_in_idsOptional true for a block with a template whose message ids name
their file’s language, such as CLDR’s main.de.localeDisplayNames....
or Rails’ de.errors....: the id segment where every one of the
template’s ids has the same English code is left out of each id before a
translation is matched with its English.
Templates (.pot) are never imported.
Every language code is turned into one form, language with optional
_REGION and @variant, whichever way the source writes it: pt-BR,
Android’s pt-rBR and pt_br become pt_BR; sr-Latn,
b+sr+Latn and sr@Latn become sr@latin; zh-Hans and zh-Hant
become zh_CN and zh_TW; ca-valencia becomes ca@valencia;
replaced codes such as iw become he; a country’s code used for its
language (cn, ua) becomes the language’s (zh_CN, uk); and a
three-letter ISO 639 code with a two-letter one becomes it (arg and
deu become an and de). Codes that aren’t languages are left out
and listed by fetch_sources and update.
fetch_sources downloads the blocks into
UPDATER_CACHE_DIR, and update rebuilds the
database from them.
damned-liesGNOME’s l10n.gnome.org: one tarball per language
of a release, for every language with translations in it. release (required) is a
release name such as gnome-51; tarballs (default ["ui"]) can add
"doc", or split the UI into "ui-libs" and "ui-apps".
drupallocalize.drupal.org: a .po per
language of the latest stable release of each of projects (required),
such as ["drupal", "token"]. With core, a Drupal major version
such as "11", each release is the newest for that core: core’s own
in that series, and a module’s whose core compatibility includes it.
The languages are the translation
server’s; a language with no file for a project is skipped. base_url
and releases_url can name mirrors of the files and the release
history; pause (default 1) is the seconds between downloads.
translationprojectThe Translation Project’s monthly backup of every package, one tarball per language.
ubuntu-langpacksUbuntu’s language packs of a series (required, such as "noble"):
language-pack-<lang>-base, the translations Launchpad exports for the
release, one .deb per language. The archive’s package index of the
series, then of its -updates pocket, gives each pack’s newest
.deb, checked against the index’s SHA512; only its
usr/share/locale-langpack/ is kept, under the pack’s language, so
files picks the domains and gives the locale:
files = ["*/usr/share/locale-langpack/{lang}/LC_MESSAGES/apport.mo"]
A pack mixes upstream projects’ translations with Launchpad’s edits of
them, unmarked, so the shipped block takes only Ubuntu’s own packages’
domains. base_url (default https://archive.ubuntu.com/ubuntu) names
a mirror; pause (default 1) is the seconds between downloads.
gitA shallow clone of url at ref (default: the default branch). A
repository doesn’t say which files are translations, so files is
required and every mask must contain {lang}. Only the files the block
reads are downloaded and checked out: files with {lang} as *,
template and keep, or sparse, a list of gitignore-style
patterns, instead:
sparse = ["**/res/values*/strings.xml"]
The clone is kept, one per url, in .repos in
UPDATER_CACHE_DIR, and shared by the blocks that fetch from it:
a release’s or a component’s block downloads only what changed since any
of them last fetched.
mirror is a copy of url to fetch from instead, to take load off a
community project’s servers, such as GNOME’s GitHub copy of a
gitlab.gnome.org repository. url stays the block’s repository. Each
check compares the two refs’ commits: the mirror is used while it has
url’s commit, or is behind but moved less than mirror_lag_days
(default 14, a block setting that [defaults] can give) ago. After
that, or when it can’t be reached, url is, and fetch_sources and
update say so; a mirror that hasn’t moved for twice that is asked
about:
url = "https://gitlab.gnome.org/GNOME/meld"
mirror = "https://github.com/GNOME/meld"
httpOne file per language under a directory listing, such as Debian’s
compendia: base_url is the listing, whose subdirectories are the
languages, and path the file under it, with {lang}. Where that file
is missing (403 or 404), fallback, a filemask with {lang} and
*, picks the last matching file in the listing of its directory; a
language with neither is skipped. pause (default 1) is the seconds
between downloads.
svnA Subversion tree with one directory per language, such as KDE’s
l10n-kf6. url (required) is the tree; dirs lists what to export
from each language’s directory (default: all of it), such as
["messages"]; pause (default 1) is the seconds between exports, to
go easy on a shared server. Needs the svn command.
joomlaJoomla’s accredited language packs for a major
release (required), such as "5", with the English they translate
from: the files of joomla-cms at ref (required), such as
"5.4-dev", that template (required) matches. Each pack’s files are
placed where Joomla installs them, beside the English:
files = ["**/language/{lang}/*.ini"]
format = "joomla-ini"
template = "**/language/en-GB/*.ini"
A pack whose SHA-256 doesn’t match its details is skipped. base_url
and repository can name mirrors; pause (default 1) is the seconds
between downloads.
moodleMoodle’s language packs for release (required),
such as "5.3": a zip per language, English included, from the index
Moodle’s own language installer reads. Each pack holds
lang/component.php, so the English pack is the template:
files = ["{lang}/*.php"]
format = "php"
template = "en/*.php"
Downloads are checked against the index’s MD5s. base_url can name a
mirror; pause (default 1) is the seconds between downloads.
pontoonMozilla’s Pontoon: each locale’s whole
translation memory as one TMX file, holding only approved translations,
for every locale with at least min_approved (default 1) approved
strings. base_url can name another Pontoon; pause (default 1) is
the seconds between downloads.
vscodeVS Code’s language packs from
vscode-loc (repository,
ref default main), paired with the English of the VS Code release
the packs target, from Microsoft’s CDN (update_url, cdn_url), as
PO files. Strings whose English the web build doesn’t have (node-only
modules) are left out.
wordpressWordPress.org’s language packs: one
zip per language, each holding .po files (select them with
files = ["**/*.po"]). kind (required) is core, plugins or
themes; slugs lists the plugins or themes. For core,
release (such as "7.1") takes the pack each language has for that
release, which may be an older one; without it, each language’s newest
pack. WordPress’s formal, informal and ao90 variants are kept with
their language. base_url can name a mirror of the API; pause
(default 1) is the seconds between downloads.
glotpressA GlotPress project’s translations, for
WordPress.org’s projects without language packs: one .po export
of the current translations per locale and translation set, of the locales
with anything translated.
project (required) is the project’s path, such as apps/android/dev.
A formal or other set goes with its language. base_url (default
https://translate.wordpress.org) names the GlotPress site; pause
(default 5) is the seconds between exports, and a 429 Too Many Requests is
retried after retry_wait (default 60) seconds, then twice that.
snapshotData kept in the database by snapshot_db,
named by snapshot; nothing is downloaded and it never changes.
Both tarball sources take base_url to use a mirror, and pause
(default 1), the seconds between downloads.
Each fetch records a fingerprint of the block: for damned-lies the
release’s translation statistics (one request; a reworded translation alone
doesn’t change them), for wordpress every pack’s version and update time, for glotpress the
project page’s translated count per locale (one request; like Damned Lies,
a rewording alone doesn’t change it),
for moodle the pack index with its MD5s, for ubuntu-langpacks the
packs’ SHA512s in the package indexes, for joomla the packs’
versions and the English ref’s commit, for drupal the projects’ releases
(the files are rebuilt daily), for pontoon none (its files are generated
on request), for translationproject the ETag or Last-Modified header of
every tarball, for git and vscode the commit of the ref (from Gitiles’
JSON on googlesource.com, where ls-remote is too costly).
fetch_sources --check recomputes it without downloading and lists the
blocks whose fingerprint changed.
A server that sends neither header gives no fingerprint: such a block counts
as changed once its last fetch is max_age_days old. A block with one
download per language refreshes its languages in turn instead, and
fetch_sources --check lists it when some are due. A block younger than
min_age_days isn’t checked at all. Every fetch also records a hash of the files it kept, and when
all the blocks update fetched are the same as last
time, it doesn’t rebuild.
Block titles are translated in amagama/po/sources/: one
<lang>.po per language, from the template sources.pot. Beside
the titles, translators word “{title} {version}” for their language and give
its text direction. After adding or changing a title, here or with
weblate_sources, transifex_sources or discover_sources, regenerate the template;
the tests fail while it’s out of date:
$ amagama-manage sources_pot