altalandi
v0.9.1
Published
Native-sounding Icelandic from your terminal or AI agent: one-time login, a Claude Code skill, and meaning verification.
Maintainers
Readme
altalandi
Turn English or stiff/AI-translated Icelandic into Icelandic that reads as if a
native wrote it—from your terminal or application. This package provides the
altalandi CLI and a typed, zero-dependency JavaScript client for the hosted
Altalandi API (Node ≥ 18).
npx altalandi login # once: opens altalandi.is, you sign in or sign up and click "Tengja"
npx altalandi "Make sure to reach out to us today."
# → Hafðu samband við okkur í dag.login stores the key in your config directory (%APPDATA%\altalandi on
Windows, ~/.config/altalandi elsewhere) and installs the Claude Code skill, so
afterwards you can just tell Claude "write this in proper Icelandic with
Altalandi". altalandi whoami shows the account and credit; altalandi logout
forgets the key. On servers and in CI, set ALTALANDI_API_KEY instead; it takes
precedence over the stored login. For agents, --file draft.txt avoids shell
quoting entirely.
stdout is the refined text (pipe it anywhere); stderr carries the 0–100
nativeness score, the meaning verdict, every API warning, and the diff.
Install
npm i -g altalandi # then use `altalandi ...`
# or run without installing: npx altalandi "..."For an application dependency, install locally and import the package root:
npm i altalandiUpdating
altalandi update # check, then ask before updating
altalandi update --check # only report; change nothing
altalandi --versionaltalandi update checks npm for a newer version and asks before installing
it. It works out how you installed the CLI (global, project dependency or npx)
and runs the matching command. If you use Claude Code, it then offers to install
or refresh the /altalandi skill that ships with each version. A skill you have
edited is backed up before it is replaced.
Nothing changes without your answer. Without a terminal to ask in (agents, CI,
pipes) it only reports what is available; pass --yes when a script already has
your consent.
Exit codes: 0 up to date or updated · 10 an update is available but was not
applied · 1 the check or the update failed.
JavaScript / TypeScript client
refineVerified requests the meaning gate and throws unless the API returns
strict meaningPreserved: true. It reads ALTALANDI_API_KEY automatically:
import { refineVerified } from "altalandi";
const result = await refineVerified("Make sure to reach out to us today.", {
domain: "marketing",
});
console.log(result.output);Use refine when you need to inspect an unverified result yourself, then apply
the same strict assertion before shipping it:
import { assertMeaningVerified, refine } from "altalandi";
const result = await refine("Some text", { register: "warm" });
assertMeaningVerified(result); // throws on both false and null
console.log(result.output);Both functions accept apiKey (for a secret-manager value), baseUrl, and
timeoutMs alongside the request options. The defaults are
ALTALANDI_API_KEY, https://altalandi.is/api, and 300,000 ms. API, network,
timeout, and verification failures throw AltalandiError subclasses with a
stable code; errors never include the request text or API key. TypeScript
declarations ship with the package. The CLI uses the same 300-second request
deadline, including response-body download.
Usage
altalandi "Some English or Icelandic text."
echo "some text" | altalandi --domain marketing
altalandi --json "..." # full JSON response
altalandi --fast "..." # skip the meaning + nativeness gates
altalandi --diff --file draft.txt # show before, after and every change with its reason
altalandi --review --file in.txt --out out.txt # for agents: one block to show as it is
altalandi --report --file draft.txt # also write a Markdown report to ./altalandi-reports/
altalandi --review --job j_20261013a1b2c3 --job-title "Handbók" --part "Hluti 1 af 3" --file part1.txt
# one part of a longer text: same --job on every part| flag | default | meaning |
|------|---------|---------|
| --domain | general | general | marketing | data-analysis | childrens-fiction |
| --register | neutral | formal | neutral | warm |
| --json | off | print the full API response |
| --diff | off | print the text before and after, then each change with its reason, on stderr (coloured in a terminal; NO_COLOR turns colour off) |
| --review | off | print one Markdown block on stdout, made to be shown as it is: the verdict, the text before and after, each change with its reason, what the call cost and the balance and free characters left. Exits on the outcome: 0 rewritten and verified or no change needed, 3 refused or unverified, 4 out of credit |
| --no-store | off | send contribute: false: this text is never kept. Without it, free use (calls covered by the free daily characters) is kept with personal data scrubbed, to improve the service; paid use is kept only with consent (privacy) |
| --out <file> | | write the refined text to <file> (UTF-8); with --review the block names it when the text is shortened |
| --report | off | also write ./altalandi-reports/altalandi-<time>-<id>.md: input, output, a table of every change (before, after, reason, layer), warnings and the verdict |
| --report-dir <dir> | | write the report to <dir> instead (implies --report) |
| --job <id> | | make this call a part of a job (verk): every call with the same id is one job on the site, one page for the whole text. The id is yours to choose: j_ and 8–64 of A-Z a-z 0-9 _ -, e.g. j_ + the date + a few random characters. With --review, the block links the job (Verkið) and this part (Hlutinn) |
| --job-title <text> | | the job's name (up to 120 characters); the first call that sends one names it |
| --part <text> | | this call's part, e.g. Hluti 2 af 5 (up to 300 characters); needs --job |
| --fast | off | preserveMeaning:false, scoreNativeness:false, onlyImprove:false, polishPass:false, scoring:"none" — normally one model call (structured input can still add a structure-preserving retry) |
Folders, i18n catalogs and websites
One command for a whole folder, one web page, or a site. The input is never written to.
altalandi ./docs # every .md .txt .html .json under ./docs → ./docs-is
altalandi ./locales --out ./locales-is # i18n catalogs: values only, placeholders kept
altalandi ./docs --dry-run # every passage and the estimate; sends nothing
altalandi https://example.com/pricing --out pricing-is.md # one page → Markdown
altalandi https://example.com/docs/ --site --max-pages 50 # a site → ./example.com-is/Short English strings (buttons, menus, labels; up to 80 characters) are first
looked up in the Hnappasafn: the Icelandic that
Icelandic websites and open-source software use, and then in the Orðakista: the
Icelandic names of countries, languages, currencies, units, months, weekdays and
cities, and Icelandic place names. A hit is written as found, with no model run, for
1 kr (the free daily characters pay first); --no-library skips this.
Every string is classified on its own (button or label, heading, sentence,
paragraph) from its length, punctuation and JSON key path, so one catalog can mix
buttons with technical prose. In an Icelandic file (--from is, a name like
site_is.json or is-IS/, or mostly Icelandic strings), buttons and headings are
checked against the Hnappasafn. The form Icelandic software uses („Tengjast“) is
kept exactly as written and never sent to a model. A minority form („Hvernig það
virkar“) becomes the established one („Svona virkar þetta“). An unknown label is
refined as usual.
Each passage (a paragraph, list item, table cell, heading or string) is refined
and checked on its own. Short passages (up to 1,500 characters) are sent
together through /v1/refine/batch, up to 40 a request and 6,000 characters;
longer ones go one call each to /v1/refine. Everything outside the passages is
written back byte for byte:
| format | refined | never touched |
|--------|---------|---------------|
| Markdown | paragraphs, list items, table cells, heading text, front-matter title/description-style values | code blocks, inline code, URLs, link targets, HTML tags, front-matter keys, heading markup |
| HTML | text in elements (inline tags travel as numbered markers and come back exactly) | tags, attributes, entities, script, style, pre, code, translate="no" |
| JSON | string values of i18n catalogs | keys, @/$ metadata, {name} {{name}} %s %(name)s, ICU plural/select structure, HTML in strings |
| Text | paragraphs (re-wrapped to their width) and lines | |
Passages with nothing to refine (code, numbers, URLs or placeholders alone) are
skipped, and a passage that appears twice is sent once. node_modules, .git,
dist, build, altalandi-reports, hidden files and folders and symlinks are
skipped. JSON config (package.json, tsconfig.json, …) and JSON with numbers
or booleans is copied unchanged; other file types are not copied.
Pages are fetched one at a time with a User-Agent naming altalandi, only where
robots.txt allows it for altalandi and for * (Crawl-delay is honoured).
The readable part (<main> or <article>, else <body> without navigation,
header, footer, sidebars, forms, scripts and hidden elements) becomes Markdown.
--site reads /sitemap.xml, or follows links when there is none, staying on
the same origin and under the URL's folder.
Cost and safety. Before refinement, the CLI reads the live tariff and, when
signed in, the account's current allowance and discounts. --dry-run --json
returns costEstimate without submitting text for refinement or spending credit:
estimatedCreditChargeIsk: the estimated deduction from existing credit, assuming every passage needs rewriting, after current free use and discounts.listValueIsk: the full tariff value before those benefits, a conservative planning figure rather than the expected bill.additionalCreditNeededIsk: any shortfall after the existing wallet balance.
Unknown account benefits produce a null credit estimate, never an invented zero. An explicitly unlimited free grant produces a zero credit estimate. A null legacy free balance alone does not establish either. Whole requests use free allowance or credit; a request that does not fit is paid in full. Shared usage, expiring benefits and tariff changes mean an estimate is not an enforced spending cap. The final receipt reports the actual charge; unchanged-text checks can lower it.
Billing is by character, including spaces and punctuation. Current minimums are
read from /v1/pricing: individual Standard requests start at 250 characters for
Icelandic and 800 for English; batch items start at 200 and 800 respectively,
with a 65-character unchanged-text check. Repeated passages are sent once.
An older engine without batching gets one call per passage (the run says so).
In a terminal the CLI asks before sending; without one (CI, agents) it sends
nothing unless you pass --yes.
For an application field with a length limit, send it as one complete text:
altalandi --review --file answer.txt --out answer-is.txt --min-output-chars 400 --max-output-chars 1000The API/SDK equivalents are minOutputChars and maxOutputChars. Limits count
the exact output as a browser does (UTF-16, including spaces and line breaks).
The writer receives the bounds and can retry once if it misses them. The final
text is checked after corrections and verification. An unsatisfied constraint
fails with output_length_constraint; the server releases the held credit.
It never truncates text or invents facts to fill a minimum. Bounded output files
are saved without an extra newline. Do not split or batch a bounded field, and
do not confuse these flags with the folder input budget --max-chars.
| flag | default | meaning |
|------|---------|---------|
| --out <path> | <folder>-is | output folder, refused if it is the input, inside it or around it; for one page, the .md file |
| --dry-run | off | list every passage and the estimate; send nothing, write nothing |
| --yes, -y | off | send without asking |
| --max-chars <n> | | files are sent in order until the next would go over n characters; the rest are neither sent nor written |
| --concurrency <n> | 2 | requests at once, 1–4 |
| --no-library | off | don't look short English UI strings up in the Hnappasafn first |
| --site / --max-pages <n> | off / 20 | read a site from the URL, up to n pages |
| --no-store | off | keep none of the passages (free use is otherwise kept, scrubbed of personal data) |
| --json | off | a machine-readable summary of every file and passage on stdout |
| --job <id> / --job-title <text> | a new job | add the run to an existing job, or name it |
A 429 waits as long as Retry-After asks (up to 5 minutes) and retries. A
402 stops the run cleanly: finished files are written, the rest are listed and
not written; run the same command again after topping up.
One job. A run is one job (verk) on your account: every request sends the
same job id (j_ + the date + 10 random characters, made per run), titled by
the folder's name or the site's host, and each file (its relative path) or page
(its URL) is a part. A batch that holds several small files is named by its
first file; a file that fits in one batch is not split across two. The summary
and the report show one Verkið: <link> to the job's page, not one link per
call. An engine that does not know jobs simply leaves the line out.
Results. Only verified changes are written. A passage keeps its original
text when its meaning check did not pass (meaningCheck other than passed, or
meaningPreserved not true), or when the refined text lost or changed code, a
URL, a placeholder or a tag. One report next to the output
(altalandi-reports/altalandi-<time>-<input>.md) lists every file and every
changed passage with its meaning verdict, and the kept passages under their own
heading with the unused suggestion and the reason.
| exit | meaning |
|------|---------|
| 0 | done |
| 2 | done, but some passages kept their original text (see the report) |
| 4 | stopped: out of credit or allowance |
| 1 | error, or not everything was done (a failed call, --max-chars, no --yes) |
Before and after
--diff prints what the website shows inline, in the terminal:
── Fyrir ───────────────────────────────────
Vertu viss um að ná út til okkar ef þú hefur einhverjar spurningar.
── Eftir ───────────────────────────────────
Hafðu endilega samband ef eitthvað er óljóst.
── Breytingar (1) ──────────────────────────
1. − Vertu viss um að ná út til okkar ef þú hefur einhverjar spurningar.
+ Hafðu endilega samband ef eitthvað er óljóst.
↳ <the reason the engine gives>
Endurskrifað og merking staðfest · innfæddni 91/100--report (or ALTALANDI_REPORT=1) writes the same content as a Markdown file
you can keep or share. Reports are off by default because they contain your text.
stdout stays the refined text alone either way, so piping is unaffected.
Háttur: whether an agent asks you first
Your account decides how agents use Altalandi (also on the website, under Stillingar):
altalandi mode # show the setting
altalandi mode spyrja # „Spyrja fyrst“ (ask): an agent asks you once per task first
altalandi mode sjalfvirkt # „Sjálfvirkt“ (auto): the agent decides
altalandi mode alltaf # „Alltaf“ (always): it also runs Altalandi on Icelandic it writesIn ask mode, a call from an agent that would send text exits 5 and sends
nothing until it is run with --approved, after you said yes. A person at a
terminal and --dry-run are never stopped. ALTALANDI_MODE=ask|auto|always
overrides the account's setting on one computer (CI).
The meaning guarantee
Altalandi never silently changes meaning. For Icelandic input where the rewrite
can't be verified to preserve meaning, it returns the original unchanged and
says so on stderr (or meaningPreserved:false with --json). Check it before
trusting the output in an automated pipeline. Only meaningPreserved:true is a
verified pass; null means the gate was skipped or did not complete. --fast
deliberately skips the gate, so its result is unverified by design.
Without --fast, a null meaning verdict is printed as MEANING UNVERIFIED:
the check did not run or could not complete, so review the output before use.
With --fast, null is expected and is printed as meaning check skipped.
API warnings are always printed on stderr; under --json they remain in the
unchanged warnings array.
For automation, a successful HTTP response is not enough: without --fast, the
CLI prints the text or JSON response and then exits with status 3 when
meaningPreserved is false or null. Only strict true exits 0. --fast is
the explicit verification opt-out and exits 0 after a successful API response.
| exit | meaning |
|------|---------|
| 0 | verified success, or successful --fast request |
| 1 | API or network failure |
| 2 | missing input/key or invalid CLI usage |
| 3 | response printed, but meaning was false or unverified |
Config
| env | default | purpose |
|-----|---------|---------|
| ALTALANDI_API_KEY | — | your key (Bearer). Mint at https://altalandi.is/stjornbord/forritarar |
| ALTALANDI_BASE_URL | https://altalandi.is/api | API base (override for self-hosting) |
| ALTALANDI_DIFF | — | 1 = always print the before/after (as --diff) |
| ALTALANDI_REPORT | — | 1 = always write a report (as --report) |
| ALTALANDI_REPORT_DIR | ./altalandi-reports | where reports go |
MIT licensed. Calls are paid from the account's prepaid credit (a 500 kr trial,
then credit bought in the portal); a call the credit cannot cover exits with the
server's 402 insufficient_balance message. The request's text and glossary fields are charged by the character
(every Unicode character, spaces, punctuation and line breaks included) at the
effective billing-tier weight, with a minimum per call.
