Summary#
A round trip in three commands: ask, translate anywhere, apply.
osy translate --to sv # locales/sv.osy in step; locales/request.json + locales/prompt.md written
# → hand both to a model, save its reply as answer.json
osy translate --apply answer.json
osy translate # later: re-sync every language after the source changed, and say what is leftSignature#
osy translate [PATH] [--to <languages>] [--apply <answer.json>] [--json]Description#
--to sv,de adds each language the project does not speak yet and syncs every locale file it has
(A language's words — locales/<language>.osy): new texts arrive as null, changed ones as stale with their old translation beside them,
removed ones are dropped. A text whose only change is the NAME of a placeholder ({ToString} now {n}) keeps its
translation, translated, with the placeholder renamed in it — and a compile serves a translation keyed under the old
name even before the file is synced, so upgrading the platform never turns a page back into English (when two texts
fit one old key, as From {ToString} does for two labels, only the sync can carry it). A placeholder is named after the
value the text shows — n.ToString() + " fields" is {n} fields — never after the method that formats it. Run in a KIT's directory it translates the kit's own words; in an
APP's, the app's words plus its kits' words that the kits do not translate into that language (a kit's translation keyed
under an old placeholder name counts).
One file per owner. In an app, the app's own words are kept in locales/<lang>.osy and each kit's in
locales/<lang>.<Package>.osy — locales/sv.Osysharp.WorkQueues.osy — so no file grows with every kit an app adds (one
file per language passed the compiler's per-file ceiling in an app using eighteen kits). A language already kept in
one file is split the first time osy translate runs, its translations kept; a file whose owner no longer has words
is removed. Every locales/*.osy is read as one language's words together.
The request (locales/request.json, format osy-translation-request/1) lists each text owed a translation with
everything a translator needs — which is the whole point: a word translated without knowing where it stands is how
.rc files went wrong.
{
"id": "9f3c0a51e2b47d10", "language": "sv",
"source": "{count} items in your bag", "role": "text", "what": "text on the page",
"placeholders": [{ "name": "count", "filledWith": "count", "type": "int" }],
"plural": "count", "pluralForms": ["one", "other"],
"where": { "component": "BagPage", "route": "/bag", "file": "model/pages/bag.osy", "line": 12 },
"beside": ["Your bag", "Check out"],
"maxLength": 30,
"previously": { "source": "…", "translation": "…" }
}The prompt (locales/prompt.md) says what those fields mean, the rules (keep every {placeholder}, one text per
plural form, plain text, the language's own conventions, the source's tone), and the exact shape to answer in:
{ "format": "osy-translation-answer/1", "translations": [
{ "id": "…", "language": "sv", "text": "Till kassan" },
{ "id": "…", "language": "sv", "plural": { "one": "{count} vara", "other": "{count} varor" } } ] }--apply checks each answer against its source before writing it — the placeholders, the plural forms the
language needs, no markup, not empty — and reports every one it refuses (the exit code is 2 when any was refused).
Nothing half-checked reaches a file, and the compile checks the file again anyway.
Both working files are regenerated on every run, and git-ignored.
See also#
- A language's words — locales/<language>.osy — the file it writes
- Which words are translated — the compiler finds every text a reader sees — where the texts come from
- A count's plural forms — one text per CLDR category — plural categories per language