Profile language
This feature stores the admin language of each user in the database. It plugs into the i18n system of core through a locale driver.
The data
profiles.locale is text and can be null. Null means Automatic: the user has not chosen a language, so the admin uses the browser language.
The check profiles_locale_format accepts any code that looks like en or pt-BR. It does not list the allowed languages, so a new language needs no migration. The route checks the code against runtimeConfig.public.plutoI18n.adminLocales.
The migration is 005_profile_locale.sql.
Why a function writes the column
public.profiles has no update policy. A policy that lets a user update their own row would also let them write is_admin.
So the function public.set_my_locale(new_locale text) is the only writer.
- It is
security definerwith an emptysearch_path. - It updates the row where
id = auth.uid()and sets onlylocaleandupdated_at. - The migration revokes
executefrompublicandanon, and grants it toauthenticated. - The migration adds no RLS policy.
A user reads their own row through the existing select policy.
Routes
GET /api/_pluto/supabase/me/locale returns { locale: string | null } for the calling user.
- A signed-out caller gets
{ locale: null }. - If the column does not exist yet, the route logs the error and returns
{ locale: null }.
PUT /api/_pluto/supabase/me/locale takes { locale: string | null }.
| Case | Answer |
|---|---|
| Signed out | 401 with the key supabase.errors.notLoggedIn |
| Body does not match | 400 |
Locale not in adminLocales | 400 with the key supabase.errors.localeUnsupported |
The function set_my_locale does not exist | 409 with the key supabase.profile.migrationPending |
| Any other error | 500 with the key supabase.errors.localeSave |
| Success | { locale } |
Every failure is an HTTP error with an i18n key. It is not a 200 with success: false.
The client
The layer registers a localeDriver that calls the two routes. If a save fails, the language card on the Settings page shows the reason in a toast. The switch stays on the device, because core keeps it in a cookie.
Core's pluto-auth-sync.client.ts loads the stored language when a session appears. The plugin is client-only. A server $fetch does not forward the user cookies, so a server run would read no locale and clear the cookie.
A site that did not apply migration 005
The language card still works, and the switch is kept in the cookie. The save returns 409, and the toast tells the admin to apply the pending migrations on /admin/migrations.