chore: refactored the UI locale handling

This commit is contained in:
2026-05-01 15:58:55 +02:00
parent 62e44150b3
commit 296a57814f
36 changed files with 114 additions and 40 deletions

View File

@@ -249,7 +249,7 @@ defmodule BDS.Desktop.ShellData do
end
defp effective_ui_language(nil) do
Process.get(:bds_ui_locale) || ui_language()
BDS.Desktop.UILocale.current() || ui_language()
end
defp effective_ui_language(locale), do: locale

View File

@@ -7,7 +7,7 @@ defmodule BDS.Desktop.ShellLive do
alias BDS.AI
alias BDS.CliSync.Watcher
alias BDS.Desktop.{FolderPicker, Overlay, ShellData}
alias BDS.Desktop.{FolderPicker, Overlay, ShellData, UILocale}
alias BDS.Desktop.ShellLive.{ChatEditor, CodeEntityEditor, ImportEditor, MediaEditor, MenuEditor, MiscEditor, SettingsEditor, TagsEditor}
alias BDS.Desktop.ShellLive.OverlayComponents, as: ShellOverlayComponents
alias BDS.Desktop.ShellLive.PostEditor
@@ -1285,7 +1285,7 @@ defmodule BDS.Desktop.ShellLive do
@impl true
def render(assigns) do
Process.put(:bds_ui_locale, assigns.page_language)
UILocale.put(assigns.page_language)
index(assigns)
end
@@ -1346,7 +1346,7 @@ defmodule BDS.Desktop.ShellLive do
|> assign_misc_editor()
end
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, UILocale.current())
defp encoded_shortcuts(shortcuts), do: Jason.encode!(shortcuts)

View File

@@ -572,5 +572,5 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
defp format_error(reason), do: inspect(reason)
def translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -114,5 +114,5 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.MessageBuild do
defp streaming_content(_request), do: ""
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -76,5 +76,5 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ModelSelection do
defp blank?(nil), do: true
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -270,5 +270,5 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
defp truthy?(_value), do: false
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -229,5 +229,5 @@ defmodule BDS.Desktop.ShellLive.ChatSurface do
end
end
defp translated(text), do: ShellData.translate(text, %{}, Process.get(:bds_ui_locale))
defp translated(text), do: ShellData.translate(text, %{}, BDS.Desktop.UILocale.current())
end

View File

@@ -190,7 +190,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
def build_template(_assigns), do: nil
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
def format_timestamp(nil), do: ""
def format_timestamp(timestamp), do: BDS.Persistence.timestamp_to_iso8601(timestamp)

View File

@@ -770,7 +770,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
end
end
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
defp present?(value), do: value not in [nil, ""]
defp blank?(value), do: value in [nil, ""]
end

View File

@@ -243,6 +243,6 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
def translate_phase(other), do: other
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
defp present?(value), do: value not in [nil, ""]
end

View File

@@ -242,5 +242,5 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
def translate_execution_phase(other), do: other
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -199,7 +199,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
def maybe_put_option(opts, _key, nil), do: opts
def maybe_put_option(opts, key, value), do: Keyword.put(opts, key, value)
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
defp present?(value), do: value not in [nil, ""]
defp blank_to_nil(""), do: nil
defp blank_to_nil(value), do: value

View File

@@ -415,7 +415,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
def build(_assigns), do: nil
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
def media_editor_save_state_label(:dirty), do: translated("Unsaved")
def media_editor_save_state_label(:saved), do: translated("Saved")

View File

@@ -307,7 +307,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
end
def translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
def row_label(item, category_titles) do
if item.kind == :category_archive do

View File

@@ -128,5 +128,5 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -81,5 +81,5 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -214,7 +214,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
def build(_assigns), do: nil
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
def misc_class(:site_validation), do: "site-validation-view"
def misc_class(:metadata_diff), do: "metadata-diff-view"

View File

@@ -59,7 +59,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
def markdown_link(text, url), do: "[#{text}](#{url})"
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
def project_metadata(nil), do: %{main_language: "en", blog_languages: []}

View File

