# Which language a reader gets — and switching it

> A reader's language is, in order: their explicit choice (a `/sv/` address prefix or a language switch), their own `[PreferredCulture]`, the browser's Accept-Language, `app.DefaultCulture`, the source language. `Session.Language` reads it, `App.Languages` lists what the app speaks, `Session.SetLanguage` switches — in place, no reload — and a mail names its recipient's with `language =>`.

<!-- id: i18n-language · area: i18n · stability: stable · html: https://osysharp.com/reference/i18n/language/ -->

## Summary        {#summary}
Only a language the app speaks is ever chosen ([A language's words — locales/<language>.osy](https://osysharp.com/reference/i18n/translations/)), and the first of these that names one wins:

1. an explicit choice — an address that starts with the language (`/sv/coffee`, served in place as `/coffee` in
   Swedish), or a switch that called `Session.SetLanguage("sv")`; remembered from then on;
2. the signed-in reader's own `[PreferredCulture]` (its language);
3. the browser's `Accept-Language`;
4. the language of `app.DefaultCulture`;
5. the source language — English, or what `app.SourceLanguage` names ([app.SourceLanguage — the language the app is written in](https://osysharp.com/reference/i18n/source-language/)).

## Signature      {#signature}
```osy syntax
string       Session.Language           // "sv" — the language this reader reads in
List<string> App.Languages              // ["en", "sv"] — the source first, then every translated language
string?      App.CultureOf(string language)          // "sv" → "sv-SE" — the culture the app formats it in, or null
void         Session.SetLanguage(string language)   // a client effect: the page re-renders in place
[Email] component … { language => order.Language; subject => …; … }   // a mail in its recipient's language
MyMail(order, language: "sv")                                           // …or rendered in a chosen one (a preview)
```

## Description    {#description}
**One choice drives everything.** The page's text, its `<title>`, `lang` and `dir` (an Arabic page reads right to
left — [Right to left — an app that reads in Arabic with no change to the app](https://osysharp.com/reference/i18n/right-to-left/)), a server render, a server function's
error message — and the culture numbers and dates format in ([Culture formatting — ToString(format, culture)](https://osysharp.com/reference/stdlib/culture-formatting/)): a Swedish reader sees
`1 234,50 kr` even when `app.DefaultCulture` is `en-GB`, because the language chooses the app's declared culture of
that language. A reader whose own culture speaks the chosen language keeps it.

**Switching is in place.** `Session.SetLanguage("sv")` fetches that language's table (by content address, cached
forever) and every region that shows a translated text re-renders — no reload, no lost state. The choice is kept in
the browser and sent with every request, so the server answers in it too.

**A mail is in its recipient's language, not the sender's.** Declare it on the mail: `language => order.Language;`.
Every text in the mail — its subject, its parts, a shared footer — renders in that language, whoever sends it (a
customer at the checkout, staff at the desk, a scheduled job). With none, a mail is in the language of the work
sending it. An app that mails about something should record the language it was placed in (`Session.Language` at the
moment it is placed). Every mail also takes an optional last argument, `language:` — `MyMail(order, language: "sv")`
renders it in Swedish whoever it is for, which is what a desk's preview of the Swedish mail needs.

**…and so are its numbers, its money, its dates and its data.** Inside a mail, a culture-less format
(`$"{total:N2}"`, `price.ToString("C")`, a date written as text) is in the culture of the MAIL's language — `17,68 kr`
in a Swedish one, `17.68 kr` in an English one — and a `[Translatable]` value it reads (a product's name) is read in
it too, wherever the mail is rendered: a request in another language, a workflow, a job, a test. A mail's language
may be named by a culture: `language => account.Language;` with a `[PreferredCulture]` holding `"sv-SE"` is Swedish.

**A person's own language is on their account.** Give the principal a `[PreferredCulture] Culture? Language;` and set it
when they switch while signed in: a device where they chose nothing (a new phone) reads the app in it once they sign
in — every request, not only the first page — and mail about them rather than about an order says
`language => account.Language;`. A choice made on a device still outranks it there.

**Words staff wrote for a mail are per language, too.** A mail's `Editable` parts keep the words staff saved per
language ([[Email] — mail rendered from data](https://osysharp.com/reference/ui/email/)): the mail asks for the words saved in ITS language, and a language with none says the
template's default as that language's locale file translates it — the English words a shop wrote never reach a
Swedish reader.

**A server body can switch too.** `Session.SetLanguage("sv")` in a server function puts Swedish in effect for the rest
of that run: its data reads and writes ([[Translatable] — an entity's own words in every language](https://osysharp.com/reference/i18n/translatable/)), its translated texts, and `Session.Language` itself.
Where no request is in flight at all — a workflow, a scheduled job, a test body — `Session.Language` is the source
language and `App.Languages` is still every language the app speaks. A culture-less format in an app that speaks more
than one language follows the language being read (the request's, the mail's, or the one a body chose), in a page
and in a function alike.

**Which culture a language formats in — `App.CultureOf`.** `App.CultureOf("sv")` answers the culture the app writes
Swedish in: `app.DefaultCulture` when that is a Swedish one, else the app's first declared culture of Swedish, else null
(the app does not speak it). It is the same rule a request in that language and a mail in it format with, so it is what
to keep as a person's `[PreferredCulture]` when they switch — without switching the run's language to find out.

**Who is served what.** A browser is served only its own language's table. The words of pages a signed-out visitor
cannot open (neither `[AllowAnonymous]` nor `[Composable]`) are in a separate table served to signed-in readers only,
and a mail's words are never served to a browser at all.

## Examples       {#examples}
```osy title="a language switch: the other language's code, one press away" test app=i18n-language
[Composable]
[AllowAnonymous]
component LanguageSwitch() {
  action Choose(string lang) { Session.SetLanguage(lang); }
  render {
    Row(gap: 1, label: "Language") {
      foreach (var lang in App.Languages.Where(l => l != Session.Language)) {
        Pressable(onClick: () => Choose(lang), label: lang) { Text(lang.ToUpper()); }
      }
    }
  }
}

translations sv {
  accessibleName "Language" => "Språk";
}
```

```osy title="the culture to keep on an account when its owner switches language" test app=i18n-language
string CultureToKeep(string language) {
  // null for a language the app does not speak — then there is nothing to keep
  return App.CultureOf(language) ?? "";
}
```

## See also       {#see-also}
- [Every language's page, for a search engine — hreflang, canonical, sitemap](https://osysharp.com/reference/i18n/search-engines/) — each page's address in every language, for a search engine
- [Which words are translated — the compiler finds every text a reader sees](https://osysharp.com/reference/i18n/extraction/) — which texts are translated
- [Culture formatting — ToString(format, culture)](https://osysharp.com/reference/stdlib/culture-formatting/) — the culture a language formats in
- [A language's words — locales/<language>.osy](https://osysharp.com/reference/i18n/translations/) — the languages an app speaks
- [app.SourceLanguage — the language the app is written in](https://osysharp.com/reference/i18n/source-language/) — `app.SourceLanguage`, the language the app is written in
