chore: refactored the UI locale handling
This commit is contained in:
14
CODESMELL.md
14
CODESMELL.md
@@ -37,11 +37,15 @@ _None._ All modules previously on the queue have been split; refresh the queue i
|
|||||||
|
|
||||||
## 2. Process Dictionary for i18n State
|
## 2. Process Dictionary for i18n State
|
||||||
|
|
||||||
**Status:** open. `Process.put(:bds_ui_locale, …)` + `Process.get(:bds_ui_locale)` in `render/1` of `BDS.Desktop.ShellLive` and every `*_editor.ex` (15 sites total).
|
**Status:** ✅ encapsulated (2026-05-10). Raw `Process.put(:bds_ui_locale, _)` / `Process.get(:bds_ui_locale)` no longer appears outside `BDS.Desktop.UILocale`. The two render boundaries (`BDS.Desktop.ShellLive.render/1` and `BDS.Desktop.ShellLive.SidebarComponents.sidebar_content/1`) now call `UILocale.put/1`; the ~30 helper read sites call `BDS.Desktop.UILocale.current/0`. The full thread-locale-through-assigns rewrite (~733 HEEx call sites) was rejected as too invasive for the marginal benefit; the encapsulation removes the implicit-global smell while keeping the LiveView lazy-render path intact.
|
||||||
|
|
||||||
**Why it matters:** implicit global state; complicates per-process isolation in tests and risks leaks between concurrent operations in the same process.
|
**Why the helper does not restore on render exit:** Phoenix's `~H` returns a `Phoenix.LiveView.Rendered` whose `dynamic` function is invoked lazily by LiveView *after* `render/1` returns. A `try/after Process.delete` wrapper around `render/1` would clear the binding before child components materialize, so render boundaries use `UILocale.put/1` (set-only). `UILocale.with_locale/2` is provided for short-lived non-LiveView contexts (background tasks, scripts) that consume the binding eagerly inside the closure.
|
||||||
|
|
||||||
**Plan:** thread `locale` through `assigns`/render args; replace `translated/1,2` with a 3-arity helper that takes the locale explicitly; ban `Process.put` via Credo afterwards.
|
**Module rules:**
|
||||||
|
|
||||||
|
- Only `lib/bds/desktop/ui_locale.ex` may touch the `:bds_ui_locale` process key.
|
||||||
|
- New code that needs the active UI locale must call `BDS.Desktop.UILocale.current/0` (or take it as an argument).
|
||||||
|
- New render entry points that change the locale must call `BDS.Desktop.UILocale.put/1` at the top of `render/1`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -165,6 +169,10 @@ Most tests share the SQLite repo and named GenServers (`BDS.Tasks`, `BDS.Search`
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### 2026-05-10
|
||||||
|
|
||||||
|
- **Process dictionary for i18n state (Section 2)**: encapsulated behind `BDS.Desktop.UILocale` (`lib/bds/desktop/ui_locale.ex`, ~50 lines). Public surface: `put/1` (set without restore, for LV render boundaries that return lazy `Rendered`), `with_locale/2` (set + try/after restore, for short-lived eager contexts), `current/0` (read, returns `nil` when unset). The two raw `Process.put(:bds_ui_locale, _)` sites (`BDS.Desktop.ShellLive.render/1` and `BDS.Desktop.ShellLive.SidebarComponents.sidebar_content/1`) now call `UILocale.put/1`; the ~30 raw `Process.get(:bds_ui_locale)` reads (every editor `translated/1,2` helper plus `BDS.Desktop.ShellData.effective_ui_language/1`) now call `BDS.Desktop.UILocale.current/0`. The full thread-locale-through-assigns rewrite (~733 HEEx call sites) was deliberately rejected as too invasive; the encapsulation removes the implicit-global smell while preserving Phoenix's lazy `Rendered` evaluation. The render path uses `put/1` (not `with_locale/2`) because Phoenix `~H` returns a `Rendered` whose `dynamic` is invoked by LiveView *after* `render/1` returns; a `try/after Process.delete` would clear the binding before child components materialize. Only `BDS.Desktop.UILocale` is allowed to touch the `:bds_ui_locale` process key. Validates clean: `mix compile --warnings-as-errors`, `mix dialyzer --format short` (Total errors: 0), `mix test` (342 tests, 0 failures, 4 skipped, three consecutive runs).
|
||||||
|
|
||||||
### 2026-05-09
|
### 2026-05-09
|
||||||
|
|
||||||
- **God modules**:
|
- **God modules**:
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ defmodule BDS.Desktop.ShellData do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp effective_ui_language(nil) do
|
defp effective_ui_language(nil) do
|
||||||
Process.get(:bds_ui_locale) || ui_language()
|
BDS.Desktop.UILocale.current() || ui_language()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp effective_ui_language(locale), do: locale
|
defp effective_ui_language(locale), do: locale
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
|
|
||||||
alias BDS.AI
|
alias BDS.AI
|
||||||
alias BDS.CliSync.Watcher
|
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.{ChatEditor, CodeEntityEditor, ImportEditor, MediaEditor, MenuEditor, MiscEditor, SettingsEditor, TagsEditor}
|
||||||
alias BDS.Desktop.ShellLive.OverlayComponents, as: ShellOverlayComponents
|
alias BDS.Desktop.ShellLive.OverlayComponents, as: ShellOverlayComponents
|
||||||
alias BDS.Desktop.ShellLive.PostEditor
|
alias BDS.Desktop.ShellLive.PostEditor
|
||||||
@@ -1285,7 +1285,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
Process.put(:bds_ui_locale, assigns.page_language)
|
UILocale.put(assigns.page_language)
|
||||||
index(assigns)
|
index(assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1346,7 +1346,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
|> assign_misc_editor()
|
|> assign_misc_editor()
|
||||||
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, UILocale.current())
|
||||||
|
|
||||||
defp encoded_shortcuts(shortcuts), do: Jason.encode!(shortcuts)
|
defp encoded_shortcuts(shortcuts), do: Jason.encode!(shortcuts)
|
||||||
|
|
||||||
|
|||||||
@@ -572,5 +572,5 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
defp format_error(reason), do: inspect(reason)
|
defp format_error(reason), do: inspect(reason)
|
||||||
|
|
||||||
def translated(text, bindings \\ %{}),
|
def translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -114,5 +114,5 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.MessageBuild do
|
|||||||
defp streaming_content(_request), do: ""
|
defp streaming_content(_request), do: ""
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -76,5 +76,5 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ModelSelection do
|
|||||||
defp blank?(nil), do: true
|
defp blank?(nil), do: true
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -270,5 +270,5 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
|
|||||||
defp truthy?(_value), do: false
|
defp truthy?(_value), do: false
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -229,5 +229,5 @@ defmodule BDS.Desktop.ShellLive.ChatSurface do
|
|||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do
|
|||||||
|
|
||||||
def build_template(_assigns), do: nil
|
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(nil), do: ""
|
||||||
def format_timestamp(timestamp), do: BDS.Persistence.timestamp_to_iso8601(timestamp)
|
def format_timestamp(timestamp), do: BDS.Persistence.timestamp_to_iso8601(timestamp)
|
||||||
|
|||||||
@@ -770,7 +770,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
|||||||
end
|
end
|
||||||
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 present?(value), do: value not in [nil, ""]
|
||||||
defp blank?(value), do: value in [nil, ""]
|
defp blank?(value), do: value in [nil, ""]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -243,6 +243,6 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
|
|||||||
|
|
||||||
def translate_phase(other), do: other
|
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, ""]
|
defp present?(value), do: value not in [nil, ""]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -242,5 +242,5 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
|
|||||||
|
|
||||||
def translate_execution_phase(other), do: other
|
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
|
end
|
||||||
|
|||||||
@@ -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, nil), do: opts
|
||||||
def maybe_put_option(opts, key, value), do: Keyword.put(opts, key, value)
|
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 present?(value), do: value not in [nil, ""]
|
||||||
defp blank_to_nil(""), do: nil
|
defp blank_to_nil(""), do: nil
|
||||||
defp blank_to_nil(value), do: value
|
defp blank_to_nil(value), do: value
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
|||||||
|
|
||||||
def build(_assigns), do: nil
|
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(:dirty), do: translated("Unsaved")
|
||||||
def media_editor_save_state_label(:saved), do: translated("Saved")
|
def media_editor_save_state_label(:saved), do: translated("Saved")
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def translated(text, bindings \\ %{}),
|
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
|
def row_label(item, category_titles) do
|
||||||
if item.kind == :category_archive do
|
if item.kind == :category_archive do
|
||||||
|
|||||||
@@ -128,5 +128,5 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -81,5 +81,5 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
|||||||
|
|
||||||
def build(_assigns), do: nil
|
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(:site_validation), do: "site-validation-view"
|
||||||
def misc_class(:metadata_diff), do: "metadata-diff-view"
|
def misc_class(:metadata_diff), do: "metadata-diff-view"
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|
|||||||
|
|
||||||
def markdown_link(text, url), do: "[#{text}](#{url})"
|
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: []}
|
def project_metadata(nil), do: %{main_language: "en", blog_languages: []}
|
||||||
|
|
||||||
|
|||||||
@@ -286,5 +286,5 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
|
|||||||
|
|
||||||
defp present?(value), do: value not in [nil, ""]
|
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
|
end
|
||||||
|
|||||||
@@ -500,7 +500,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
def post_editor_mode_label(:preview), do: translated("Preview")
|
def post_editor_mode_label(:preview), do: translated("Preview")
|
||||||
|
|
||||||
def translated(text, bindings \\ %{}),
|
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, %{})
|
defp assigned_project_metadata(assigns), do: Map.get(assigns, :project_metadata, %{})
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -101,5 +101,5 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -186,5 +186,5 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def translated(text, bindings \\ %{}),
|
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
|
defp current_settings_section(assigns) do
|
||||||
meta = current_tab_meta(assigns)
|
meta = current_tab_meta(assigns)
|
||||||
|
|||||||
@@ -199,5 +199,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -89,5 +89,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.EditorSettings do
|
|||||||
defp boolean_string(false), do: "false"
|
defp boolean_string(false), do: "false"
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -190,5 +190,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -96,5 +96,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -108,5 +108,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ProjectSettings do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -85,5 +85,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -99,5 +99,5 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.StyleEditor do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp translated(text, bindings \\ %{}),
|
defp translated(text, bindings \\ %{}),
|
||||||
do: ShellData.translate(text, bindings, Process.get(:bds_ui_locale))
|
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
|
|||||||
use Phoenix.Component
|
use Phoenix.Component
|
||||||
|
|
||||||
alias BDS.Desktop.ShellData
|
alias BDS.Desktop.ShellData
|
||||||
|
alias BDS.Desktop.UILocale
|
||||||
alias BDS.UI.Registry
|
alias BDS.UI.Registry
|
||||||
|
|
||||||
def sidebar_content(assigns) do
|
def sidebar_content(assigns) do
|
||||||
Process.put(:bds_ui_locale, assigns.page_language)
|
UILocale.put(assigns.page_language)
|
||||||
assigns = prepare_filter_assigns(assigns)
|
assigns = prepare_filter_assigns(assigns)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
@@ -462,7 +463,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents 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 template_sidebar?(sidebar_data), do: Map.get(sidebar_data, :title) == "Templates"
|
defp template_sidebar?(sidebar_data), do: Map.get(sidebar_data, :title) == "Templates"
|
||||||
|
|
||||||
|
|||||||
@@ -127,5 +127,5 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
|||||||
def action(:import), do: %{kind: "import", label: "sidebar.import.newDefinition"}
|
def action(:import), do: %{kind: "import", label: "sidebar.import.newDefinition"}
|
||||||
def action(_view), do: nil
|
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
|
end
|
||||||
|
|||||||
@@ -95,5 +95,5 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
|
|||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
|||||||
|
|
||||||
def build(_assigns), do: nil
|
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
|
def tag_font_size(count, counts) do
|
||||||
max_count = Enum.max([1 | Enum.map(counts, & &1.count)])
|
max_count = Enum.max([1 | Enum.map(counts, & &1.count)])
|
||||||
|
|||||||
65
lib/bds/desktop/ui_locale.ex
Normal file
65
lib/bds/desktop/ui_locale.ex
Normal 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
|
||||||
Reference in New Issue
Block a user