@@ -286,5 +286,5 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
defp present?(value), do: value not in [nil, ""]
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -500,7 +500,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
def post_editor_mode_label(:preview), do: translated("Preview")
def translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
defp assigned_project_metadata(assigns), do: Map.get(assigns, :project_metadata, %{})
end

View File

@@ -101,5 +101,5 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -186,5 +186,5 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -143,7 +143,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
end
def translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
defp current_settings_section(assigns) do
meta = current_tab_meta(assigns)

View File

@@ -199,5 +199,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -89,5 +89,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.EditorSettings do
defp boolean_string(false), do: "false"
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -190,5 +190,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -96,5 +96,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -108,5 +108,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ProjectSettings do
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -85,5 +85,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings do
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -99,5 +99,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.StyleEditor do
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -4,10 +4,11 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
use Phoenix.Component
alias BDS.Desktop.ShellData
alias BDS.Desktop.UILocale
alias BDS.UI.Registry
def sidebar_content(assigns) do
Process.put(:bds_ui_locale, assigns.page_language)
UILocale.put(assigns.page_language)
assigns = prepare_filter_assigns(assigns)
~H"""
@@ -462,7 +463,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
"""
end
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
defp translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
defp template_sidebar?(sidebar_data), do: Map.get(sidebar_data, :title) == "Templates"

View File

@@ -127,5 +127,5 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
def action(:import), do: %{kind: "import", label: "sidebar.import.newDefinition"}
def action(_view), do: nil
defp translated(text), do: ShellData.translate(text, %{}, Process.get(:bds_ui_locale))
defp translated(text), do: ShellData.translate(text, %{}, BDS.Desktop.UILocale.current())
end

View File

@@ -95,5 +95,5 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
end
end
defp translated(text), do: ShellData.translate(text, %{}, Process.get(:bds_ui_locale))
defp translated(text), do: ShellData.translate(text, %{}, BDS.Desktop.UILocale.current())
end

View File

@@ -187,7 +187,7 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
def build(_assigns), do: nil
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
def translated(text, bindings \\ %{}), do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
def tag_font_size(count, counts) do
max_count = Enum.max([1 | Enum.map(counts, & &1.count)])

View File

@@ -0,0 +1,65 @@
defmodule BDS.Desktop.UILocale do
@moduledoc """
Per-render UI locale binding for the desktop LiveView shell.
The shell renders the UI in the user's selected language, which can differ
from the OS locale and from the project's `mainLanguage`. Phoenix HEEx
templates call `translated/1,2` helpers without an explicit locale, so we
bind the active locale once per `render/1` and the helpers read it back.
This module encapsulates that binding so call sites do not touch the raw
process dictionary directly. Use `with_locale/2` around any render or
component that needs a locale binding; use `current/0` to read it.
Direct use of `Process.put(:bds_ui_locale, _)` or
`Process.get(:bds_ui_locale)` is forbidden outside this module.
"""
@key :bds_ui_locale
@typedoc "A normalized UI locale code such as `\"en\"` or `\"de\"`."
@type locale :: String.t() | nil
@doc """
Bind `locale` for any subsequent reads of `current/0` in this process.
Used at LiveView render boundaries. The binding persists past the call
(mirroring per-process locale state) so that lazily evaluated child
components see the active locale. Each render boundary overwrites it.
"""
@spec put(locale()) :: :ok
def put(locale) do
Process.put(@key, locale)
:ok
end
@doc """
Set the UI locale for the duration of `fun`, then restore the prior value.
Safe under exceptions: the prior binding is restored in an `after` clause.
Use this for short-lived non-LiveView contexts (background tasks, scripts)
where eager evaluation guarantees the binding is consumed before `fun`
returns. Do not use around LiveView `render/1` because the returned
`Phoenix.LiveView.Rendered` struct evaluates its dynamic parts lazily.
"""
@spec with_locale(locale(), (-> result)) :: result when result: var
def with_locale(locale, fun) when is_function(fun, 0) do
previous = Process.get(@key)
Process.put(@key, locale)
try do
fun.()
after
restore(previous)
end
end
@doc """
Read the active UI locale binding for this process, or `nil` when unset.
"""
@spec current() :: locale()
def current, do: Process.get(@key)
defp restore(nil), do: Process.delete(@key)
defp restore(value), do: Process.put(@key, value)
end