Hi everyone,
I’ve been working on implementing real-time i18n in my own Phoenix LiveView application, specifically on the registration page. The goal was to allow users to change the language of the form instantly.
After complete this feature, I had browse forum to see community’s solution and I found Routex - build powerful Phoenix routes: localize, customize, and innovate finally. It’s a pity to erase the experience so I wanna write my solution and have a discuss related how to make it better(the code seems to be a little bit ugly, frankly speaking).
Repo: GES233/EchoesUnderBlossoms: Some echoes should not be forgotten. (document seems to be a bit chunibyo cause I refactored it by Gemini several times.)
The Initial Goal & Problem
On my registration LiveView (HanaShirabeWeb.MemberLive.Registration), I have a language selector. When a user changes the language, the UI text (labels, buttons) should update instantly.
At the beginning, I write a function attampt to handle it simply, Gettext.put_locale(some_target_locale).
I forgot what the problem was, but the nav bar remained.
First attempt
To my shame, I knew absolutely nothing about Phoenix and LiveView before this.
I implement a cookie to store the locale state before(a plug called SetLocale), so I tried to use it to persist locale state at client side.
This is how it determine languages:
defp fetch_locale_from_sources(conn) do
locale_from_user =
if !is_nil(conn.assigns.current_scope),
do: conn.assigns.current_scope.member.prefer_locale,
else: nil
[
conn.params["locale"],
locale_from_user,
conn.req_cookies[@locale_cookie],
get_req_header(conn, "accept-language") |> parse_accept_language()
]
end
But when I add /?locale=en/ja/... request to refresh the page, the language changes, but with full of rough(I don’t know hot to describe it in English accurately) and all data in form disappear.
I tried to convince myself that since the user required changing the language settings in form, the data wasn’t necessary to store.
I don’t know if I succeeded, but it seems like other data isn’t being saved.
But I’m actually quite against stuffing all sorts of data into the user-visible params, because many websites stuff links with all sorts of data that could potentially track users(such as Bilibili[1], Douyin, RedNote, etc.), and what’s even more disgusting is that many people spread these links everywhere with several parameters that have no meaning for sharing.
So I have HIGH requirements for the simplicity of website links.
I don’t remember how many AI programs I’ve tried(at least GPT/Grok/Gimini/Qwen/Deepseek), so I’ll just go straight to solution.
ColocatedHook + Controller + Delay Reflash
1. Phoenix LiveView → Client(ColocatedHook)
def handle_event(
"locale_changed",
%{
"_target" => ["registration_form", "prefer_locale"],
"registration_form" => %{"prefer_locale" => locale}
},
socket
) do
Gettext.put_locale(HanaShirabeWeb.Gettext, locale)
# The test code for this function only needs to account for cookie updates.
socket = push_event(socket, "set_locale_cookie", %{locale: locale})
{:noreply, socket}
end
2. Client → Controller
in <script :type={Phoenix.LiveView.ColocatedHook} name=".LocaleFormInput">:
export default {
mounted() {
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
this.handleEvent("set_locale_cookie", ({ locale }) => {
fetch(`/set-locale/${locale}`, {
method: "POST",
headers: {
"x-csrf-token": csrfToken
}
}).then(response => {
if (response.ok) {
this.pushEvent("locale_cookie_updated", {locale: locale});
}}).catch(error => {
console.error(`Failed to set locale cookie to '${locale}':`, error)
});
});
}
}
And in server side:
defmodule HanaShirabeWeb.LocaleController do
@moduledoc """
This is actually a plugin designed to allow the page language to be updated immediately
when the language is changed in the registration form.
"""
use HanaShirabeWeb, :controller
# It needs to be determined that cookies have a lower priority than `?locale=(...)` and user settings.
def update(conn, %{"locale" => locale}) do
# I extract the inject-cookie-phase into a function
conn |> HanaShirabeWeb.SetLocale.persist(locale) |> send_resp(204, "")
end
end
with router: post "/set-locale/:locale", LocaleController, :update
3. Refresh
# Because of the properties of LiveView's underlying socket and the "global" action of changing the language
# this means it cannot be solved using Phoenix.LiveView
# so this very inelegant method has to be used.
# すみません
def handle_event("locale_cookie_updated", %{"locale" => locale}, socket) do
# for that flash message with `Locale Updated!`
Gettext.put_locale(locale)
{:noreply,
socket
|> put_flash(:info, gettext("Locale updated!"))
|> redirect(to: ~p"/sign_up", replace: true)}
end
Display
There’s no gif demostration because of its size.
Besides sharing this rather bumpy experience to vent, I’m also curious if there’s a more elegant way to do it?
Also, I know that the audience of my Repo project is not very relevant with here, but if you want to discuss it, you can do so here.
添加去除跳转时网址参数(?大概)功能的 · Issue #263 · the1812/Bilibili-Evolved ↩︎

























