Osy#betaRun it on Osyrin: osyrin.com

Reference / I18n

osy translate — the request for any model, and its answer read back

osy translate --to sv,de · osy translate --apply answer.json · osy translate

There is no platform model. `osy translate --to sv,de` brings `locales/sv.osy` and `locales/de.osy` in step with the source and writes `locales/request.json` — every text still to translate, with its context — and `locales/prompt.md`, the instructions to hand any model (or translator) with it. `osy translate --apply answer.json` checks every answer with the compile's own rules and writes the good ones into the files.

stablei18nclitranslationllm

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 left

Signature#

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#

Related

A language's words — locales/<language>.osy

A project's translations are checked-in source: one `locales/<language>.osy` per language, a `translations sv { }`…

Which words are translated — the compiler finds every text a reader sees

You write your app's words where they belong, in ordinary strings. The compiler finds every one a reader can see — by…

A count's plural forms — one text per CLDR category

A message with a whole-number hole (`$"{n} items"`) may take one text per plural category of its language — CLDR's…