A UI language picker: every language named in its own language — 日本語, Deutsch, العربية, 한국어 — with its name in the page's language beside it, its BCP-47 tag, and a filter that reaches it by all four. Reach for it wherever a form has to settle which language something is read in: the interface or display language in account, profile, workspace and organisation settings; the language switcher on a multilingual site, marketing page or docs site; the language a customer's emails, notifications, invoices and receipts are sent in; the locale on an admin's view of a user, a seat or a tenant; the language of a help centre, knowledge base or support ticket; subtitle and audio-track selection; the source and target language on a translation or localisation screen; and the "preferred language" row on onboarding and sign-up. Common asks it answers: "language select", "language picker", "language switcher react", "locale select", "locale picker", "i18n language dropdown", "language selector shadcn", "shadcn language switcher", "BCP-47 select", "language combobox", "next-intl language switcher", "i18next language selector", "react-i18next dropdown", "choose interface language", "native language names select", "endonym language list", "list of languages react", "RTL language select". Official shadcn/ui has no language, locale or i18n item of any kind, and touches Intl nowhere in its sixty-three components — and the one item whose name comes close, direction, is a Radix DirectionProvider wrapper that takes a dir you have already worked out; it has no idea which languages are right-to-left. This fills that gap and exports the missing line between them. The fact the whole API is shaped around: there is no list of languages to read. Intl.supportedValuesOf answers for calendars, collations, currencies, numbering systems, time zones and units, and throws a RangeError for "language", "locale", "region" and "script" — unlike pulld's country-select and currency-select, which read the world's list at runtime. That absence is the right shape anyway, because a UI language field offers what you have actually translated, not the world's eight thousand languages: it offers the directories in your locales/ folder. So `languages` is the main prop — `languages={Object.keys(messages)}` — with a documented default of forty-one common tags so it renders something sensible on install. Every row leads with the endonym, the language naming itself, from Intl.DisplayNames(tag).of(tag). That is the one thing a hand-written language picker gets wrong, and it is not a nicety: the person opening this control is very often someone who cannot read the page it is on — that is why they opened it — so a list that says "Japanese" is no help to them and a list that says 日本語 is. The name in the page's language sits beside it for whoever is choosing on someone else's behalf, support staff setting a customer's locale or an admin filling in a seat, and disappears when it would only repeat the endonym. Tags, not bare ISO 639 codes, because a tag is what a locale directory is called: pt-BR and pt-PT are two shipped translations and the runtime names both ("Brazilian Portuguese", "European Portuguese"), zh-Hans is "Simplified Chinese", es-419 is "Latin American Spanish", and in Japanese the same call gives「ポルトガル語 (ブラジル)」. Right-to-left is line-drawn on purpose. Each row is marked with lang and dir, so a screen reader pronounces 한국어 with a Korean voice instead of spelling it out and العربية is laid out correctly inside a list that is not — but the component does not touch your document, because turning a chosen language into an RTL page is a decision about the whole tree. getLanguageDirection(tag) is exported so the two lines that do are yours (`document.documentElement.lang = tag; document.documentElement.dir = getLanguageDirection(tag)`), it is what you feed shadcn's own direction item, and it knows more than Arabic and Hebrew: Persian, Urdu, Pashto, Sorani Kurdish, Yiddish, Divehi, Sindhi and Uyghur are right-to-left too, while Kurmanji Kurdish and Azerbaijani are not. A tag the runtime cannot name is dropped rather than drawn raw — and the rule its sibling pickers use is not enough here, because Intl names "xx-US" as "xx (United States)", so the language subtag is checked as well; "pt_BR", the POSIX spelling that Intl rejects outright, and tags carrying a -u- extension go the same way. The filter reads four faces, because four different people type into it: the endonym (someone looking for their own language), the name in the page's language (someone choosing for them), the English name (typed constantly on non-English sites, because it is what the documentation says), and the tag itself (the developer, who has "pt-BR" in their head because it is the name of a directory). An exact tag wins outright, so "id" answers with Indonesian rather than Ido; a partial one finds a family, so "pt" brings both Portuguese translations and "zh" both Chinese scripts; folding strips accents and punctuation, so "cestina" finds čeština and 日本 finds 日本語; and a query that folds away to nothing, like "()", filters instead of quietly showing every row. Sorted through Intl.Collator by what the rows actually say, which groups the list by script and lets the reader's own collation decide where their script lands. The trigger is labelled with the endonym, and because that label does not depend on who is reading, it renders correctly on the server and never flashes a bare tag through hydration the way a name in the reader's language has to. A real combobox, not a styled div: the trigger is a type="button" with role="combobox" and aria-expanded, the panel is a listbox driven by aria-activedescendant, arrow keys, Home, End, Enter and Escape all work, the highlight scrolls itself into view, opening a field that already says 日本語 starts on 日本語, and an outside press closes it. Works controlled (`value` + `onValueChange`) or uncontrolled (`defaultValue`), and always emits the caller's own tag, character for character — never canonicalised, because that string is a key into your translations. `name` adds a hidden input so it submits with a native form; a stored tag outside a narrowed `languages` is still named rather than reading as "nothing chosen"; `priority` pins the two or three languages most of your readers use above the rest; `tags` turns off the tag column; and getLanguageName(), getLanguageEndonym() and getLanguageDirection() are exported for the rest of the page. Styled entirely with shadcn tokens (input, ring, accent, popover, muted-foreground), so it follows light and dark mode, and it ships zero dependencies — no locale-data package, no icon package, one file.