Languages

Neens ships its interface in eight languages, and every one of them is fully translated — not “mostly”. This page covers how to choose yours, how an API client asks for a language, and what stays in English on purpose.

At a glance

WhereTopbar language switcher, or Settings → Account → Language
Key API routesPUT /auth/locale (persist your choice), Accept-Language header (per request)
Languagesen es fr de it pt ar ja
ScopePer user. Your language never changes what anyone else sees
Falls back toEnglish — for content Neens does not own, and for the handful of API messages that are not in the translated set (see What stays in English)

Supported languages

CodeLanguageDirection
enEnglishleft-to-right
esEspañolleft-to-right
frFrançaisleft-to-right
deDeutschleft-to-right
itItalianoleft-to-right
ptPortuguêsleft-to-right
arالعربيةright-to-left
ja日本語left-to-right

Picking Arabic sets the document direction to RTL, so text, input fields, and the browser’s own scrollbars and text selection follow Arabic reading order.

⚠️

Arabic layout is not yet mirrored. The direction flips, but the app’s spacing and alignment still use physical left/right utilities rather than CSS logical properties, so paddings, margins, and column alignment stay in their left-to-right positions. Arabic is fully translated and usable; the layout pass is outstanding.

Choose your language

Before you sign in

The sign-in page has no language switcher. Neens guesses from your browser’s Accept-Language, so the sign-in form, error messages, and the password rules usually already speak your language — but it is a guess, not a choice, and there is no way to override it until you are signed in.

After you sign in

Use the topbar language switcher, or Settings → Account → Language → Display language. The switch is immediate — no reload — and is saved to your profile (PUT /auth/locale), so it follows you to any browser or device you sign in from.

Your first sign-in stores whatever you were reading

If your profile has no language stored yet, Neens back-fills the one the interface is currently using — your explicit pick if you made one, otherwise the browser guess. From then on the stored value wins everywhere, on every device, until you change it in Settings.

Because the first sign-in promotes a browser guess into a stored preference, a user whose browser is set to a language they do not want should change it in Settings → Account; clearing browser state will not do it.

Ask for a language from an API client

Every JSON API route honours the standard Accept-Language request header. The web app stamps your active language on every call it makes; your own scripts and integrations can do the same.

# German error copy from a deliberately unauthenticated call
curl -s https://app.neens.ai/api/auth/me \
  -H 'Accept-Language: de'
# {"detail":"Nicht authentifiziert."}
 
# Same call, Japanese
curl -s https://app.neens.ai/api/auth/me \
  -H 'Accept-Language: ja'
# {"detail":"認証されていません。"}

Header handling follows the spec, so you can pass a browser’s header through unchanged:

You sendNeens usesWhy
dedeexact match
pt-BRptregion is dropped when the base language is supported
fr-CA, fr;q=0.9, en;q=0.5frhighest-quality supported entry wins
nlenunsupported → English, never an error
(no header)en—

Accept-Language affects the response copy, never the data. It does not filter, translate, or reorder your traces, scores, or any other stored record.

What gets translated

SurfaceTranslated?Notes
The whole web interfaceYes, completelyEvery navigation item, button, table header, tooltip, empty state, and error banner
API error messagesHigh-traffic messagesThe curated catalogue covers authentication, permission, and validation errors. A message outside it stays readable English rather than blank
Transactional emailsYesInvitations, activation, password changed/reset, and failed-sign-in alerts, sent in the recipient’s stored language
The digest emailNo — EnglishLocalizing the digest body is deferred; only the greeting uses your name
Right-to-left text directionYes (ar)The document direction flips. Layout mirroring is still outstanding — see the note above
Your own dataNo — it’s yoursTrace content, session text, agent and judge names, dataset rows, annotations
LLM-generated textFollows your LLMCluster labels, remediation drafts, and narrative summaries are written by the LLM connection you configured, from your own trace data

What stays in English

Some strings are identical in every language on purpose, and translating them would make the product harder to use:

  • The product name — Neens.
  • Acronyms borrowed unchanged across all eight languages — API, SDK, LLM, URL, JSON, CSV, HTTP, IP, PR, CI, MCP, SSO, TOTP.
  • Metric and verdict tokens that must match what the API returns and the CI runners print — pass^k, PASS, FAIL, p50/p95/p99.
  • Code you would copy-paste — field names, file names, env var names, header names (X-Neens-Project-Id), span attributes (neens.*), key prefixes (nk_live_…), and model ids such as claude-opus-5.
  • Vendor names — GitHub, Slack, ClickHouse, Postgres, Langfuse, Zendesk, and the rest.

The judgement calls — a word that happens to be identical in one language, a label that stays an acronym — are recorded one by one with a written reason. The purely mechanical cases (a bare number, a URL, an identifier like user_id) are recognised automatically and deliberately are not listed, so the recorded list stays short enough to actually read.

How completeness is guaranteed

A missing translation is easy to catch. A translation that is silently the English string is not: the language file has the key, everything loads, and only a reader who speaks the language notices. That is exactly what happened to one of our pre-production screens, whose sign-in hints sat in English across all seven non-English languages while every automated check reported green.

Neens now blocks both failure modes before a change can ship:

  1. Every language carries every key. A key that exists in English and is missing elsewhere fails the build.
  2. No language may carry the English string. If a value is byte-identical to the English one, it fails the build — unless it is on the short, reasoned list above, or it is structurally untranslatable (a bare number, a URL, an identifier like user_id, a {{placeholder}}).
  3. Every language carries every plural form it needs. English has two (one/other); Arabic has six and the Romance languages have three. A language missing one of its own forms falls back to English for exactly the counts in that form — for Arabic that was 0 and 2–99 — so the build now checks each language against its own CLDR rules, not against English’s.
  4. Nothing can be deferred quietly. The list of accepted exceptions can only shrink automatically; adding to it takes a deliberate, reviewed edit.

So “eight languages” means the same thing for the eighth language as for the first.

Troubleshooting

SymptomCauseFix
Interface is English after signing in on a new deviceYour profile preference is set, but you had picked a different language on that browser before signing inSigning in applies your stored profile language; switch from the topbar if you want to change it
A specific error message is English while the UI is translatedThe message is outside the curated API catalogue, or it embeds live values (an id, a limit)Nothing to fix — the message stays readable rather than blank. Report it if it is a common one
Emails arrive in English but the UI is translatedEmails use your stored profile language; a browser-only choice never reached the serverSign in and pick your language from Settings → Account so it is persisted
Cluster labels and remediation text are EnglishThey are generated by your configured LLM from your trace data, not shipped copyNothing to fix in Neens; the language follows the model and the data
Arabic reads right-to-left but the layout is not mirroredKnown limitation — the interface still uses physical left/right spacingNothing to do yet; the text direction is correct and the layout pass is tracked separately

Related: Account settings · LLM connections