{"version":"v1.0","client_state":{"DocsNavigation.default":{"fields":{"query":""},"persist":"memory","send_to_server":true},"ColorScheme.default":{"fields":{"mode":"system"},"persist":"local","send_to_server":true}},"endpoints":{"action":"/_bretzel/action","sse":"/_bretzel/sse","refetch":"/_bretzel/refetch"},"csrf":"f3096b96136bc92e76211b66698230e1","page_id":"53490e616a354bacbcce1f0bad94bbd8","address":""}

Speaking the visitor's language

Two halves, and only one is yours: the framework translates its own sentences, your app translates its own.

What the framework does on its own

The sentences Bretzel produces — a carousel's accessibility labels, a `file_upload`'s maximum-size message, an empty table's text — go through its own table. Declare the app's languages, and they follow.
app = Bretzel(
    secret_key="…",
    lang="en",                  # the default language
    languages=("en", "fr"),     # the ones we accept
)
The language is resolved PER REQUEST: an explicit choice wins over the browser's header, and a language one has not declared falls back to the default rather than rendering a raw key.

What your app must do

`Language()` reads like its three ambient sisters — `Screen()`, `ColorScheme()`, `LiveConnection()`: one object, one field. The table is yours.
from bretzel import Language

PHRASES = {
    "en": {"save": "Save", "cancel": "Cancel"},
    "fr": {"save": "Enregistrer",
            "cancel": "Annuler"},
}

def say(key: str) -> str:
    """A function, not a component: this is TEXT, and
    it must be able to go into a `placeholder=` as
    much as into a `ui.text`."""
    code = Language().code.split("-")[0]
    return PHRASES.get(code, PHRASES["en"])[key]

ui.button(say("save"), color="primary")
`Language().code` can carry a region (`fr-CA`, `en-GB`). Cutting at the hyphen is the simplest form that works; keeping the region only makes sense if the table really distinguishes it.

Letting the user choose

`Language.set` is a SERVER handler: it picks the language and reloads the page in it. So one passes it as a `partial`, not as a call — `Language.set("fr")` would run at RENDER time, not on the click.
from functools import partial

ui.button("English", on_click=partial(Language.set, "en"))
ui.button("Français", on_click=partial(Language.set, "fr"))
Why this exists when the language is already automatic: `Accept-Language` describes the operating system's configuration, not a reading choice. With no way of saying otherwise, somebody whose machine is in English cannot read in French.

What it does not do

A seam, not a library
This is not an i18n library. No `.po` files, no plural rules, no string extraction, no date or currency formatting beyond what the browser can do. It is a SEAM: the request's language, and the framework's table for its own words. The roadmap announces full i18n in 2.1.
That split is a choice, not a lack of time: an app's sentences belong to it, and a framework imposing its catalogue format would impose its tooling too.