Posts

The language of a post

Every post can say which language it is written in. This page covers what is stored, how it is chosen, and what uses it.

The routes that take a language are in the API reference: text-service, photo-service and video-service.

What it is for

  • Screen readers. A French post on an English page is read with French pronunciation, because its text is marked with its language.
  • Other fediverse servers. Mastodon and Bluesky both file a post under its language, and their users filter by it. Posts sent from here arrive tagged with theirs.
  • Custom feeds can keep to posts in one language.
  • Later: a translate button, which needs to know what it is translating from.

The author chooses. Nothing guesses a language. A post whose author did not say has none, and that is deliberate: other servers show an untagged post to everybody, while a wrongly tagged one is hidden from the people who could read it.

One language per post. A post that mixes two is tagged with whichever the author picks.

The tags

BCP 47, kept simple: a language, and optionally a region.

Tag
en, fr, fa A language, by its two-letter ISO 639-1 code
fr-CA, pt-BR, es-419 A language as written in a region
yue A three-letter code, for a language with no two-letter one
und Undetermined: the author would rather not say

A tag is stored in its usual spelling whatever case it arrived in (FR-ca becomes fr-CA). A two-letter code that is not a language, a language's name instead of its code, or anything longer than a language and a region is refused with 400 language_invalid.

The rules are in anahita-libs/language.

On a post

language is accepted when a note, article, topic, photo or video is created (POST /notes/:owner_id/ and the others; for a video, POST /videos/:owner_id/uploads), when a reply is posted (POST /…/:id/replies), and when a post is edited (PATCH /…/:id). It is optional. Left out when creating, the post has no language; left out when editing, it keeps the one it has.

Every post in a response carries language when it has one.

It is stored in nodes.language, which the PHP application's schema already had and nothing wrote to.

On a person

A person can say which language they usually post in: language on PATCH /people/:id. It is the same column on the person's own node, and it is what a composer should start on.

The signed-in viewer is told theirs: /oauth/userinfo carries language when it is set, and leaves it out when it is not.

This is the language somebody writes in. The language the interface is shown in is a separate thing, chosen in the web app.

What the web app does

  • A language button in the composer, beside the audience button, showing the code (EN). It opens a list of about sixty languages, named in the language the app is being read in.
  • Where it starts, in order: the posting language on their profile; the browser's language, which is the computer's; English. A language chosen for one post applies to that post only.
  • Posts are marked with their language where they are drawn, so a screen reader reads each in the right one.
  • Profile settings have "Language you post in". Left on "My browser's language", nothing is stored.

Where the code is

What Where
The tags anahita-libs/language/language.go (Normalize), tested in language_test.go
Reading a request's language, 400 language_invalid anahita-libs/handlers/language.go (RequestLanguage, RefuseLanguage)
On a post text-service/cmd/api/handlers/, photo-service/cmd/api/handlers/photo.go, video-service/cmd/api/handlers/video.go
Where it is stored table nodes, column language; graph-grpc-service/data/repositories/nodes_add.go, nodes_edit.go
On a person actor-service/cmd/api/handlers/person.go
In /oauth/userinfo auth-service/data/services/oauth_user_info.go
Sent to other servers activitypub-service/ap/note.go (NewNote), atproto-service/bsky/outgoing.go (NewOutPost)
Custom feeds in one language anahita-libs/feeds/definition.go, graph-grpc-service/data/repositories/feed_custom.go
The web app src/components/LanguageButton.jsx, src/utils/postLanguage.js (anahita-web)

What is not built

Decided on 2026-10-05, when the fuller plan was weighed against what uses a language today:

  • Detecting the language from the text, as a fallback when a client sends none or as a "looks like French" suggestion in the composer.
  • Tagging posts written before this existed. They have no language. It could only be guessed, and guessing is what the point above leaves out.
  • Changing a post's language in the web app after it is posted. The API accepts it; the edit form does not offer it yet.
  • An index on the column. Custom feeds filter by language, without one; it is worth adding when feeds that do grow slow.
  • More than one language per post. Bluesky allows three.

They belong with federation, a translate button or custom feeds, whichever first needs them.

From anahitasocial/anahita-services, 2026-10-27.

Written by Claude, reviewed by Rastin Mehr · About this