feat: complete change to gettext from homebrew i18n solution

This commit is contained in:
2026-05-03 22:28:25 +02:00
parent 4bee8cf1db
commit 4de8492c4f
96 changed files with 21579 additions and 1497 deletions

View File

@@ -7,6 +7,7 @@ defmodule BDS.Desktop.MenuBar do
alias BDS.UI.MenuBar, as: ShellMenuBar alias BDS.UI.MenuBar, as: ShellMenuBar
alias Desktop.OS alias Desktop.OS
alias Desktop.Window alias Desktop.Window
use Gettext, backend: BDS.Gettext
def groups(opts \\ []) do def groups(opts \\ []) do
opts opts
@@ -140,60 +141,58 @@ defmodule BDS.Desktop.MenuBar do
defp native_label(label, nil), do: label defp native_label(label, nil), do: label
defp native_label(label, shortcut), do: label <> "\t" <> shortcut defp native_label(label, shortcut), do: label <> "\t" <> shortcut
defp group_label(:file), do: translate("menuBar.file") defp group_label(:file), do: dgettext("ui", "File")
defp group_label(:edit), do: translate("menuBar.edit") defp group_label(:edit), do: dgettext("ui", "Edit")
defp group_label(:view), do: translate("menuBar.view") defp group_label(:view), do: dgettext("ui", "View")
defp group_label(:blog), do: translate("menuBar.blog") defp group_label(:blog), do: dgettext("ui", "Blog")
defp group_label(:help), do: translate("menuBar.help") defp group_label(:help), do: dgettext("ui", "Help")
defp item_label(:new_post), do: translate("menuBar.newPost") defp item_label(:new_post), do: dgettext("ui", "New Post")
defp item_label(:import_media), do: translate("menuBar.importMedia") defp item_label(:import_media), do: dgettext("ui", "Import Media")
defp item_label(:save), do: translate("menuBar.save") defp item_label(:save), do: dgettext("ui", "Save")
defp item_label(:open_in_browser), do: translate("menuBar.openInBrowser") defp item_label(:open_in_browser), do: dgettext("ui", "Open in Browser")
defp item_label(:open_data_folder), do: translate("menuBar.openDataFolder") defp item_label(:open_data_folder), do: dgettext("ui", "Open Data Folder")
defp item_label(:close_tab), do: translate("menuBar.closeTab") defp item_label(:close_tab), do: dgettext("ui", "Close Tab")
defp item_label(:quit), do: translate("menuBar.quit") defp item_label(:quit), do: dgettext("ui", "Quit")
defp item_label(:undo), do: translate("menuBar.undo") defp item_label(:undo), do: dgettext("ui", "Undo")
defp item_label(:redo), do: translate("menuBar.redo") defp item_label(:redo), do: dgettext("ui", "Redo")
defp item_label(:cut), do: translate("menuBar.cut") defp item_label(:cut), do: dgettext("ui", "Cut")
defp item_label(:copy), do: translate("menuBar.copy") defp item_label(:copy), do: dgettext("ui", "Copy")
defp item_label(:paste), do: translate("menuBar.paste") defp item_label(:paste), do: dgettext("ui", "Paste")
defp item_label(:delete), do: translate("menuBar.delete") defp item_label(:delete), do: dgettext("ui", "Delete")
defp item_label(:select_all), do: translate("menuBar.selectAll") defp item_label(:select_all), do: dgettext("ui", "Select All")
defp item_label(:find), do: translate("menuBar.find") defp item_label(:find), do: dgettext("ui", "Find")
defp item_label(:replace), do: translate("menuBar.replace") defp item_label(:replace), do: dgettext("ui", "Replace")
defp item_label(:edit_preferences), do: translate("menuBar.preferences") defp item_label(:edit_preferences), do: dgettext("ui", "Preferences")
defp item_label(:view_posts), do: translate("menuBar.viewPosts") defp item_label(:view_posts), do: dgettext("ui", "Posts")
defp item_label(:view_media), do: translate("menuBar.viewMedia") defp item_label(:view_media), do: dgettext("ui", "Media")
defp item_label(:toggle_sidebar), do: translate("menuBar.toggleSidebar") defp item_label(:toggle_sidebar), do: dgettext("ui", "Toggle Sidebar")
defp item_label(:toggle_panel), do: translate("menuBar.togglePanel") defp item_label(:toggle_panel), do: dgettext("ui", "Toggle Panel")
defp item_label(:toggle_assistant_sidebar), do: translate("menuBar.toggleAssistantSidebar") defp item_label(:toggle_assistant_sidebar), do: dgettext("ui", "Toggle Assistant Sidebar")
defp item_label(:toggle_dev_tools), do: translate("menuBar.toggleDevTools") defp item_label(:toggle_dev_tools), do: dgettext("ui", "Toggle Dev Tools")
defp item_label(:reload), do: translate("menuBar.reload") defp item_label(:reload), do: dgettext("ui", "Reload")
defp item_label(:force_reload), do: translate("menuBar.forceReload") defp item_label(:force_reload), do: dgettext("ui", "Force Reload")
defp item_label(:reset_zoom), do: translate("menuBar.resetZoom") defp item_label(:reset_zoom), do: dgettext("ui", "Reset Zoom")
defp item_label(:zoom_in), do: translate("menuBar.zoomIn") defp item_label(:zoom_in), do: dgettext("ui", "Zoom In")
defp item_label(:zoom_out), do: translate("menuBar.zoomOut") defp item_label(:zoom_out), do: dgettext("ui", "Zoom Out")
defp item_label(:toggle_full_screen), do: translate("menuBar.toggleFullScreen") defp item_label(:toggle_full_screen), do: dgettext("ui", "Toggle Full Screen")
defp item_label(:publish_selected), do: translate("menuBar.publishSelected") defp item_label(:publish_selected), do: dgettext("ui", "Publish Selected")
defp item_label(:preview_post), do: translate("menuBar.previewPost") defp item_label(:preview_post), do: dgettext("ui", "Preview Post")
defp item_label(:edit_menu), do: translate("menuBar.editMenu") defp item_label(:edit_menu), do: dgettext("ui", "Edit Menu")
defp item_label(:rebuild_database), do: translate("menuBar.rebuildDatabase") defp item_label(:rebuild_database), do: dgettext("ui", "Rebuild Database")
defp item_label(:reindex_text), do: translate("menuBar.reindexText") defp item_label(:reindex_text), do: dgettext("ui", "Reindex Text")
defp item_label(:rebuild_embedding_index), do: translate("menuBar.rebuildEmbeddingIndex") defp item_label(:rebuild_embedding_index), do: dgettext("ui", "Rebuild Embedding Index")
defp item_label(:metadata_diff), do: translate("menuBar.metadataDiff") defp item_label(:metadata_diff), do: dgettext("ui", "Metadata Diff")
defp item_label(:regenerate_calendar), do: translate("menuBar.regenerateCalendar") defp item_label(:regenerate_calendar), do: dgettext("ui", "Regenerate Calendar")
defp item_label(:validate_translations), do: translate("menuBar.validateTranslations") defp item_label(:validate_translations), do: dgettext("ui", "Validate Translations")
defp item_label(:fill_missing_translations), do: translate("menuBar.fillMissingTranslations") defp item_label(:fill_missing_translations), do: dgettext("ui", "Fill Missing Translations")
defp item_label(:find_duplicates), do: translate("menuBar.findDuplicates") defp item_label(:find_duplicates), do: dgettext("ui", "Find Duplicate Posts")
defp item_label(:generate_sitemap), do: translate("menuBar.generateSite") defp item_label(:generate_sitemap), do: dgettext("ui", "Generate Site")
defp item_label(:validate_site), do: translate("menuBar.validateSite") defp item_label(:validate_site), do: dgettext("ui", "Validate Site")
defp item_label(:upload_site), do: translate("menuBar.uploadSite") defp item_label(:upload_site), do: dgettext("ui", "Upload Site")
defp item_label(:about), do: translate("menuBar.about") defp item_label(:about), do: dgettext("ui", "About")
defp item_label(:documentation), do: translate("menuBar.documentation") defp item_label(:documentation), do: dgettext("ui", "Documentation")
defp item_label(:api_documentation), do: translate("menuBar.apiDocumentation") defp item_label(:api_documentation), do: dgettext("ui", "API Documentation")
defp item_label(:view_on_github), do: translate("menuBar.viewOnGithub") defp item_label(:view_on_github), do: dgettext("ui", "View on GitHub")
defp item_label(:report_issue), do: translate("menuBar.reportIssue") defp item_label(:report_issue), do: dgettext("ui", "Report Issue")
defp translate(text), do: ShellData.translate(text, %{}, UILocale.current())
end end

View File

@@ -1,6 +1,8 @@
defmodule BDS.Desktop.ShellData do defmodule BDS.Desktop.ShellData do
@moduledoc false @moduledoc false
use Gettext, backend: BDS.Gettext
alias BDS.Git alias BDS.Git
alias BDS.I18n alias BDS.I18n
alias BDS.Projects alias BDS.Projects
@@ -12,12 +14,24 @@ defmodule BDS.Desktop.ShellData do
Application.get_env(:bds, :desktop)[:title] || "Blogging Desktop Server" Application.get_env(:bds, :desktop)[:title] || "Blogging Desktop Server"
end end
def ui_language do def activity_icon(id) do
I18n.current_ui_locale() case to_string(id) do
"posts" -> ~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zM6 20V4h7v5h5v11H6z"></path><path d="M8 12h8v2H8zm0 4h8v2H8z"></path></svg>)
"pages" -> ~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 4h10v4h6v12H4V4zm10 1.5V9h4.5L14 5.5zM7 12h10v1.5H7V12zm0 3h10v1.5H7V15z"></path></svg>)
"media" -> ~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path></svg>)
"scripts" -> ~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M20 3H4a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h7v2H8v2h8v-2h-3v-2h7a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zM5 14V5h14v9H5zm2-7.5L9.5 9 7 11.5l1.4 1.4L12.3 9 8.4 5.1 7 6.5zm6.5 5.5h4v-2h-4v2z"></path></svg>)
"templates" -> ~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 4h7v7H4V4zm9 0h7v7h-7V4zM4 13h7v7H4v-7zm9 0h7v7h-7v-7zM5.5 5.5v4h4v-4h-4zm9 0v4h4v-4h-4zm-9 9v4h4v-4h-4zm9 0v4h4v-4h-4z"></path></svg>)
"tags" -> ~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58s1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41s-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z"></path></svg>)
"chat" -> ~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"></path><circle cx="8" cy="10" r="1.5"></circle><circle cx="12" cy="10" r="1.5"></circle><circle cx="16" cy="10" r="1.5"></circle></svg>)
"import" -> ~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path></svg>)
"git" -> ~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M22 11.73L12.27 2a1 1 0 0 0-1.41 0L8.84 4.02l2.56 2.56a1.2 1.2 0 0 1 1.52 1.53l2.47 2.47a1.2 1.2 0 1 1-.72.67l-2.3-2.3v6.06a1.2 1.2 0 1 1-.85 0V8.9a1.2 1.2 0 0 1-.66-1.59L8.35 4.8 2 11.16a1 1 0 0 0 0 1.41L11.73 22a1 1 0 0 0 1.41 0L22 13.14a1 1 0 0 0 0-1.41z"></path></svg>)
"settings" -> ~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"></path></svg>)
_other -> activity_icon("posts")
end
end end
def translations(locale \\ nil) do def ui_language do
I18n.get_ui_translations(effective_ui_language(locale)) I18n.current_ui_locale()
end end
def supported_ui_languages do def supported_ui_languages do
@@ -26,14 +40,6 @@ defmodule BDS.Desktop.ShellData do
end) end)
end end
def translate(key, bindings \\ %{}, locale \\ nil) do
text = Map.get(translations(locale), to_string(key), to_string(key))
Enum.reduce(bindings, text, fn {binding, value}, acc ->
String.replace(acc, "%{#{binding}}", to_string(value))
end)
end
def project_snapshot do def project_snapshot do
Projects.shell_snapshot() Projects.shell_snapshot()
rescue rescue
@@ -77,20 +83,20 @@ defmodule BDS.Desktop.ShellData do
def assistant_cards do def assistant_cards do
[ [
%{label: "Offline Gate", text: "Automatic AI actions stay gated by airplane mode."}, %{label: dgettext("ui", "Offline Gate"), text: dgettext("ui", "Automatic AI actions stay gated by airplane mode.")},
%{ %{
label: "Filesystem Sync", label: dgettext("ui", "Filesystem Sync"),
text: "Metadata flush, diffing, and rebuild hooks still need editor wiring." text: dgettext("ui", "Metadata flush, diffing, and rebuild hooks still need editor wiring.")
}, },
%{label: "Desktop Runtime", text: "The app window is now served from LiveView state."} %{label: dgettext("ui", "Desktop Runtime"), text: dgettext("ui", "The app window is now served from LiveView state.")}
] ]
end end
def editor_meta(task_status) do def editor_meta(task_status) do
[ [
%{label: "Status", value: task_status.running_task_message || "Idle"}, %{label: dgettext("ui", "Status"), value: task_status.running_task_message || dgettext("ui", "Idle")},
%{label: "Mode", value: "Offline"}, %{label: dgettext("ui", "Mode"), value: dgettext("ui", "Offline")},
%{label: "Main Language", value: ui_language()} %{label: dgettext("ui", "Main Language"), value: ui_language()}
] ]
end end
@@ -140,70 +146,18 @@ defmodule BDS.Desktop.ShellData do
|> Enum.uniq() |> Enum.uniq()
end end
defp git_remote_state_provider do
Application.get_env(:bds, :git_remote_state_provider, &Git.remote_state/2)
end
defp parse_positive_count(value) do
case Integer.parse(value) do
{count, _rest} when count > 0 -> count
_other -> 0
end
end
def activity_icon(id) do
case to_string(id) do
"posts" ->
~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zM6 20V4h7v5h5v11H6z"></path><path d="M8 12h8v2H8zm0 4h8v2H8z"></path></svg>)
"pages" ->
~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 4h10v4h6v12H4V4zm10 1.5V9h4.5L14 5.5zM7 12h10v1.5H7V12zm0 3h10v1.5H7V15z"></path></svg>)
"media" ->
~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path></svg>)
"scripts" ->
~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M20 3H4a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h7v2H8v2h8v-2h-3v-2h7a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zM5 14V5h14v9H5zm2-7.5L9.5 9 7 11.5l1.4 1.4L12.3 9 8.4 5.1 7 6.5zm6.5 5.5h4v-2h-4v2z"></path></svg>)
"templates" ->
~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 4h7v7H4V4zm9 0h7v7h-7V4zM4 13h7v7H4v-7zm9 0h7v7h-7v-7zM5.5 5.5v4h4v-4h-4zm9 0v4h4v-4h-4zm-9 9v4h4v-4h-4zm9 0v4h4v-4h-4z"></path></svg>)
"tags" ->
~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58s1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41s-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z"></path></svg>)
"chat" ->
~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"></path><circle cx="8" cy="10" r="1.5"></circle><circle cx="12" cy="10" r="1.5"></circle><circle cx="16" cy="10" r="1.5"></circle></svg>)
"import" ->
~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path></svg>)
"git" ->
~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M22 11.73L12.27 2a1 1 0 0 0-1.41 0L8.84 4.02l2.56 2.56a1.2 1.2 0 0 1 1.52 1.53l2.47 2.47a1.2 1.2 0 1 1-.72.67l-2.3-2.3v6.06a1.2 1.2 0 1 1-.85 0V8.9a1.2 1.2 0 0 1-.66-1.59L8.35 4.8 2 11.16a1 1 0 0 0 0 1.41L11.73 22a1 1 0 0 0 1.41 0L22 13.14a1 1 0 0 0 0-1.41z"></path></svg>)
"settings" ->
~s(<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"></path></svg>)
_other ->
activity_icon("posts")
end
end
def dashboard_status_label(status) do def dashboard_status_label(status) do
case to_string(status) do case to_string(status) do
"draft" -> translate("dashboard.status.draft") "draft" -> dgettext("ui", "Draft")
"published" -> translate("dashboard.status.published") "published" -> dgettext("ui", "Published")
"archived" -> translate("dashboard.status.archived") "archived" -> dgettext("ui", "Archived")
other -> other |> String.replace("_", " ") |> String.capitalize() other -> other |> String.replace("_", " ") |> String.capitalize()
end end
end end
def dashboard_post_count_label(count) do def dashboard_post_count_label(count) do
normalized_count = count || 0 normalized_count = count || 0
dngettext("ui", "%{count} post", "%{count} posts", normalized_count, count: normalized_count)
key =
if normalized_count == 1, do: "dashboard.postCount.one", else: "dashboard.postCount.other"
translate(key, %{count: normalized_count})
end end
def dashboard_tag_cloud_items(items) when is_list(items) do def dashboard_tag_cloud_items(items) when is_list(items) do
@@ -258,10 +212,10 @@ defmodule BDS.Desktop.ShellData do
def route_label(route) do def route_label(route) do
case to_string(route) do case to_string(route) do
"git_log" -> "git_log" ->
"Git Log" dgettext("ui", "Git Log")
"post_links" -> "post_links" ->
"Post Links" dgettext("ui", "Post Links")
other -> other ->
other other
@@ -288,11 +242,16 @@ defmodule BDS.Desktop.ShellData do
end end
end end
defp effective_ui_language(nil) do defp git_remote_state_provider do
BDS.Desktop.UILocale.current() || ui_language() Application.get_env(:bds, :git_remote_state_provider, &Git.remote_state/2)
end end
defp effective_ui_language(locale), do: locale defp parse_positive_count(value) do
case Integer.parse(value) do
{count, _rest} when count > 0 -> count
_other -> 0
end
end
defp maybe_add_panel_tab(tabs, :post, :post_links), do: tabs ++ [:post_links] defp maybe_add_panel_tab(tabs, :post, :post_links), do: tabs ++ [:post_links]

View File

@@ -57,6 +57,7 @@ defmodule BDS.Desktop.ShellLive do
alias BDS.UI.{Commands, MenuBar, Session, Workbench} alias BDS.UI.{Commands, MenuBar, Session, Workbench}
alias Desktop.OS alias Desktop.OS
alias BDS.Desktop.Shutdown alias BDS.Desktop.Shutdown
use Gettext, backend: BDS.Gettext
@refresh_interval 1_500 @refresh_interval 1_500
@output_entry_limit 20 @output_entry_limit 20
@@ -586,29 +587,26 @@ defmodule BDS.Desktop.ShellLive do
|> assign(:current_tab, current_tab(workbench)) |> assign(:current_tab, current_tab(workbench))
end end
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)
defp encoded_workbench_session(workbench), do: Jason.encode!(Session.serialize(workbench)) defp encoded_workbench_session(workbench), do: Jason.encode!(Session.serialize(workbench))
defp panel_tab_label(:tasks), do: translated("Tasks") defp panel_tab_label(:tasks), do: dgettext("ui", "Tasks")
defp panel_tab_label(:output), do: translated("Output") defp panel_tab_label(:output), do: dgettext("ui", "Output")
defp panel_tab_label(:git_log), do: translated("Git Log") defp panel_tab_label(:git_log), do: dgettext("ui", "Git Log")
defp panel_tab_label(tab), do: ShellData.route_label(tab) defp panel_tab_label(tab), do: ShellData.route_label(tab)
defp activity_label("AI Assistant"), do: "Chat" defp activity_label("AI Assistant"), do: dgettext("ui", "Chat")
defp activity_label("Source Control"), do: "Git" defp activity_label("Source Control"), do: dgettext("ui", "Git")
defp activity_label(label), do: translated(label) defp activity_label(label), do: label
defp active_sidebar_label(activity_buttons, active_view, sidebar_data) do defp active_sidebar_label(activity_buttons, active_view, sidebar_data) do
Enum.find_value(activity_buttons, translated(Map.get(sidebar_data, :title, "")), fn button -> Enum.find_value(activity_buttons, Map.get(sidebar_data, :title, ""), fn button ->
if button.id == active_view, do: activity_label(button.label), else: nil if button.id == active_view, do: activity_label(button.label), else: nil
end) end)
end end
defp sidebar_header_label(label), do: translated(label) defp sidebar_header_label(label), do: label
defp timeline_height(entry, entries) do defp timeline_height(entry, entries) do
max_count = max_count =
@@ -680,6 +678,8 @@ defmodule BDS.Desktop.ShellLive do
if normalized == socket.assigns.page_language do if normalized == socket.assigns.page_language do
socket socket
else else
UILocale.put(normalized)
socket socket
|> assign(:page_language, normalized) |> assign(:page_language, normalized)
|> reload_shell(socket.assigns.workbench) |> reload_shell(socket.assigns.workbench)

View File

@@ -6,9 +6,9 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
import Phoenix.HTML, only: [raw: 1] import Phoenix.HTML, only: [raw: 1]
alias BDS.{AI, BoundedAtoms, MapUtils, Persistence} alias BDS.{AI, BoundedAtoms, MapUtils, Persistence}
alias BDS.Desktop.ShellData
alias BDS.Desktop.ShellLive.ChatEditor.{MessageBuild, ModelSelection, ToolTracking} alias BDS.Desktop.ShellLive.ChatEditor.{MessageBuild, ModelSelection, ToolTracking}
alias BDS.Desktop.ShellLive.TabHelpers alias BDS.Desktop.ShellLive.TabHelpers
use Gettext, backend: BDS.Gettext
embed_templates("chat_editor_html/*") embed_templates("chat_editor_html/*")
@@ -72,7 +72,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
{:noreply, assign(socket, :model_selector_open?, false) |> build_data()} {:noreply, assign(socket, :model_selector_open?, false) |> build_data()}
{:error, reason} -> {:error, reason} ->
notify_parent({:chat_editor_output, translated("Chat"), inspect(reason), "error"}) notify_parent({:chat_editor_output, dgettext("ui", "Chat"), inspect(reason), "error"})
{:noreply, assign(socket, :model_selector_open?, false) |> build_data()} {:noreply, assign(socket, :model_selector_open?, false) |> build_data()}
end end
end end
@@ -196,8 +196,8 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
socket.assigns.offline_mode -> socket.assigns.offline_mode ->
notify_parent( notify_parent(
{:chat_editor_output, translated("Chat"), {:chat_editor_output, dgettext("ui", "Chat"),
translated("Automatic AI actions stay gated by airplane mode."), "info"} dgettext("ui", "Automatic AI actions stay gated by airplane mode."), "info"}
) )
build_data(socket) build_data(socket)
@@ -272,7 +272,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
assign(socket, :request, nil) |> build_data() assign(socket, :request, nil) |> build_data()
{:error, reason} -> {:error, reason} ->
notify_parent({:chat_editor_output, translated("Chat"), format_error(reason), "error"}) notify_parent({:chat_editor_output, dgettext("ui", "Chat"), format_error(reason), "error"})
assign(socket, :request, nil) |> build_data() assign(socket, :request, nil) |> build_data()
end end
end end
@@ -483,8 +483,8 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
# ── HEEx-callable helpers ───────────────────────────────────────────────── # ── HEEx-callable helpers ─────────────────────────────────────────────────
@spec message_role_label(atom()) :: String.t() @spec message_role_label(atom()) :: String.t()
def message_role_label(:user), do: translated("chat.role.you") def message_role_label(:user), do: dgettext("ui", "You")
def message_role_label(_role), do: translated("chat.role.assistant") def message_role_label(_role), do: dgettext("ui", "Assistant")
defdelegate tool_call_name(tool_call), to: ToolTracking defdelegate tool_call_name(tool_call), to: ToolTracking
defdelegate tool_call_arguments(tool_call), to: ToolTracking defdelegate tool_call_arguments(tool_call), to: ToolTracking
@@ -547,10 +547,10 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
<% end %> <% end %>
</summary> </summary>
<div class="chat-tool-marker-details" data-testid="chat-tool-marker-details"> <div class="chat-tool-marker-details" data-testid="chat-tool-marker-details">
<div class="chat-tool-marker-detail-label"><%= translated("chat.toolArguments") %></div> <div class="chat-tool-marker-detail-label"><%= dgettext("ui", "Arguments") %></div>
<pre><%= Jason.encode!(marker.arguments || %{}, pretty: true) %></pre> <pre><%= Jason.encode!(marker.arguments || %{}, pretty: true) %></pre>
<%= if marker.result not in [nil, ""] do %> <%= if marker.result not in [nil, ""] do %>
<div class="chat-tool-marker-detail-label"><%= translated("chat.toolResult") %></div> <div class="chat-tool-marker-detail-label"><%= dgettext("ui", "Result") %></div>
<pre><%= marker.result %></pre> <pre><%= marker.result %></pre>
<% end %> <% end %>
</div> </div>
@@ -571,7 +571,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
<summary class="chat-inline-surface-header"> <summary class="chat-inline-surface-header">
<span class="chat-inline-surface-icon"><%= surface_icon(@surface.type) %></span> <span class="chat-inline-surface-icon"><%= surface_icon(@surface.type) %></span>
<span class="chat-inline-surface-title"><%= surface_title(@surface) %></span> <span class="chat-inline-surface-title"><%= surface_title(@surface) %></span>
<button class="chat-inline-surface-dismiss" type="button" phx-click="dismiss_chat_surface" phx-target={@myself} phx-value-surface-id={@surface.id} aria-label={translated("chat.dismissSurface")} data-testid="chat-inline-surface-dismiss">×</button> <button class="chat-inline-surface-dismiss" type="button" phx-click="dismiss_chat_surface" phx-target={@myself} phx-value-surface-id={@surface.id} aria-label={dgettext("ui", "Dismiss surface")} data-testid="chat-inline-surface-dismiss">×</button>
</summary> </summary>
<div class="chat-inline-surface-body"> <div class="chat-inline-surface-body">
<%= case @surface.type do %> <%= case @surface.type do %>
@@ -848,7 +848,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
defp present?(value), do: not is_nil(value) defp present?(value), do: not is_nil(value)
defp format_error(%{kind: :endpoint_not_configured}), defp format_error(%{kind: :endpoint_not_configured}),
do: translated("chat.apiKeyRequiredDescription") do: dgettext("ui", "Configure an API key in Settings to enable AI chat.")
defp format_error(reason), do: inspect(reason) defp format_error(reason), do: inspect(reason)
@@ -860,7 +860,4 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
:error -> 0 :error -> 0
end end
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -3,8 +3,8 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.MessageBuild do
alias BDS.AI alias BDS.AI
alias BDS.AI.ChatConversation alias BDS.AI.ChatConversation
alias BDS.Desktop.ShellData
alias BDS.Desktop.ShellLive.ChatEditor.{ModelSelection, ToolSurfaces, ToolTracking} alias BDS.Desktop.ShellLive.ChatEditor.{ModelSelection, ToolSurfaces, ToolTracking}
use Gettext, backend: BDS.Gettext
@spec build(term()) :: term() @spec build(term()) :: term()
def build(%{current_tab: %{type: :chat, id: conversation_id}} = assigns) do def build(%{current_tab: %{type: :chat, id: conversation_id}} = assigns) do
@@ -22,7 +22,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.MessageBuild do
%{ %{
id: conversation.id, id: conversation.id,
title: conversation.title || translated("chat.newChat"), title: conversation.title || dgettext("ui", "New Chat"),
model: conversation.model, model: conversation.model,
effective_model: effective_model, effective_model: effective_model,
available_models: available_models, available_models: available_models,
@@ -268,7 +268,4 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.MessageBuild do
defp request_started_at(%{started_at: started_at}) when is_integer(started_at), do: started_at defp request_started_at(%{started_at: started_at}) when is_integer(started_at), do: started_at
defp request_started_at(_request), do: nil defp request_started_at(_request), do: nil
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -2,9 +2,9 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ModelSelection do
@moduledoc false @moduledoc false
alias BDS.AI alias BDS.AI
alias BDS.Desktop.ShellData
import Phoenix.Component, only: [assign: 3] import Phoenix.Component, only: [assign: 3]
use Gettext, backend: BDS.Gettext
@spec toggle_model_selector(term(), term()) :: term() @spec toggle_model_selector(term(), term()) :: term()
def toggle_model_selector(socket, reload) do def toggle_model_selector(socket, reload) do
@@ -34,7 +34,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ModelSelection do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("Chat"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Chat"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -78,7 +78,4 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ModelSelection do
defp blank?(value) when is_binary(value), do: String.trim(value) == "" defp blank?(value) when is_binary(value), do: String.trim(value) == ""
defp blank?(nil), do: true defp blank?(nil), do: true
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -1,7 +1,7 @@
defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
@moduledoc false @moduledoc false
alias BDS.Desktop.ShellData use Gettext, backend: BDS.Gettext
@render_tool_names MapSet.new([ @render_tool_names MapSet.new([
"render_card", "render_card",
@@ -85,7 +85,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
|> List.wrap() |> List.wrap()
|> Enum.map(fn entry -> |> Enum.map(fn entry ->
%{ %{
label: map_value(entry, "label", translated("chat.role.assistant")), label: map_value(entry, "label", dgettext("ui", "Assistant")),
value: numeric_value(map_value(entry, "value", 0)), value: numeric_value(map_value(entry, "value", 0)),
segments: List.wrap(map_value(entry, "segments", [])) segments: List.wrap(map_value(entry, "segments", []))
} }
@@ -173,7 +173,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
fields: fields, fields: fields,
submit_label: submit_label:
map_value(arguments, "submitLabel") || map_value(arguments, "submitLabel") ||
map_value(arguments, "submit_label", translated("chat.stop")), map_value(arguments, "submit_label", dgettext("ui", "Stop")),
submit_action: submit_action:
map_value(arguments, "submitAction") || map_value(arguments, "submitAction") ||
map_value(arguments, "submit_action", "submitForm") map_value(arguments, "submit_action", "submitForm")
@@ -249,7 +249,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
defp decode_surface_actions(actions) when is_list(actions) do defp decode_surface_actions(actions) when is_list(actions) do
Enum.map(actions, fn action -> Enum.map(actions, fn action ->
%{ %{
label: map_value(action, "label", translated("chat.openSettings")), label: map_value(action, "label", dgettext("ui", "Open Settings")),
action: map_value(action, "action", "openSettings"), action: map_value(action, "action", "openSettings"),
payload: map_value(action, "payload", %{}) payload: map_value(action, "payload", %{})
} }
@@ -296,7 +296,4 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
defp truthy?(value) when value in [true, "true", 1, "1", "on"], do: true defp truthy?(value) when value in [true, "true", 1, "1", "on"], do: true
defp truthy?(_value), do: false defp truthy?(_value), do: false
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -3,7 +3,7 @@
<div class="chat-panel-title"> <div class="chat-panel-title">
<span class="chat-panel-title-main"> <span class="chat-panel-title-main">
<%= if @chat_editor.needs_api_key? do %> <%= if @chat_editor.needs_api_key? do %>
<%= translated("chat.setupTitle") %> <%= dgettext("ui", "AI Chat Setup") %>
<% else %> <% else %>
<%= @chat_editor.title %> <%= @chat_editor.title %>
<% end %> <% end %>
@@ -18,7 +18,7 @@
phx-target={@myself} phx-target={@myself}
data-testid="chat-model-selector-button" data-testid="chat-model-selector-button"
> >
<span><%= @chat_editor.effective_model || translated("chat.modelUnavailable") %></span> <span><%= @chat_editor.effective_model || dgettext("ui", "No model") %></span>
<span class="chat-model-selector-caret">▾</span> <span class="chat-model-selector-caret">▾</span>
</button> </button>
@@ -59,24 +59,24 @@
<%= if @chat_editor.needs_api_key? do %> <%= if @chat_editor.needs_api_key? do %>
<div class="chat-welcome chat-api-key-state" data-testid="chat-api-key-required"> <div class="chat-welcome chat-api-key-state" data-testid="chat-api-key-required">
<div class="chat-welcome-icon">🔑</div> <div class="chat-welcome-icon">🔑</div>
<h2><%= translated("chat.apiKeyRequiredTitle") %></h2> <h2><%= dgettext("ui", "API Key Required") %></h2>
<p><%= translated("chat.apiKeyRequiredDescription") %></p> <p><%= dgettext("ui", "Configure an API key in Settings to enable AI chat.") %></p>
<div class="api-key-form"> <div class="api-key-form">
<button class="api-key-submit" type="button" phx-click="open_chat_settings" phx-target={@myself}><%= translated("chat.openSettings") %></button> <button class="api-key-submit" type="button" phx-click="open_chat_settings" phx-target={@myself}><%= dgettext("ui", "Open Settings") %></button>
</div> </div>
</div> </div>
<% else %> <% else %>
<%= if Enum.empty?(@chat_editor.messages) and not @chat_editor.is_streaming do %> <%= if Enum.empty?(@chat_editor.messages) and not @chat_editor.is_streaming do %>
<div class="chat-welcome"> <div class="chat-welcome">
<div class="chat-welcome-icon">🤖</div> <div class="chat-welcome-icon">🤖</div>
<h2><%= translated("chat.welcomeTitle") %></h2> <h2><%= dgettext("ui", "Welcome to the AI Assistant") %></h2>
<p><%= translated("chat.welcomeDescription") %></p> <p><%= dgettext("ui", "I can help you manage your blog with rich visualizations. Try asking me to:") %></p>
<ul> <ul>
<li><%= translated("chat.welcomeTipSearch") %></li> <li><%= dgettext("ui", "Search for posts about a specific topic") %></li>
<li><%= translated("chat.welcomeTipChart") %></li> <li><%= dgettext("ui", "Show a chart of posts published per month") %></li>
<li><%= translated("chat.welcomeTipTable") %></li> <li><%= dgettext("ui", "Compare my recent posts in a table") %></li>
<li><%= translated("chat.welcomeTipMetadata") %></li> <li><%= dgettext("ui", "Update metadata for posts or media") %></li>
<li><%= translated("chat.welcomeTipTabs") %></li> <li><%= dgettext("ui", "Show post statistics by year in tabs with charts") %></li>
</ul> </ul>
</div> </div>
<% else %> <% else %>
@@ -149,11 +149,11 @@
<%= unless @chat_editor.needs_api_key? do %> <%= unless @chat_editor.needs_api_key? do %>
<div class="chat-input-container" data-testid="chat-input-container"> <div class="chat-input-container" data-testid="chat-input-container">
<%= if @chat_editor.is_streaming do %> <%= if @chat_editor.is_streaming do %>
<button class="chat-abort-button" data-testid="chat-abort-button" type="button" phx-click="abort_chat_editor_message" phx-target={@myself}>◼ <%= translated("chat.stop") %></button> <button class="chat-abort-button" data-testid="chat-abort-button" type="button" phx-click="abort_chat_editor_message" phx-target={@myself}>◼ <%= dgettext("ui", "Stop") %></button>
<% end %> <% end %>
<form class="chat-input-wrapper" phx-change="change_chat_editor_input" phx-submit="send_chat_editor_message" phx-target={@myself}> <form class="chat-input-wrapper" phx-change="change_chat_editor_input" phx-submit="send_chat_editor_message" phx-target={@myself}>
<textarea class="chat-input chat-surface-input" name="message" rows="1" placeholder={translated("chat.inputPlaceholder")} disabled={@chat_editor.is_streaming}><%= @chat_editor.input %></textarea> <textarea class="chat-input chat-surface-input" name="message" rows="1" placeholder={dgettext("ui", "Type a message...")} disabled={@chat_editor.is_streaming}><%= @chat_editor.input %></textarea>
<button class="chat-send-button" data-testid="chat-send-button" type="button" phx-click="send_chat_editor_message" phx-target={@myself} disabled={@chat_editor.send_disabled?}>↑</button> <button class="chat-send-button" data-testid="chat-send-button" type="button" phx-click="send_chat_editor_message" phx-target={@myself} disabled={@chat_editor.send_disabled?}>↑</button>
</form> </form>

View File

@@ -3,7 +3,7 @@ defmodule BDS.Desktop.ShellLive.ChatSurface do
import Phoenix.Component, only: [assign: 3] import Phoenix.Component, only: [assign: 3]
alias BDS.Desktop.ShellData use Gettext, backend: BDS.Gettext
def assistant_turn(prompt, socket) do def assistant_turn(prompt, socket) do
[ [
@@ -12,12 +12,12 @@ defmodule BDS.Desktop.ShellLive.ChatSurface do
] ]
end end
def assistant_project_name(nil), do: translated("Projects") def assistant_project_name(nil), do: dgettext("ui", "Projects")
def assistant_project_name(project), do: project.name def assistant_project_name(project), do: project.name
def assistant_message_label("assistant"), do: translated("Assistant") def assistant_message_label("assistant"), do: dgettext("ui", "Assistant")
def assistant_message_label("user"), do: translated("You") def assistant_message_label("user"), do: dgettext("ui", "You")
def assistant_message_label(_role), do: translated("Assistant") def assistant_message_label(_role), do: dgettext("ui", "Assistant")
def assistant_message_testid(role), do: "assistant-message-#{role}" def assistant_message_testid(role), do: "assistant-message-#{role}"
@@ -30,19 +30,9 @@ defmodule BDS.Desktop.ShellLive.ChatSurface do
defp assistant_reply(socket) do defp assistant_reply(socket) do
if socket.assigns.offline_mode do if socket.assigns.offline_mode do
ShellData.translate( BDS.Gettext.lgettext(socket.assigns.page_language, "ui", "Automatic AI actions stay gated by airplane mode.")
"Automatic AI actions stay gated by airplane mode.",
%{},
socket.assigns.page_language
)
else else
ShellData.translate( BDS.Gettext.lgettext(socket.assigns.page_language, "ui", "The assistant sidebar chat surface is ready, but model execution is not connected yet.")
"The assistant sidebar chat surface is ready, but model execution is not connected yet.",
%{},
socket.assigns.page_language
)
end end
end end
defp translated(text), do: ShellData.translate(text, %{}, BDS.Desktop.UILocale.current())
end end

View File

@@ -30,6 +30,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
translate_execution_phase: 1 translate_execution_phase: 1
] ]
use Gettext, backend: BDS.Gettext
import TaxonomyEditing, import TaxonomyEditing,
only: [ only: [
existing_taxonomy_terms: 1, existing_taxonomy_terms: 1,
@@ -162,7 +163,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
definition_id = socket.assigns.definition_id definition_id = socket.assigns.definition_id
socket = socket =
case FolderPicker.choose_directory(translated("importAnalysis.uploadsFolder")) do case FolderPicker.choose_directory(dgettext("ui", "Uploads Folder")) do
{:ok, uploads_folder_path} -> {:ok, uploads_folder_path} ->
{:ok, _definition} = {:ok, _definition} =
ImportDefinitions.update_definition(definition_id, %{ ImportDefinitions.update_definition(definition_id, %{
@@ -175,7 +176,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
build_data(socket) build_data(socket)
{:error, %{message: message}} -> {:error, %{message: message}} ->
notify_output(translated("activity.import"), message, "error") notify_output(dgettext("ui", "Import"), message, "error")
build_data(socket) build_data(socket)
end end
@@ -187,7 +188,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
project_id = socket.assigns.project_id project_id = socket.assigns.project_id
socket = socket =
case FilePicker.choose_file(translated("importAnalysis.wxrFile")) do case FilePicker.choose_file(dgettext("ui", "WXR File")) do
{:ok, wxr_file_path} -> {:ok, wxr_file_path} ->
{:ok, definition} = {:ok, definition} =
ImportDefinitions.update_definition(definition_id, %{ ImportDefinitions.update_definition(definition_id, %{
@@ -214,7 +215,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
socket socket
|> assign(:analysis_state, %{ |> assign(:analysis_state, %{
loading: true, loading: true,
step: translated("importAnalysis.analyzingWxr"), step: dgettext("ui", "Analyzing WXR file..."),
detail: Path.basename(wxr_file_path), detail: Path.basename(wxr_file_path),
file_path: wxr_file_path, file_path: wxr_file_path,
ref: task.ref ref: task.ref
@@ -226,7 +227,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
build_data(socket) build_data(socket)
{:error, %{message: message}} -> {:error, %{message: message}} ->
notify_output(translated("activity.import"), message, "error") notify_output(dgettext("ui", "Import"), message, "error")
build_data(socket) build_data(socket)
end end
@@ -430,12 +431,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
%{} = report <- ImportDefinitions.decode_analysis_result(definition) do %{} = report <- ImportDefinitions.decode_analysis_result(definition) do
if socket.assigns.offline_mode? do if socket.assigns.offline_mode? do
notify_output( notify_output(
translated("activity.import"), dgettext("ui", "Import"),
ShellData.translate( BDS.Gettext.lgettext(socket.assigns[:page_language] || ShellData.ui_language(), "ui", "Automatic AI actions stay gated by airplane mode."),
"Automatic AI actions stay gated by airplane mode.",
%{},
socket.assigns[:page_language] || ShellData.ui_language()
),
"info" "info"
) )
@@ -467,15 +464,15 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
mapped_count = TaxonomyEditing.auto_mapped_count(report, updated_report) mapped_count = TaxonomyEditing.auto_mapped_count(report, updated_report)
notify_output( notify_output(
translated("activity.import"), dgettext("ui", "Import"),
translated("importAnalysis.mappedCount", %{count: mapped_count}), dgettext("ui", "%{count} mapped", count: mapped_count),
"info" "info"
) )
build_data(socket) build_data(socket)
{:error, reason} -> {:error, reason} ->
notify_output(translated("activity.import"), inspect(reason), "error") notify_output(dgettext("ui", "Import"), inspect(reason), "error")
build_data(socket) build_data(socket)
end end
end end
@@ -552,7 +549,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
socket socket
|> assign(:analysis_state, default_analysis_state()) |> assign(:analysis_state, default_analysis_state())
|> notify_output(translated("activity.import"), message, "error") |> notify_output(dgettext("ui", "Import"), message, "error")
match?(%{ref: ^ref}, socket.assigns.execution_state) and reason not in [:normal, :shutdown] -> match?(%{ref: ^ref}, socket.assigns.execution_state) and reason not in [:normal, :shutdown] ->
message = if is_binary(reason), do: reason, else: inspect(reason) message = if is_binary(reason), do: reason, else: inspect(reason)
@@ -567,7 +564,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
ref: nil ref: nil
}) })
) )
|> notify_output(translated("activity.import"), message, "error") |> notify_output(dgettext("ui", "Import"), message, "error")
true -> true ->
socket socket
@@ -630,11 +627,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
end end
defp maybe_update_tab_meta(socket, name) do defp maybe_update_tab_meta(socket, name) do
title = name || translated("importAnalysis.untitledImport") title = name || dgettext("ui", "Untitled Import")
notify_parent( notify_parent(
{:import_editor_tab_meta, socket.assigns.definition_id, title, {:import_editor_tab_meta, socket.assigns.definition_id, title,
translated("importAnalysis.headerDescription")} dgettext("ui", "Select a WordPress export file (WXR) and an uploads folder to analyze what would be imported.")}
) )
socket socket
@@ -653,7 +650,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
end end
end end
defp selected_model_label(nil, []), do: translated("importAnalysis.analyzeWith") defp selected_model_label(nil, []), do: dgettext("ui", "Analyze with...")
defp selected_model_label(nil, [model | _rest]), do: model.name || model.id defp selected_model_label(nil, [model | _rest]), do: model.name || model.id
defp selected_model_label(model_id, available_models) do defp selected_model_label(model_id, available_models) do
@@ -685,14 +682,14 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
socket socket
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("activity.import"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Import"), inspect(reason), "error")
end end
{:error, %{message: message}} -> {:error, %{message: message}} ->
notify_output(socket, translated("activity.import"), message, "error") notify_output(socket, dgettext("ui", "Import"), message, "error")
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("activity.import"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Import"), inspect(reason), "error")
end end
end end
@@ -713,8 +710,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
ref: nil ref: nil
}) })
|> notify_output( |> notify_output(
translated("activity.import"), dgettext("ui", "Import"),
translated("importAnalysis.importComplete", %{count: previous_state.count}), dgettext("ui", "Import completed successfully!", count: previous_state.count),
"info" "info"
) )
@@ -727,7 +724,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
error: message, error: message,
ref: nil ref: nil
}) })
|> notify_output(translated("activity.import"), message, "error") |> notify_output(dgettext("ui", "Import"), message, "error")
{:error, reason} -> {:error, reason} ->
message = inspect(reason) message = inspect(reason)
@@ -740,7 +737,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
error: message, error: message,
ref: nil ref: nil
}) })
|> notify_output(translated("activity.import"), message, "error") |> notify_output(dgettext("ui", "Import"), message, "error")
end end
# Allow DB connections to settle before rebuilding # Allow DB connections to settle before rebuilding
@@ -812,27 +809,27 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
class="import-definition-name" class="import-definition-name"
type="text" type="text"
name="import_definition[name]" name="import_definition[name]"
value={@import_editor.definition_name || translated("importAnalysis.untitledImport")} value={@import_editor.definition_name || dgettext("ui", "Untitled Import")}
placeholder={translated("importAnalysis.namePlaceholder")} placeholder={dgettext("ui", "Import name...")}
/> />
<p><%= translated("importAnalysis.headerDescription") %></p> <p><%= dgettext("ui", "Select a WordPress export file (WXR) and an uploads folder to analyze what would be imported.") %></p>
</form> </form>
<div class="import-file-selectors"> <div class="import-file-selectors">
<div class="import-file-row"> <div class="import-file-row">
<label><%= translated("importAnalysis.uploadsFolder") %></label> <label><%= dgettext("ui", "Uploads Folder") %></label>
<div class={["import-file-path", if(blank?(@import_editor.uploads_folder_path), do: "placeholder")]}> <div class={["import-file-path", if(blank?(@import_editor.uploads_folder_path), do: "placeholder")]}>
<%= @import_editor.uploads_folder_path || translated("importAnalysis.noFolderSelected") %> <%= @import_editor.uploads_folder_path || dgettext("ui", "No folder selected") %>
</div> </div>
<button type="button" phx-click="select_import_uploads_folder" phx-target={@myself}><%= translated("Open") %></button> <button type="button" phx-click="select_import_uploads_folder" phx-target={@myself}><%= dgettext("ui", "Open") %></button>
</div> </div>
<div class="import-file-row"> <div class="import-file-row">
<label><%= translated("importAnalysis.wxrFile") %></label> <label><%= dgettext("ui", "WXR File") %></label>
<div class={["import-file-path", if(blank?(@import_editor.wxr_file_path), do: "placeholder")]}> <div class={["import-file-path", if(blank?(@import_editor.wxr_file_path), do: "placeholder")]}>
<%= @import_editor.wxr_file_path || translated("importAnalysis.selectFileToAnalyze") %> <%= @import_editor.wxr_file_path || dgettext("ui", "Select a file to analyze") %>
</div> </div>
<button class="import-analyze-btn" type="button" phx-click="select_import_wxr_file" phx-target={@myself}><%= translated("importAnalysis.selectAndAnalyze") %></button> <button class="import-analyze-btn" type="button" phx-click="select_import_wxr_file" phx-target={@myself}><%= dgettext("ui", "Select & Analyze") %></button>
</div> </div>
</div> </div>
@@ -840,7 +837,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<div class="import-loading"> <div class="import-loading">
<div class="import-spinner"></div> <div class="import-spinner"></div>
<div class="import-progress"> <div class="import-progress">
<div class="import-progress-step"><%= @analysis_state.step || translated("importAnalysis.analyzingWxr") %></div> <div class="import-progress-step"><%= @analysis_state.step || dgettext("ui", "Analyzing WXR file...") %></div>
<%= if present?(@analysis_state.detail) do %> <%= if present?(@analysis_state.detail) do %>
<div class="import-progress-detail"><%= @analysis_state.detail %></div> <div class="import-progress-detail"><%= @analysis_state.detail %></div>
<% end %> <% end %>
@@ -851,37 +848,37 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= if not is_nil(@report) and not @import_editor.is_loading do %> <%= if not is_nil(@report) and not @import_editor.is_loading do %>
<div class="import-site-info"> <div class="import-site-info">
<div class="import-site-info-item"> <div class="import-site-info-item">
<span class="info-label"><%= translated("importAnalysis.site") %></span> <span class="info-label"><%= dgettext("ui", "Site") %></span>
<span class="info-value"><%= get_in(@report, [:site_info, :title]) || translated("importAnalysis.untitled") %></span> <span class="info-value"><%= get_in(@report, [:site_info, :title]) || dgettext("ui", "Untitled") %></span>
</div> </div>
<div class="import-site-info-item"> <div class="import-site-info-item">
<span class="info-label"><%= translated("importAnalysis.url") %></span> <span class="info-label"><%= dgettext("ui", "URL") %></span>
<span class="info-value"><%= get_in(@report, [:site_info, :url]) || translated("importAnalysis.notAvailable") %></span> <span class="info-value"><%= get_in(@report, [:site_info, :url]) || dgettext("ui", "N/A") %></span>
</div> </div>
<div class="import-site-info-item"> <div class="import-site-info-item">
<span class="info-label"><%= translated("importAnalysis.language") %></span> <span class="info-label"><%= dgettext("ui", "Language") %></span>
<span class="info-value"><%= get_in(@report, [:site_info, :language]) || translated("importAnalysis.notAvailable") %></span> <span class="info-value"><%= get_in(@report, [:site_info, :language]) || dgettext("ui", "N/A") %></span>
</div> </div>
<div class="import-site-info-item"> <div class="import-site-info-item">
<span class="info-label"><%= translated("importAnalysis.file") %></span> <span class="info-label"><%= dgettext("ui", "File") %></span>
<span class="info-value"><%= @import_editor.wxr_file_path |> to_string() |> Path.basename() %></span> <span class="info-value"><%= @import_editor.wxr_file_path |> to_string() |> Path.basename() %></span>
</div> </div>
</div> </div>
<div class="import-stat-cards"> <div class="import-stat-cards">
<.stat_card label={translated("importAnalysis.posts")} stats={@report.post_stats} /> <.stat_card label={dgettext("ui", "posts")} stats={@report.post_stats} />
<%= if Map.get(@report, :other_stats) && Map.get(@report.other_stats, :total, 0) > 0 do %> <%= if Map.get(@report, :other_stats) && Map.get(@report.other_stats, :total, 0) > 0 do %>
<.other_stat_card label={translated("importAnalysis.other")} stats={@report.other_stats} /> <.other_stat_card label={dgettext("ui", "Other")} stats={@report.other_stats} />
<% end %> <% end %>
<.stat_card label={translated("importAnalysis.pages")} stats={@report.page_stats} /> <.stat_card label={dgettext("ui", "pages")} stats={@report.page_stats} />
<.media_stat_card label={translated("importAnalysis.media")} stats={@report.media_stats} /> <.media_stat_card label={dgettext("ui", "media")} stats={@report.media_stats} />
<.taxonomy_stat_card label={translated("importAnalysis.categories")} stats={@report.category_stats} /> <.taxonomy_stat_card label={dgettext("ui", "Categories")} stats={@report.category_stats} />
<.taxonomy_stat_card label={translated("importAnalysis.tags")} stats={@report.tag_stats} /> <.taxonomy_stat_card label={dgettext("ui", "Tags")} stats={@report.tag_stats} />
</div> </div>
<%= if Enum.any?(Map.get(@report, :date_distribution, [])) do %> <%= if Enum.any?(Map.get(@report, :date_distribution, [])) do %>
<div class="import-date-distribution"> <div class="import-date-distribution">
<h3><%= translated("importAnalysis.dateDistribution") %></h3> <h3><%= dgettext("ui", "Date Distribution") %></h3>
<div class="distribution-bars"> <div class="distribution-bars">
<%= for row <- @report.date_distribution do %> <%= for row <- @report.date_distribution do %>
<div class="distribution-row"> <div class="distribution-row">
@@ -900,13 +897,13 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= if @execution_state.is_executing do %> <%= if @execution_state.is_executing do %>
<div class="import-execution-progress"> <div class="import-execution-progress">
<div class="import-execution-header"> <div class="import-execution-header">
<h3><%= translated("importAnalysis.importing") %></h3> <h3><%= dgettext("ui", "Importing...") %></h3>
</div> </div>
<div class="import-progress-bar"> <div class="import-progress-bar">
<div class="import-progress-fill" style={"width: #{execution_progress_width(@execution_state)}%;"}></div> <div class="import-progress-fill" style={"width: #{execution_progress_width(@execution_state)}%;"}></div>
</div> </div>
<div class="import-progress-info"> <div class="import-progress-info">
<span class="import-phase"><%= @execution_state.phase || translated("importAnalysis.executionStarting") %></span> <span class="import-phase"><%= @execution_state.phase || dgettext("ui", "Starting...") %></span>
<%= if present?(@execution_state.detail) do %> <%= if present?(@execution_state.detail) do %>
<span class="import-detail"><%= @execution_state.detail %></span> <span class="import-detail"><%= @execution_state.detail %></span>
<% end %> <% end %>
@@ -921,18 +918,18 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= if not @execution_state.is_executing and not @execution_state.completed do %> <%= if not @execution_state.is_executing and not @execution_state.completed do %>
<div class="import-execute-section"> <div class="import-execute-section">
<div class="import-execute-summary"> <div class="import-execute-summary">
<%= translated("importAnalysis.readyToImport") %> <%= dgettext("ui", "Ready to import:") %>
<%= if @counts.tags > 0 do %><span class="import-count-tag"><%= @counts.tags %> <%= translated("importAnalysis.tagsCategories") %></span><% end %> <%= if @counts.tags > 0 do %><span class="import-count-tag"><%= @counts.tags %> <%= dgettext("ui", "tags/categories") %></span><% end %>
<%= if @counts.posts > 0 do %><span class="import-count-tag"><%= @counts.posts %> <%= translated("importAnalysis.posts") %></span><% end %> <%= if @counts.posts > 0 do %><span class="import-count-tag"><%= @counts.posts %> <%= dgettext("ui", "posts") %></span><% end %>
<%= if @counts.media > 0 do %><span class="import-count-tag"><%= @counts.media %> <%= translated("importAnalysis.media") %></span><% end %> <%= if @counts.media > 0 do %><span class="import-count-tag"><%= @counts.media %> <%= dgettext("ui", "media") %></span><% end %>
<%= if @counts.pages > 0 do %><span class="import-count-tag"><%= @counts.pages %> <%= translated("importAnalysis.pages") %></span><% end %> <%= if @counts.pages > 0 do %><span class="import-count-tag"><%= @counts.pages %> <%= dgettext("ui", "pages") %></span><% end %>
</div> </div>
<button class="import-execute-btn" type="button" phx-click="execute_import_editor" phx-target={@myself} disabled={@counts.total == 0}> <button class="import-execute-btn" type="button" phx-click="execute_import_editor" phx-target={@myself} disabled={@counts.total == 0}>
<%= if @counts.total == 0 do %> <%= if @counts.total == 0 do %>
<%= translated("importAnalysis.nothingToImport") %> <%= dgettext("ui", "Nothing to Import") %>
<% else %> <% else %>
<%= translated("importAnalysis.importItems", %{count: @counts.total}) %> <%= dgettext("ui", "Import %{count} Items", count: @counts.total) %>
<% end %> <% end %>
</button> </button>
</div> </div>
@@ -940,56 +937,56 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= if @execution_state.completed do %> <%= if @execution_state.completed do %>
<div class="import-execution-complete"> <div class="import-execution-complete">
<span><%= translated("importAnalysis.importComplete", %{count: @execution_state.count || @counts.total}) %></span> <span><%= dgettext("ui", "Import completed successfully!", count: @execution_state.count || @counts.total) %></span>
</div> </div>
<% end %> <% end %>
<%= if present?(@execution_state.error) do %> <%= if present?(@execution_state.error) do %>
<div class="import-execution-error"> <div class="import-execution-error">
<span><%= translated("importAnalysis.importFailed", %{error: @execution_state.error}) %></span> <span><%= dgettext("ui", "Import failed: %{error}", error: @execution_state.error) %></span>
</div> </div>
<% end %> <% end %>
<%= if Enum.any?(@post_conflicts) do %> <%= if Enum.any?(@post_conflicts) do %>
<.conflict_section title={translated("importAnalysis.postSlugConflicts")} items={@post_conflicts} expanded={@sections.post_conflicts} section="post_conflicts" myself={@myself} /> <.conflict_section title={dgettext("ui", "Post Slug Conflicts")} items={@post_conflicts} expanded={@sections.post_conflicts} section="post_conflicts" myself={@myself} />
<% end %> <% end %>
<%= if Enum.any?(@page_conflicts) do %> <%= if Enum.any?(@page_conflicts) do %>
<.conflict_section title={translated("importAnalysis.pageSlugConflicts")} items={@page_conflicts} expanded={@sections.page_conflicts} section="page_conflicts" myself={@myself} /> <.conflict_section title={dgettext("ui", "Page Slug Conflicts")} items={@page_conflicts} expanded={@sections.page_conflicts} section="page_conflicts" myself={@myself} />
<% end %> <% end %>
<%= if Enum.any?(@post_items) do %> <%= if Enum.any?(@post_items) do %>
<.post_detail_section title={translated("importAnalysis.postsWithCount", %{count: length(@post_items)})} items={@post_items} expanded={@sections.posts} section="posts" myself={@myself} /> <.post_detail_section title={dgettext("ui", "Posts (%{count})", count: length(@post_items))} items={@post_items} expanded={@sections.posts} section="posts" myself={@myself} />
<% end %> <% end %>
<%= if Enum.any?(@other_items) do %> <%= if Enum.any?(@other_items) do %>
<.post_detail_section title={translated("importAnalysis.otherWithCount", %{count: length(@other_items)})} items={@other_items} expanded={@sections.other} section="other" show_type={true} myself={@myself} /> <.post_detail_section title={dgettext("ui", "Other (%{count})", count: length(@other_items))} items={@other_items} expanded={@sections.other} section="other" show_type={true} myself={@myself} />
<% end %> <% end %>
<%= if Enum.any?(@detail_pages) do %> <%= if Enum.any?(@detail_pages) do %>
<.post_detail_section title={translated("importAnalysis.pagesWithCount", %{count: length(@detail_pages)})} items={@detail_pages} expanded={@sections.pages} section="pages" myself={@myself} /> <.post_detail_section title={dgettext("ui", "Pages (%{count})", count: length(@detail_pages))} items={@detail_pages} expanded={@sections.pages} section="pages" myself={@myself} />
<% end %> <% end %>
<%= if Enum.any?(@detail_media) do %> <%= if Enum.any?(@detail_media) do %>
<.media_detail_section title={translated("importAnalysis.mediaWithCount", %{count: length(@detail_media)})} items={@detail_media} expanded={@sections.media} section="media" myself={@myself} /> <.media_detail_section title={dgettext("ui", "Media (%{count})", count: length(@detail_media))} items={@detail_media} expanded={@sections.media} section="media" myself={@myself} />
<% end %> <% end %>
<%= if Enum.any?(Map.get(@report.items, :categories, [])) or Enum.any?(Map.get(@report.items, :tags, [])) do %> <%= if Enum.any?(Map.get(@report.items, :categories, [])) or Enum.any?(Map.get(@report.items, :tags, [])) do %>
<section class="import-detail-section"> <section class="import-detail-section">
<button class="import-section-toggle" type="button" phx-click="toggle_import_section" phx-target={@myself} phx-value-section="taxonomy"> <button class="import-section-toggle" type="button" phx-click="toggle_import_section" phx-target={@myself} phx-value-section="taxonomy">
<span><%= translated("importAnalysis.taxonomyTitle") %></span> <span><%= dgettext("ui", "Categories & Tags") %></span>
<span class="toggle-icon"><%= if @sections.taxonomy, do: "▾", else: "▸" %></span> <span class="toggle-icon"><%= if @sections.taxonomy, do: "▾", else: "▸" %></span>
</button> </button>
<%= if @sections.taxonomy do %> <%= if @sections.taxonomy do %>
<div class="taxonomy-analyze-row"> <div class="taxonomy-analyze-row">
<div class="taxonomy-analyze-dropdown"> <div class="taxonomy-analyze-dropdown">
<button class="taxonomy-analyze-btn" type="button" phx-click="toggle_import_ai_model_selector" phx-target={@myself}><%= translated("importAnalysis.analyzeWith") %></button> <button class="taxonomy-analyze-btn" type="button" phx-click="toggle_import_ai_model_selector" phx-target={@myself}><%= dgettext("ui", "Analyze with...") %></button>
<%= if @import_editor.model_selector_open? do %> <%= if @import_editor.model_selector_open? do %>
<div class="taxonomy-model-dropdown"> <div class="taxonomy-model-dropdown">
<%= for model <- @import_editor.available_models do %> <%= for model <- @import_editor.available_models do %>
<button class="taxonomy-model-option" type="button" phx-click="select_import_ai_model" phx-target={@myself} phx-value-model={model.id}> <button class="taxonomy-model-option" type="button" phx-click="select_import_ai_model" phx-target={@myself} phx-value-model={model.id}>
<%= model.provider_name || model.provider || translated("importAnalysis.unknown") %>: <%= model.name || model.id %> <%= model.provider_name || model.provider || dgettext("ui", "Unknown") %>: <%= model.name || model.id %>
</button> </button>
<% end %> <% end %>
</div> </div>
@@ -1000,12 +997,12 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= @import_editor.selected_model_label %> <%= @import_editor.selected_model_label %>
</button> </button>
<span class="taxonomy-analyze-hint"><%= translated("importAnalysis.aiMappingHint") %></span> <span class="taxonomy-analyze-hint"><%= dgettext("ui", "AI will suggest mappings from new to existing items to avoid duplicates") %></span>
</div> </div>
<div class="import-taxonomy-groups"> <div class="import-taxonomy-groups">
<.taxonomy_group <.taxonomy_group
title={translated("importAnalysis.categories")} title={dgettext("ui", "Categories")}
items={Map.get(@report.items, :categories, [])} items={Map.get(@report.items, :categories, [])}
suggestions={Map.get(@import_editor.taxonomy_terms, :categories, [])} suggestions={Map.get(@import_editor.taxonomy_terms, :categories, [])}
edit={@import_editor.taxonomy_edit} edit={@import_editor.taxonomy_edit}
@@ -1013,7 +1010,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
myself={@myself} myself={@myself}
/> />
<.taxonomy_group <.taxonomy_group
title={translated("importAnalysis.tags")} title={dgettext("ui", "Tags")}
items={Map.get(@report.items, :tags, [])} items={Map.get(@report.items, :tags, [])}
suggestions={Map.get(@import_editor.taxonomy_terms, :tags, [])} suggestions={Map.get(@import_editor.taxonomy_terms, :tags, [])}
edit={@import_editor.taxonomy_edit} edit={@import_editor.taxonomy_edit}
@@ -1029,14 +1026,14 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= if Enum.any?(Map.get(macros, :discovered, [])) do %> <%= if Enum.any?(Map.get(macros, :discovered, [])) do %>
<section class="import-detail-section"> <section class="import-detail-section">
<button class="import-section-toggle" type="button" phx-click="toggle_import_section" phx-target={@myself} phx-value-section="macros"> <button class="import-section-toggle" type="button" phx-click="toggle_import_section" phx-target={@myself} phx-value-section="macros">
<span><%= translated("importAnalysis.macrosWithCount", %{count: macros.total || length(macros.discovered)}) %></span> <span><%= dgettext("ui", "Macros (%{count})", count: macros.total || length(macros.discovered)) %></span>
<span class="toggle-icon"><%= if @sections.macros, do: "▾", else: "▸" %></span> <span class="toggle-icon"><%= if @sections.macros, do: "▾", else: "▸" %></span>
</button> </button>
<%= if @sections.macros do %> <%= if @sections.macros do %>
<div class="macros-summary"> <div class="macros-summary">
<span class="macros-mapped"><%= translated("importAnalysis.mappedCount", %{count: macros.mapped_count || 0}) %></span> <span class="macros-mapped"><%= dgettext("ui", "%{count} mapped", count: macros.mapped_count || 0) %></span>
<span class="macros-unmapped"><%= translated("importAnalysis.unmappedCount", %{count: macros.unmapped_count || 0}) %></span> <span class="macros-unmapped"><%= dgettext("ui", "%{count} unmapped", count: macros.unmapped_count || 0) %></span>
</div> </div>
<div class="macros-list"> <div class="macros-list">
<%= for macro <- macros.discovered do %> <%= for macro <- macros.discovered do %>
@@ -1044,9 +1041,9 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<div class="macro-header"> <div class="macro-header">
<span class="macro-name"><%= macro.name %></span> <span class="macro-name"><%= macro.name %></span>
<span class={"macro-status-badge #{if macro.mapped, do: "mapped", else: "unmapped"}"}> <span class={"macro-status-badge #{if macro.mapped, do: "mapped", else: "unmapped"}"}>
<%= if macro.mapped, do: translated("importAnalysis.macroStatusMapped"), else: translated("importAnalysis.macroStatusUnknown") %> <%= if macro.mapped, do: dgettext("ui", "Mapped"), else: dgettext("ui", "Unknown") %>
</span> </span>
<span class="macro-count"><%= translated("importAnalysis.macroUses", %{count: macro.total_count}) %></span> <span class="macro-count"><%= dgettext("ui", "%{count} uses", count: macro.total_count) %></span>
</div> </div>
<%= if Enum.any?(Map.get(macro, :usages, [])) do %> <%= if Enum.any?(Map.get(macro, :usages, [])) do %>
<div class="macro-usages"> <div class="macro-usages">
@@ -1058,17 +1055,17 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<span class="macro-usage-param"><%= k %>=<%= v %></span> <span class="macro-usage-param"><%= k %>=<%= v %></span>
<% end %> <% end %>
<% else %> <% else %>
<%= translated("importAnalysis.noParameters") %> <%= dgettext("ui", "(no parameters)") %>
<% end %> <% end %>
</span> </span>
<span class="macro-usage-count"><%= translated("importAnalysis.macroUses", %{count: usage.count}) %></span> <span class="macro-usage-count"><%= dgettext("ui", "%{count} uses", count: usage.count) %></span>
</div> </div>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
<%= if Enum.any?(Map.get(macro, :post_slugs, [])) do %> <%= if Enum.any?(Map.get(macro, :post_slugs, [])) do %>
<div class="macro-post-slugs"> <div class="macro-post-slugs">
<%= translated("importAnalysis.usedIn", %{items: Enum.join(Enum.take(macro.post_slugs, 5), ", "), more: if(length(macro.post_slugs) > 5, do: translated("importAnalysis.moreSuffix", %{count: length(macro.post_slugs) - 5}), else: "")}) %> <%= dgettext("ui", "Used in: %{items}%{more}", items: Enum.join(Enum.take(macro.post_slugs, 5), ", "), more: (if length(macro.post_slugs) > 5, do: dgettext("ui", ", +%{count} more", count: length(macro.post_slugs) - 5), else: "")) %>
</div> </div>
<% end %> <% end %>
</div> </div>
@@ -1084,7 +1081,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor"> <svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path> <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path>
</svg> </svg>
<p><%= translated("importAnalysis.emptyState") %></p> <p><%= dgettext("ui", "Select a WordPress export file to begin analysis.") %></p>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>
@@ -1111,10 +1108,10 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<table class="import-detail-table conflicts-table"> <table class="import-detail-table conflicts-table">
<thead> <thead>
<tr> <tr>
<th><%= translated("importAnalysis.slug") %></th> <th><%= dgettext("ui", "Slug") %></th>
<th><%= translated("importAnalysis.newEntryWxr") %></th> <th><%= dgettext("ui", "New Entry (WXR)") %></th>
<th><%= translated("importAnalysis.existingEntry") %></th> <th><%= dgettext("ui", "Existing Entry") %></th>
<th><%= translated("importAnalysis.resolution") %></th> <th><%= dgettext("ui", "Resolution") %></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -1122,15 +1119,15 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<tr> <tr>
<td class="slug-cell"><%= Map.get(item, :slug) %></td> <td class="slug-cell"><%= Map.get(item, :slug) %></td>
<td><%= Map.get(item, :title) %></td> <td><%= Map.get(item, :title) %></td>
<td><%= Map.get(item, :existing_title) || translated("importAnalysis.none") %></td> <td><%= Map.get(item, :existing_title) || dgettext("ui", "--") %></td>
<td> <td>
<form phx-change="change_import_conflict_resolution" phx-target={@myself}> <form phx-change="change_import_conflict_resolution" phx-target={@myself}>
<input type="hidden" name="item_type" value={Map.get(item, :item_type)} /> <input type="hidden" name="item_type" value={Map.get(item, :item_type)} />
<input type="hidden" name="item_name" value={Map.get(item, :slug)} /> <input type="hidden" name="item_name" value={Map.get(item, :slug)} />
<select class="resolution-select" name="resolution"> <select class="resolution-select" name="resolution">
<option value="ignore" selected={conflict_resolution_selected?(item, "ignore")}><%= translated("importAnalysis.ignore") %></option> <option value="ignore" selected={conflict_resolution_selected?(item, "ignore")}><%= dgettext("ui", "Ignore") %></option>
<option value="overwrite" selected={conflict_resolution_selected?(item, "overwrite")}><%= translated("importAnalysis.overwrite") %></option> <option value="overwrite" selected={conflict_resolution_selected?(item, "overwrite")}><%= dgettext("ui", "Overwrite") %></option>
<option value="import" selected={Map.get(item, :resolution) == "import"}><%= translated("importAnalysis.importNewSlug") %></option> <option value="import" selected={Map.get(item, :resolution) == "import"}><%= dgettext("ui", "Import (new slug)") %></option>
</select> </select>
</form> </form>
</td> </td>
@@ -1163,15 +1160,15 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<table class="import-detail-table"> <table class="import-detail-table">
<thead> <thead>
<tr> <tr>
<th><%= translated("importAnalysis.status") %></th> <th><%= dgettext("ui", "Status") %></th>
<%= if @show_type do %> <%= if @show_type do %>
<th><%= translated("importAnalysis.type") %></th> <th><%= dgettext("ui", "Type") %></th>
<% end %> <% end %>
<th><%= translated("importAnalysis.title") %></th> <th><%= dgettext("ui", "Title") %></th>
<th><%= translated("importAnalysis.slug") %></th> <th><%= dgettext("ui", "Slug") %></th>
<th><%= translated("importAnalysis.categories") %></th> <th><%= dgettext("ui", "Categories") %></th>
<th><%= translated("importAnalysis.wpStatus") %></th> <th><%= dgettext("ui", "WP Status") %></th>
<th><%= translated("importAnalysis.existingMatch") %></th> <th><%= dgettext("ui", "Existing Match") %></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -1184,8 +1181,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<td><%= Map.get(item, :title) %></td> <td><%= Map.get(item, :title) %></td>
<td class="slug-cell"><%= Map.get(item, :slug) %></td> <td class="slug-cell"><%= Map.get(item, :slug) %></td>
<td class="categories-cell"><%= joined_or_none(Map.get(item, :categories)) %></td> <td class="categories-cell"><%= joined_or_none(Map.get(item, :categories)) %></td>
<td><%= Map.get(item, :wp_status) || translated("importAnalysis.none") %></td> <td><%= Map.get(item, :wp_status) || dgettext("ui", "--") %></td>
<td class="existing-match"><%= Map.get(item, :existing_title) || translated("importAnalysis.none") %></td> <td class="existing-match"><%= Map.get(item, :existing_title) || dgettext("ui", "--") %></td>
</tr> </tr>
<% end %> <% end %>
</tbody> </tbody>
@@ -1214,11 +1211,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<table class="import-detail-table"> <table class="import-detail-table">
<thead> <thead>
<tr> <tr>
<th><%= translated("importAnalysis.status") %></th> <th><%= dgettext("ui", "Status") %></th>
<th><%= translated("importAnalysis.filename") %></th> <th><%= dgettext("ui", "Filename") %></th>
<th><%= translated("importAnalysis.type") %></th> <th><%= dgettext("ui", "Type") %></th>
<th><%= translated("importAnalysis.path") %></th> <th><%= dgettext("ui", "Path") %></th>
<th><%= translated("importAnalysis.existingMatch") %></th> <th><%= dgettext("ui", "Existing Match") %></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -1226,9 +1223,9 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<tr> <tr>
<td><span class={status_badge_class(item.status)}><%= item.status %></span></td> <td><span class={status_badge_class(item.status)}><%= item.status %></span></td>
<td><%= Map.get(item, :filename) %></td> <td><%= Map.get(item, :filename) %></td>
<td class="mime-type-cell"><%= Map.get(item, :mime_type) || translated("importAnalysis.none") %></td> <td class="mime-type-cell"><%= Map.get(item, :mime_type) || dgettext("ui", "--") %></td>
<td class="slug-cell"><%= Map.get(item, :relative_path) %></td> <td class="slug-cell"><%= Map.get(item, :relative_path) %></td>
<td class="existing-match"><%= Map.get(item, :existing_title) || translated("importAnalysis.none") %></td> <td class="existing-match"><%= Map.get(item, :existing_title) || dgettext("ui", "--") %></td>
</tr> </tr>
<% end %> <% end %>
</tbody> </tbody>
@@ -1248,10 +1245,10 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<h3><%= @label %></h3> <h3><%= @label %></h3>
<div class="import-stat-number"><%= total_stats(@stats) %></div> <div class="import-stat-number"><%= total_stats(@stats) %></div>
<div class="import-stat-breakdown"> <div class="import-stat-breakdown">
<%= if @stats.new_count > 0 do %><span class="import-stat-tag stat-new"><%= @stats.new_count %> <%= translated("importAnalysis.new") %></span><% end %> <%= if @stats.new_count > 0 do %><span class="import-stat-tag stat-new"><%= @stats.new_count %> <%= dgettext("ui", "new") %></span><% end %>
<%= if @stats.update_count > 0 do %><span class="import-stat-tag stat-update"><%= @stats.update_count %> <%= translated("importAnalysis.update") %></span><% end %> <%= if @stats.update_count > 0 do %><span class="import-stat-tag stat-update"><%= @stats.update_count %> <%= dgettext("ui", "update") %></span><% end %>
<%= if @stats.conflict_count > 0 do %><span class="import-stat-tag stat-conflict"><%= @stats.conflict_count %> <%= translated("importAnalysis.conflict") %></span><% end %> <%= if @stats.conflict_count > 0 do %><span class="import-stat-tag stat-conflict"><%= @stats.conflict_count %> <%= dgettext("ui", "conflict") %></span><% end %>
<%= if @stats.duplicate_count > 0 do %><span class="import-stat-tag stat-duplicate"><%= @stats.duplicate_count %> <%= translated("importAnalysis.duplicate") %></span><% end %> <%= if @stats.duplicate_count > 0 do %><span class="import-stat-tag stat-duplicate"><%= @stats.duplicate_count %> <%= dgettext("ui", "duplicate") %></span><% end %>
</div> </div>
</div> </div>
""" """
@@ -1285,11 +1282,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<h3><%= @label %></h3> <h3><%= @label %></h3>
<div class="import-stat-number"><%= total_media_stats(@stats) %></div> <div class="import-stat-number"><%= total_media_stats(@stats) %></div>
<div class="import-stat-breakdown"> <div class="import-stat-breakdown">
<%= if @stats.new_count > 0 do %><span class="import-stat-tag stat-new"><%= @stats.new_count %> <%= translated("importAnalysis.new") %></span><% end %> <%= if @stats.new_count > 0 do %><span class="import-stat-tag stat-new"><%= @stats.new_count %> <%= dgettext("ui", "new") %></span><% end %>
<%= if @stats.update_count > 0 do %><span class="import-stat-tag stat-update"><%= @stats.update_count %> <%= translated("importAnalysis.update") %></span><% end %> <%= if @stats.update_count > 0 do %><span class="import-stat-tag stat-update"><%= @stats.update_count %> <%= dgettext("ui", "update") %></span><% end %>
<%= if @stats.conflict_count > 0 do %><span class="import-stat-tag stat-conflict"><%= @stats.conflict_count %> <%= translated("importAnalysis.conflict") %></span><% end %> <%= if @stats.conflict_count > 0 do %><span class="import-stat-tag stat-conflict"><%= @stats.conflict_count %> <%= dgettext("ui", "conflict") %></span><% end %>
<%= if @stats.duplicate_count > 0 do %><span class="import-stat-tag stat-duplicate"><%= @stats.duplicate_count %> <%= translated("importAnalysis.duplicate") %></span><% end %> <%= if @stats.duplicate_count > 0 do %><span class="import-stat-tag stat-duplicate"><%= @stats.duplicate_count %> <%= dgettext("ui", "duplicate") %></span><% end %>
<%= if @stats.missing_count > 0 do %><span class="import-stat-tag stat-missing"><%= @stats.missing_count %> <%= translated("importAnalysis.missing") %></span><% end %> <%= if @stats.missing_count > 0 do %><span class="import-stat-tag stat-missing"><%= @stats.missing_count %> <%= dgettext("ui", "missing") %></span><% end %>
</div> </div>
</div> </div>
""" """
@@ -1305,9 +1302,9 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<h3><%= @label %></h3> <h3><%= @label %></h3>
<div class="import-stat-number"><%= @stats.existing_count + @stats.mapped_count + @stats.new_count %></div> <div class="import-stat-number"><%= @stats.existing_count + @stats.mapped_count + @stats.new_count %></div>
<div class="import-stat-breakdown"> <div class="import-stat-breakdown">
<%= if @stats.existing_count > 0 do %><span class="import-stat-tag stat-update"><%= @stats.existing_count %> <%= translated("importAnalysis.existing") %></span><% end %> <%= if @stats.existing_count > 0 do %><span class="import-stat-tag stat-update"><%= @stats.existing_count %> <%= dgettext("ui", "existing") %></span><% end %>
<%= if @stats.mapped_count > 0 do %><span class="import-stat-tag stat-mapped"><%= @stats.mapped_count %> <%= translated("importAnalysis.mapped") %></span><% end %> <%= if @stats.mapped_count > 0 do %><span class="import-stat-tag stat-mapped"><%= @stats.mapped_count %> <%= dgettext("ui", "mapped") %></span><% end %>
<%= if @stats.new_count > 0 do %><span class="import-stat-tag stat-new"><%= @stats.new_count %> <%= translated("importAnalysis.new") %></span><% end %> <%= if @stats.new_count > 0 do %><span class="import-stat-tag stat-new"><%= @stats.new_count %> <%= dgettext("ui", "new") %></span><% end %>
</div> </div>
</div> </div>
""" """
@@ -1342,14 +1339,14 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
type="text" type="text"
name="mapped_to" name="mapped_to"
value={Map.get(@edit || %{}, :value, Map.get(item, :mapped_to) || "") || ""} value={Map.get(@edit || %{}, :value, Map.get(item, :mapped_to) || "") || ""}
placeholder={translated("importAnalysis.mapToPlaceholder")} placeholder={dgettext("ui", "Map to...")}
list={"taxonomy-suggestions-#{@type}"} list={"taxonomy-suggestions-#{@type}"}
autocomplete="off" autocomplete="off"
/> />
<button class="taxonomy-edit-btn" type="submit" title={translated("importAnalysis.mapToPlaceholder")}>✓</button> <button class="taxonomy-edit-btn" type="submit" title={dgettext("ui", "Map to...")}>✓</button>
<button class="taxonomy-edit-btn ghost" type="button" phx-click="cancel_import_taxonomy_edit" phx-target={@myself} title={translated("Cancel")}>×</button> <button class="taxonomy-edit-btn ghost" type="button" phx-click="cancel_import_taxonomy_edit" phx-target={@myself} title={dgettext("ui", "Cancel")}>×</button>
<%= if present?(item.mapped_to) do %> <%= if present?(item.mapped_to) do %>
<button class="taxonomy-clear-btn" type="button" phx-click="clear_import_taxonomy_mapping" phx-target={@myself} phx-value-type={@type} phx-value-name={item.name} title={translated("importAnalysis.clearMapping")}>×</button> <button class="taxonomy-clear-btn" type="button" phx-click="clear_import_taxonomy_mapping" phx-target={@myself} phx-value-type={@type} phx-value-name={item.name} title={dgettext("ui", "Clear mapping")}>×</button>
<% end %> <% end %>
</form> </form>
<% else %> <% else %>
@@ -1380,7 +1377,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
phx-value-name={item.name} phx-value-name={item.name}
phx-value-mapped_to={Map.get(item, :mapped_to) || ""} phx-value-mapped_to={Map.get(item, :mapped_to) || ""}
><%= item.mapped_to %></button> ><%= item.mapped_to %></button>
<button class="taxonomy-clear-btn" type="button" phx-click="clear_import_taxonomy_mapping" phx-target={@myself} phx-value-type={@type} phx-value-name={item.name} title={translated("importAnalysis.clearMapping")}>×</button> <button class="taxonomy-clear-btn" type="button" phx-click="clear_import_taxonomy_mapping" phx-target={@myself} phx-value-type={@type} phx-value-name={item.name} title={dgettext("ui", "Clear mapping")}>×</button>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
@@ -1416,7 +1413,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
end end
defp joined_or_none(values) when is_list(values) and values != [], do: Enum.join(values, ", ") defp joined_or_none(values) when is_list(values) and values != [], do: Enum.join(values, ", ")
defp joined_or_none(_values), do: translated("importAnalysis.none") defp joined_or_none(_values), do: dgettext("ui", "--")
defp status_badge_class(status), do: ["status-badge", status] defp status_badge_class(status), do: ["status-badge", status]
@@ -1445,7 +1442,4 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
defp maybe_put(map, _key, nil), do: map defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value) defp maybe_put(map, key, value), do: Map.put(map, key, value)
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -2,7 +2,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
@moduledoc false @moduledoc false
alias BDS.{ImportAnalysis, ImportDefinitions, Metadata} alias BDS.{ImportAnalysis, ImportDefinitions, Metadata}
alias BDS.Desktop.{FilePicker, FolderPicker, ShellData} alias BDS.Desktop.{FilePicker, FolderPicker}
use Gettext, backend: BDS.Gettext
@spec change_definition(term(), term(), term()) :: term() @spec change_definition(term(), term(), term()) :: term()
def change_definition(socket, params, reload) do def change_definition(socket, params, reload) do
@@ -18,7 +19,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
@spec select_uploads_folder(term(), term(), term()) :: term() @spec select_uploads_folder(term(), term(), term()) :: term()
def select_uploads_folder(socket, reload, append_output) do def select_uploads_folder(socket, reload, append_output) do
with %{id: definition_id} <- socket.assigns.current_tab do with %{id: definition_id} <- socket.assigns.current_tab do
case FolderPicker.choose_directory(translated("importAnalysis.uploadsFolder")) do case FolderPicker.choose_directory(dgettext("ui", "Uploads Folder")) do
{:ok, uploads_folder_path} -> {:ok, uploads_folder_path} ->
{:ok, _definition} = {:ok, _definition} =
ImportDefinitions.update_definition(definition_id, %{ ImportDefinitions.update_definition(definition_id, %{
@@ -32,7 +33,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
{:error, %{message: message}} -> {:error, %{message: message}} ->
socket socket
|> append_output.(translated("activity.import"), message, nil, "error") |> append_output.(dgettext("ui", "Import"), message, nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
else else
@@ -44,7 +45,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
def select_and_analyze(socket, reload, append_output) do def select_and_analyze(socket, reload, append_output) do
with %{id: definition_id} <- socket.assigns.current_tab, with %{id: definition_id} <- socket.assigns.current_tab,
%{} = definition <- ImportDefinitions.get_definition(definition_id) do %{} = definition <- ImportDefinitions.get_definition(definition_id) do
case FilePicker.choose_file(translated("importAnalysis.wxrFile")) do case FilePicker.choose_file(dgettext("ui", "WXR File")) do
{:ok, wxr_file_path} -> {:ok, wxr_file_path} ->
project_id = socket.assigns.projects.active_project_id project_id = socket.assigns.projects.active_project_id
@@ -78,7 +79,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
:import_editor_analysis_states, :import_editor_analysis_states,
Map.put(socket.assigns.import_editor_analysis_states, definition_id, %{ Map.put(socket.assigns.import_editor_analysis_states, definition_id, %{
loading: true, loading: true,
step: translated("importAnalysis.analyzingWxr"), step: dgettext("ui", "Analyzing WXR file..."),
detail: Path.basename(wxr_file_path), detail: Path.basename(wxr_file_path),
file_path: wxr_file_path, file_path: wxr_file_path,
ref: task.ref ref: task.ref
@@ -99,7 +100,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
{:error, %{message: message}} -> {:error, %{message: message}} ->
socket socket
|> append_output.(translated("activity.import"), message, nil, "error") |> append_output.(dgettext("ui", "Import"), message, nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
else else
@@ -167,18 +168,18 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("activity.import"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Import"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
{:error, %{message: message}} -> {:error, %{message: message}} ->
socket socket
|> append_output.(translated("activity.import"), message, nil, "error") |> append_output.(dgettext("ui", "Import"), message, nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("activity.import"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Import"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -200,7 +201,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
:import_editor_analysis_states, :import_editor_analysis_states,
Map.delete(socket.assigns.import_editor_analysis_states, definition_id) Map.delete(socket.assigns.import_editor_analysis_states, definition_id)
) )
|> append_output.(translated("activity.import"), message, nil, "error") |> append_output.(dgettext("ui", "Import"), message, nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -294,12 +295,12 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
def translate_phase(step) when is_binary(step) do def translate_phase(step) when is_binary(step) do
case step do case step do
"parsing" -> translated("importAnalysis.analysisPhase.parsing") "parsing" -> dgettext("ui", "Parsing WXR file...")
"scanning" -> translated("importAnalysis.analysisPhase.scanning") "scanning" -> dgettext("ui", "Scanning entries...")
"taxonomies" -> translated("importAnalysis.analysisPhase.taxonomies") "taxonomies" -> dgettext("ui", "Analyzing taxonomies...")
"posts" -> translated("importAnalysis.analysisPhase.posts") "posts" -> dgettext("ui", "Analyzing posts...")
"media" -> translated("importAnalysis.analysisPhase.media") "media" -> dgettext("ui", "Analyzing media...")
"complete" -> translated("importAnalysis.analysisPhase.complete") "complete" -> dgettext("ui", "Analysis complete")
other -> other other -> other
end end
end end
@@ -307,8 +308,5 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.AnalysisState do
@spec translate_phase(term()) :: term() @spec translate_phase(term()) :: term()
def translate_phase(other), do: other def translate_phase(other), do: other
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

View File

@@ -2,8 +2,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
@moduledoc false @moduledoc false
alias BDS.{ImportDefinitions, ImportExecution} alias BDS.{ImportDefinitions, ImportExecution}
alias BDS.Desktop.ShellData
alias BDS.Desktop.ShellLive.ImportEditor.AnalysisState alias BDS.Desktop.ShellLive.ImportEditor.AnalysisState
use Gettext, backend: BDS.Gettext
@spec execute_import(term(), term(), term()) :: term() @spec execute_import(term(), term(), term()) :: term()
def execute_import(socket, reload, _append_output) do def execute_import(socket, reload, _append_output) do
@@ -129,8 +129,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
}) })
) )
|> append_output.( |> append_output.(
translated("activity.import"), dgettext("ui", "Import"),
translated("importAnalysis.importComplete", %{count: previous_state.count}), dgettext("ui", "Import completed successfully!", count: previous_state.count),
nil, nil,
"info" "info"
) )
@@ -148,7 +148,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
ref: nil ref: nil
}) })
) )
|> append_output.(translated("activity.import"), message, nil, "error") |> append_output.(dgettext("ui", "Import"), message, nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
{:error, reason} -> {:error, reason} ->
@@ -165,7 +165,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
ref: nil ref: nil
}) })
) )
|> append_output.(translated("activity.import"), message, nil, "error") |> append_output.(dgettext("ui", "Import"), message, nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -208,7 +208,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
ref: nil ref: nil
}) })
) )
|> append_output.(translated("activity.import"), message, nil, "error") |> append_output.(dgettext("ui", "Import"), message, nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -265,16 +265,16 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
seconds = div(ms, 1000) seconds = div(ms, 1000)
if seconds < 60 do if seconds < 60 do
translated("importAnalysis.eta", %{ dgettext("ui", "ETA: %{value}",
value: translated("importAnalysis.etaSeconds", %{count: seconds}) value: dgettext("ui", "%{count}s", count: seconds)
}) )
else else
m = div(seconds, 60) m = div(seconds, 60)
s = rem(seconds, 60) s = rem(seconds, 60)
translated("importAnalysis.eta", %{ dgettext("ui", "ETA: %{value}",
value: translated("importAnalysis.etaMinutes", %{minutes: m, seconds: s}) value: dgettext("ui", "%{minutes}m %{seconds}s", minutes: m, seconds: s)
}) )
end end
end end
@@ -282,18 +282,15 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.ProgressTracking do
def translate_execution_phase(phase) when is_binary(phase) do def translate_execution_phase(phase) when is_binary(phase) do
case phase do case phase do
"tags" -> translated("importAnalysis.phase.tags") "tags" -> dgettext("ui", "Importing tags & categories...")
"posts" -> translated("importAnalysis.phase.posts") "posts" -> dgettext("ui", "Importing posts...")
"media" -> translated("importAnalysis.phase.media") "media" -> dgettext("ui", "Importing media...")
"pages" -> translated("importAnalysis.phase.pages") "pages" -> dgettext("ui", "Importing pages...")
"complete" -> translated("importAnalysis.phase.complete") "complete" -> dgettext("ui", "Import complete")
other -> other other -> other
end end
end end
@spec translate_execution_phase(term()) :: term() @spec translate_execution_phase(term()) :: term()
def translate_execution_phase(other), do: other def translate_execution_phase(other), do: other
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -2,7 +2,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
@moduledoc false @moduledoc false
alias BDS.{AI, ImportDefinitions, Metadata, Tags} alias BDS.{AI, ImportDefinitions, Metadata, Tags}
alias BDS.Desktop.ShellData use Gettext, backend: BDS.Gettext
@spec start_taxonomy_edit(term(), term(), term()) :: term() @spec start_taxonomy_edit(term(), term(), term()) :: term()
def start_taxonomy_edit( def start_taxonomy_edit(
@@ -85,12 +85,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
socket.assigns.offline_mode -> socket.assigns.offline_mode ->
socket socket
|> append_output.( |> append_output.(
translated("activity.import"), dgettext("ui", "Import"),
ShellData.translate( BDS.Gettext.lgettext(socket.assigns.page_language, "ui", "Automatic AI actions stay gated by airplane mode."),
"Automatic AI actions stay gated by airplane mode.",
%{},
socket.assigns.page_language
),
nil, nil,
"info" "info"
) )
@@ -124,8 +120,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
socket socket
|> append_output.( |> append_output.(
translated("activity.import"), dgettext("ui", "Import"),
translated("importAnalysis.mappedCount", %{count: mapped_count}), dgettext("ui", "%{count} mapped", count: mapped_count),
Map.get(socket.assigns.import_editor_selected_models, definition_id), Map.get(socket.assigns.import_editor_selected_models, definition_id),
"info" "info"
) )
@@ -134,7 +130,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.( |> append_output.(
translated("activity.import"), dgettext("ui", "Import"),
inspect(reason), inspect(reason),
Map.get(socket.assigns.import_editor_selected_models, definition_id), Map.get(socket.assigns.import_editor_selected_models, definition_id),
"error" "error"
@@ -274,19 +270,16 @@ defmodule BDS.Desktop.ShellLive.ImportEditor.TaxonomyEditing do
def taxonomy_mapping_tooltip(item) do def taxonomy_mapping_tooltip(item) do
action = action =
if present?(item.mapped_to), if present?(item.mapped_to),
do: translated("importAnalysis.mappingActionEdit"), do: dgettext("ui", "edit"),
else: translated("importAnalysis.mappingActionAdd") else: dgettext("ui", "add")
translated("importAnalysis.mappingTooltip", %{action: action}) dgettext("ui", "Click to %{action} mapping", action: action)
end end
@spec maybe_put_option(term(), term(), term()) :: term() @spec maybe_put_option(term(), term(), term()) :: term()
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, 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

View File

@@ -77,8 +77,8 @@
data-testid="toggle-sidebar" data-testid="toggle-sidebar"
type="button" type="button"
phx-click="toggle_sidebar" phx-click="toggle_sidebar"
aria-label={translated("Toggle sidebar")} aria-label={dgettext("ui", "Toggle sidebar")}
title={translated("Toggle sidebar")} title={dgettext("ui", "Toggle sidebar")}
> >
<span class={["window-titlebar-sidebar-icon", if(@workbench.sidebar_visible, do: "is-active", else: "is-inactive")]}> <span class={["window-titlebar-sidebar-icon", if(@workbench.sidebar_visible, do: "is-active", else: "is-inactive")]}>
<span class="window-titlebar-sidebar-pane"></span> <span class="window-titlebar-sidebar-pane"></span>
@@ -89,8 +89,8 @@
data-testid="toggle-panel" data-testid="toggle-panel"
type="button" type="button"
phx-click="toggle_panel" phx-click="toggle_panel"
aria-label={translated("Toggle panel")} aria-label={dgettext("ui", "Toggle panel")}
title={translated("Toggle panel")} title={dgettext("ui", "Toggle panel")}
> >
<span class={["window-titlebar-panel-icon", if(@workbench.panel.visible, do: "is-active", else: "is-inactive")]}> <span class={["window-titlebar-panel-icon", if(@workbench.panel.visible, do: "is-active", else: "is-inactive")]}>
<span class="window-titlebar-panel-pane"></span> <span class="window-titlebar-panel-pane"></span>
@@ -101,8 +101,8 @@
data-testid="toggle-assistant" data-testid="toggle-assistant"
type="button" type="button"
phx-click="toggle_assistant_sidebar" phx-click="toggle_assistant_sidebar"
aria-label={translated("Toggle assistant")} aria-label={dgettext("ui", "Toggle assistant")}
title={translated("Toggle assistant")} title={dgettext("ui", "Toggle assistant")}
> >
<span class={["window-titlebar-assistant-icon", if(@workbench.assistant_sidebar_visible, do: "is-active", else: "is-inactive")]}> <span class={["window-titlebar-assistant-icon", if(@workbench.assistant_sidebar_visible, do: "is-active", else: "is-inactive")]}>
<span class="window-titlebar-assistant-pane"></span> <span class="window-titlebar-assistant-pane"></span>
@@ -178,8 +178,8 @@
data-testid="sidebar-filter-toggle" data-testid="sidebar-filter-toggle"
type="button" type="button"
phx-click="toggle_sidebar_filters" phx-click="toggle_sidebar_filters"
aria-label={translated(Map.get(@sidebar_data.filters, :toggle_filters_label))} aria-label={Map.get(@sidebar_data.filters, :toggle_filters_label)}
title={translated(Map.get(@sidebar_data.filters, :toggle_filters_label))} title={Map.get(@sidebar_data.filters, :toggle_filters_label)}
> >
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M6 12v-1h4v1H6zM4 8v-1h8v1H4zm-2-4v-1h12v1H2z"/> <path d="M6 12v-1h4v1H6zM4 8v-1h8v1H4zm-2-4v-1h12v1H2z"/>
@@ -194,8 +194,8 @@
type="button" type="button"
phx-click="create_sidebar_item" phx-click="create_sidebar_item"
phx-value-kind={create_action.kind} phx-value-kind={create_action.kind}
aria-label={translated(create_action.label)} aria-label={create_action.label}
title={translated(create_action.label)} title={create_action.label}
> >
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M14 7v1H8v6H7V8H1V7h6V1h1v6h6z"/> <path d="M14 7v1H8v6H7V8H1V7h6V1h1v6h6z"/>
@@ -215,7 +215,7 @@
<main class="app-content" data-region="content"> <main class="app-content" data-region="content">
<div class="tab-bar" data-region="tab-bar"> <div class="tab-bar" data-region="tab-bar">
<%= if Enum.empty?(@workbench.tabs) do %> <%= if Enum.empty?(@workbench.tabs) do %>
<div class="tab-bar-empty"><%= translated("Dashboard") %></div> <div class="tab-bar-empty"><%= dgettext("ui", "Dashboard") %></div>
<% else %> <% else %>
<div class="tab-bar-tabs"> <div class="tab-bar-tabs">
<%= for tab <- @workbench.tabs do %> <%= for tab <- @workbench.tabs do %>
@@ -253,8 +253,8 @@
phx-click="close_tab" phx-click="close_tab"
phx-value-type={tab.type} phx-value-type={tab.type}
phx-value-id={tab.id} phx-value-id={tab.id}
aria-label={translated("Close tab")} aria-label={dgettext("ui", "Close tab")}
title={translated("Close tab")} title={dgettext("ui", "Close tab")}
> >
× ×
</button> </button>
@@ -269,41 +269,41 @@
<%= if is_nil(@current_tab) do %> <%= if is_nil(@current_tab) do %>
<div class="editor-empty"> <div class="editor-empty">
<div class="dashboard-content"> <div class="dashboard-content">
<h1 data-testid="editor-title"><%= translated("dashboard.title") %></h1> <h1 data-testid="editor-title"><%= dgettext("ui", "Dashboard") %></h1>
<p class="text-muted"><%= translated("dashboard.subtitle") %></p> <p class="text-muted"><%= dgettext("ui", "Overview of your blog database") %></p>
<div class="dashboard-stats"> <div class="dashboard-stats">
<div class="stat-card"> <div class="stat-card">
<div class="stat-number"><%= @dashboard.post_stats.total_posts || 0 %></div> <div class="stat-number"><%= @dashboard.post_stats.total_posts || 0 %></div>
<div class="stat-label"><%= translated("dashboard.stats.totalPosts") %></div> <div class="stat-label"><%= dgettext("ui", "Total Posts") %></div>
<div class="stat-breakdown"> <div class="stat-breakdown">
<span class="stat-tag stat-published"><%= translated("dashboard.stats.published", %{count: @dashboard.post_stats.published_count || 0}) %></span> <span class="stat-tag stat-published"><%= dgettext("ui", "dashboard.stats.published", count: @dashboard.post_stats.published_count || 0) %></span>
<span class="stat-tag stat-draft"><%= translated("dashboard.stats.drafts", %{count: @dashboard.post_stats.draft_count || 0}) %></span> <span class="stat-tag stat-draft"><%= dgettext("ui", "dashboard.stats.drafts", count: @dashboard.post_stats.draft_count || 0) %></span>
<%= if (@dashboard.post_stats.archived_count || 0) > 0 do %> <%= if (@dashboard.post_stats.archived_count || 0) > 0 do %>
<span class="stat-tag stat-archived"><%= translated("dashboard.stats.archived", %{count: @dashboard.post_stats.archived_count || 0}) %></span> <span class="stat-tag stat-archived"><%= dgettext("ui", "dashboard.stats.archived", count: @dashboard.post_stats.archived_count || 0) %></span>
<% end %> <% end %>
</div> </div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-number"><%= @dashboard.media_stats.media_count || 0 %></div> <div class="stat-number"><%= @dashboard.media_stats.media_count || 0 %></div>
<div class="stat-label"><%= translated("dashboard.stats.mediaFiles") %></div> <div class="stat-label"><%= dgettext("ui", "Media Files") %></div>
<div class="stat-breakdown"> <div class="stat-breakdown">
<span class="stat-tag"><%= translated("dashboard.stats.images", %{count: @dashboard.media_stats.image_count || 0}) %></span> <span class="stat-tag"><%= dgettext("ui", "dashboard.stats.images", count: @dashboard.media_stats.image_count || 0) %></span>
<span class="stat-tag"><%= ShellData.format_bytes(@dashboard.media_stats.total_bytes || 0) %></span> <span class="stat-tag"><%= ShellData.format_bytes(@dashboard.media_stats.total_bytes || 0) %></span>
</div> </div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-number"><%= length(@dashboard_tag_cloud_items) %></div> <div class="stat-number"><%= length(@dashboard_tag_cloud_items) %></div>
<div class="stat-label"><%= translated("dashboard.stats.tags") %></div> <div class="stat-label"><%= dgettext("ui", "Tags") %></div>
<div class="stat-breakdown"> <div class="stat-breakdown">
<span class="stat-tag"><%= translated("dashboard.stats.categories", %{count: length(@dashboard_category_counts)}) %></span> <span class="stat-tag"><%= dgettext("ui", "dashboard.stats.categories", count: length(@dashboard_category_counts)) %></span>
</div> </div>
</div> </div>
</div> </div>
<%= if Enum.any?(@dashboard_timeline_entries) do %> <%= if Enum.any?(@dashboard_timeline_entries) do %>
<div class="dashboard-section"> <div class="dashboard-section">
<h4><%= translated("dashboard.section.postsOverTime") %></h4> <h4><%= dgettext("ui", "Posts Over Time") %></h4>
<div class="timeline-chart"> <div class="timeline-chart">
<%= for entry <- @dashboard_timeline_entries do %> <%= for entry <- @dashboard_timeline_entries do %>
<div class="timeline-bar-container"> <div class="timeline-bar-container">
@@ -322,7 +322,7 @@
<%= if Enum.any?(@dashboard_tag_cloud_items) do %> <%= if Enum.any?(@dashboard_tag_cloud_items) do %>
<div class="dashboard-section"> <div class="dashboard-section">
<h4><%= translated("dashboard.section.tags") %></h4> <h4><%= dgettext("ui", "Tags") %></h4>
<div class="tag-cloud"> <div class="tag-cloud">
<%= for item <- @dashboard_tag_cloud_items do %> <%= for item <- @dashboard_tag_cloud_items do %>
<span class={["dashboard-tag", if(item.color, do: "has-color")]} style={ShellData.render_dashboard_tag_style(item)} title={ShellData.dashboard_post_count_label(item.count)}><%= item.tag %></span> <span class={["dashboard-tag", if(item.color, do: "has-color")]} style={ShellData.render_dashboard_tag_style(item)} title={ShellData.dashboard_post_count_label(item.count)}><%= item.tag %></span>
@@ -333,7 +333,7 @@
<%= if Enum.any?(@dashboard_category_counts) do %> <%= if Enum.any?(@dashboard_category_counts) do %>
<div class="dashboard-section"> <div class="dashboard-section">
<h4><%= translated("dashboard.section.categories") %></h4> <h4><%= dgettext("ui", "Categories") %></h4>
<div class="tag-cloud"> <div class="tag-cloud">
<%= for category <- @dashboard_category_counts do %> <%= for category <- @dashboard_category_counts do %>
<span class="dashboard-tag dashboard-category" title={ShellData.dashboard_post_count_label(category.count || 0)}> <span class="dashboard-tag dashboard-category" title={ShellData.dashboard_post_count_label(category.count || 0)}>
@@ -347,7 +347,7 @@
<%= if Enum.any?(@dashboard_recent_posts) do %> <%= if Enum.any?(@dashboard_recent_posts) do %>
<div class="dashboard-section"> <div class="dashboard-section">
<h4><%= translated("dashboard.section.recentlyUpdated") %></h4> <h4><%= dgettext("ui", "Recently Updated") %></h4>
<div class="recent-posts-list"> <div class="recent-posts-list">
<%= for post <- @dashboard_recent_posts do %> <%= for post <- @dashboard_recent_posts do %>
<button <button
@@ -373,8 +373,8 @@
<div class="dashboard-inspector-meta" hidden> <div class="dashboard-inspector-meta" hidden>
<%= for item <- @editor_meta do %> <%= for item <- @editor_meta do %>
<section class="editor-meta-row"> <section class="editor-meta-row">
<strong data-testid="editor-meta-label"><%= translated(item.label) %></strong> <strong data-testid="editor-meta-label"><%= item.label %></strong>
<span><%= translated(item.value) %></span> <span><%= item.value %></span>
</section> </section>
<% end %> <% end %>
</div> </div>
@@ -442,8 +442,8 @@
<aside class="editor-meta"> <aside class="editor-meta">
<%= for item <- @editor_meta do %> <%= for item <- @editor_meta do %>
<section class="editor-meta-row"> <section class="editor-meta-row">
<strong data-testid="editor-meta-label"><%= translated(item.label) %></strong> <strong data-testid="editor-meta-label"><%= item.label %></strong>
<span><%= translated(item.value) %></span> <span><%= item.value %></span>
</section> </section>
<% end %> <% end %>
</aside> </aside>
@@ -471,8 +471,8 @@
data-testid="panel-close" data-testid="panel-close"
type="button" type="button"
phx-click="toggle_panel" phx-click="toggle_panel"
aria-label={translated("Close panel")} aria-label={dgettext("ui", "Close panel")}
title={translated("Close panel")} title={dgettext("ui", "Close panel")}
> >
× ×
</button> </button>
@@ -493,24 +493,24 @@
<div class="assistant-content"> <div class="assistant-content">
<header class="assistant-sidebar-header"> <header class="assistant-sidebar-header">
<div class="assistant-sidebar-heading"> <div class="assistant-sidebar-heading">
<strong><%= translated("AI Assistant") %></strong> <strong><%= dgettext("ui", "AI Assistant") %></strong>
<span class="assistant-sidebar-description"><%= translated("AI conversations") %></span> <span class="assistant-sidebar-description"><%= dgettext("ui", "AI conversations") %></span>
</div> </div>
<span class={[ <span class={[
"assistant-sidebar-status", "assistant-sidebar-status",
if(@offline_mode, do: "is-offline", else: "is-online") if(@offline_mode, do: "is-offline", else: "is-online")
]}> ]}>
<%= if @offline_mode, do: translated("Offline"), else: translated("Chat") %> <%= if @offline_mode, do: dgettext("ui", "Offline"), else: dgettext("ui", "Chat") %>
</span> </span>
</header> </header>
<section class="assistant-sidebar-context" data-testid="assistant-context"> <section class="assistant-sidebar-context" data-testid="assistant-context">
<div class="assistant-sidebar-context-row"> <div class="assistant-sidebar-context-row">
<span class="assistant-sidebar-context-label"><%= translated("Project") %></span> <span class="assistant-sidebar-context-label"><%= dgettext("ui", "Project") %></span>
<span class="assistant-sidebar-context-value"><%= BDS.Desktop.ShellLive.ChatSurface.assistant_project_name(@current_project) %></span> <span class="assistant-sidebar-context-value"><%= BDS.Desktop.ShellLive.ChatSurface.assistant_project_name(@current_project) %></span>
</div> </div>
<div class="assistant-sidebar-context-row"> <div class="assistant-sidebar-context-row">
<span class="assistant-sidebar-context-label"><%= translated("Editor") %></span> <span class="assistant-sidebar-context-label"><%= dgettext("ui", "Editor") %></span>
<span class="assistant-sidebar-context-value"><%= BDS.Desktop.ShellLive.TabHelpers.tab_title(@current_tab, @tab_meta) %></span> <span class="assistant-sidebar-context-value"><%= BDS.Desktop.ShellLive.TabHelpers.tab_title(@current_tab, @tab_meta) %></span>
</div> </div>
<p class="assistant-sidebar-context-text"><%= BDS.Desktop.ShellLive.TabHelpers.tab_subtitle(@current_tab, @tab_meta) %></p> <p class="assistant-sidebar-context-text"><%= BDS.Desktop.ShellLive.TabHelpers.tab_subtitle(@current_tab, @tab_meta) %></p>
@@ -527,7 +527,7 @@
data-testid="assistant-prompt-input" data-testid="assistant-prompt-input"
name="assistant[prompt]" name="assistant[prompt]"
rows="6" rows="6"
placeholder={translated("Ask the assistant about the active project or editor.")} placeholder={dgettext("ui", "Ask the assistant about the active project or editor.")}
><%= @assistant_prompt %></textarea> ><%= @assistant_prompt %></textarea>
<button <button
@@ -536,7 +536,7 @@
type="submit" type="submit"
disabled={String.trim(@assistant_prompt || "") == ""} disabled={String.trim(@assistant_prompt || "") == ""}
> >
<%= translated("Start chat") %> <%= dgettext("ui", "Start chat") %>
</button> </button>
</form> </form>
@@ -544,8 +544,8 @@
<div class="assistant-sidebar-welcome"> <div class="assistant-sidebar-welcome">
<%= for card <- @assistant_cards do %> <%= for card <- @assistant_cards do %>
<section class="assistant-card"> <section class="assistant-card">
<strong><%= translated(card.label) %></strong> <strong><%= card.label %></strong>
<span><%= translated(card.text) %></span> <span><%= card.text %></span>
</section> </section>
<% end %> <% end %>
</div> </div>
@@ -576,8 +576,8 @@
data-testid="toggle-sidebar" data-testid="toggle-sidebar"
type="button" type="button"
phx-click="toggle_sidebar" phx-click="toggle_sidebar"
aria-label={translated("Toggle sidebar")} aria-label={dgettext("ui", "Toggle sidebar")}
title={translated("Toggle sidebar")} title={dgettext("ui", "Toggle sidebar")}
> >
<span class={["window-titlebar-sidebar-icon", if(@workbench.sidebar_visible, do: "is-active", else: "is-inactive")]}> <span class={["window-titlebar-sidebar-icon", if(@workbench.sidebar_visible, do: "is-active", else: "is-inactive")]}>
<span class="window-titlebar-sidebar-pane"></span> <span class="window-titlebar-sidebar-pane"></span>
@@ -588,8 +588,8 @@
data-testid="toggle-panel" data-testid="toggle-panel"
type="button" type="button"
phx-click="toggle_panel" phx-click="toggle_panel"
aria-label={translated("Toggle panel")} aria-label={dgettext("ui", "Toggle panel")}
title={translated("Toggle panel")} title={dgettext("ui", "Toggle panel")}
> >
<span class={["window-titlebar-panel-icon", if(@workbench.panel.visible, do: "is-active", else: "is-inactive")]}> <span class={["window-titlebar-panel-icon", if(@workbench.panel.visible, do: "is-active", else: "is-inactive")]}>
<span class="window-titlebar-panel-pane"></span> <span class="window-titlebar-panel-pane"></span>
@@ -600,8 +600,8 @@
data-testid="toggle-assistant" data-testid="toggle-assistant"
type="button" type="button"
phx-click="toggle_assistant_sidebar" phx-click="toggle_assistant_sidebar"
aria-label={translated("Toggle assistant")} aria-label={dgettext("ui", "Toggle assistant")}
title={translated("Toggle assistant")} title={dgettext("ui", "Toggle assistant")}
> >
<span class={["window-titlebar-assistant-icon", if(@workbench.assistant_sidebar_visible, do: "is-active", else: "is-inactive")]}> <span class={["window-titlebar-assistant-icon", if(@workbench.assistant_sidebar_visible, do: "is-active", else: "is-inactive")]}>
<span class="window-titlebar-assistant-pane"></span> <span class="window-titlebar-assistant-pane"></span>
@@ -614,7 +614,7 @@
class="project-selector-trigger" class="project-selector-trigger"
data-testid="project-selector-trigger" data-testid="project-selector-trigger"
type="button" type="button"
title={translated("Switch project")} title={dgettext("ui", "Switch project")}
phx-click="toggle_project_menu" phx-click="toggle_project_menu"
> >
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" class="project-icon"> <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" class="project-icon">
@@ -629,7 +629,7 @@
<%= if @project_menu_open do %> <%= if @project_menu_open do %>
<div class="project-dropdown" data-testid="project-dropdown" phx-click-away="close_project_menu"> <div class="project-dropdown" data-testid="project-dropdown" phx-click-away="close_project_menu">
<div class="project-dropdown-header"> <div class="project-dropdown-header">
<span><%= translated("Projects") %></span> <span><%= dgettext("ui", "Projects") %></span>
</div> </div>
<div class="project-list"> <div class="project-list">
<%= for project <- @projects.projects do %> <%= for project <- @projects.projects do %>
@@ -650,10 +650,10 @@
</div> </div>
<div class="project-dropdown-footer"> <div class="project-dropdown-footer">
<button class="existing-project-btn" type="button" phx-click="import_project"> <button class="existing-project-btn" type="button" phx-click="import_project">
<span><%= translated("Open Existing Blog") %></span> <span><%= dgettext("ui", "Open Existing Blog") %></span>
</button> </button>
<button class="create-project-btn" type="button" phx-click="create_project"> <button class="create-project-btn" type="button" phx-click="create_project">
<span><%= translated("New Project") %></span> <span><%= dgettext("ui", "New Project") %></span>
</button> </button>
</div> </div>
</div> </div>
@@ -663,7 +663,7 @@
<%= if @status.left.running_task_message do %> <%= if @status.left.running_task_message do %>
<span class="task-spinner"></span> <span class="task-spinner"></span>
<% end %> <% end %>
<span class="task-message-text"><%= @status.left.running_task_message || translated("Idle") %></span> <span class="task-message-text"><%= @status.left.running_task_message || dgettext("ui", "Idle") %></span>
<%= if (@status.left.running_task_overflow || 0) > 0 do %> <%= if (@status.left.running_task_overflow || 0) > 0 do %>
<span class="status-bar-count">+<%= @status.left.running_task_overflow %></span> <span class="status-bar-count">+<%= @status.left.running_task_overflow %></span>
<% end %> <% end %>
@@ -673,9 +673,9 @@
<span class="status-bar-item"><%= @status.right.post_count %></span> <span class="status-bar-item"><%= @status.right.post_count %></span>
<span class="status-bar-item"><%= @status.right.media_count %></span> <span class="status-bar-item"><%= @status.right.media_count %></span>
<span class="status-bar-item theme-badge"><%= @status.right.theme_badge %></span> <span class="status-bar-item theme-badge"><%= @status.right.theme_badge %></span>
<button class={["status-bar-item", "offline-badge", if(@status.right.offline_mode, do: "active")]} data-testid="status-offline-button" type="button" phx-click="toggle_offline_mode" title={translated("Toggle offline mode")}>✈</button> <button class={["status-bar-item", "offline-badge", if(@status.right.offline_mode, do: "active")]} data-testid="status-offline-button" type="button" phx-click="toggle_offline_mode" title={dgettext("ui", "Toggle offline mode")}>✈</button>
<form class="status-bar-item language-badge" data-testid="status-language-form" phx-change="change_ui_language"> <form class="status-bar-item language-badge" data-testid="status-language-form" phx-change="change_ui_language">
<span><%= translated("UI") %></span> <span><%= dgettext("ui", "UI") %></span>
<select class="status-bar-language-select" name="ui_language" data-testid="status-language-select"> <select class="status-bar-language-select" name="ui_language" data-testid="status-language-select">
<%= for language <- @supported_ui_languages do %> <%= for language <- @supported_ui_languages do %>
<option selected={language.code == @page_language} value={language.code}><%= language.flag %></option> <option selected={language.code == @page_language} value={language.code}><%= language.flag %></option>

View File

@@ -5,12 +5,13 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
import Ecto.Query import Ecto.Query
alias BDS.Desktop.{FilePicker, ShellData} alias BDS.Desktop.{FilePicker}
alias BDS.{AI, I18n, Media} alias BDS.{AI, I18n, Media}
alias BDS.Media.Media, as: MediaRecord alias BDS.Media.Media, as: MediaRecord
alias BDS.Media.Translation alias BDS.Media.Translation
alias BDS.Posts.Post alias BDS.Posts.Post
alias BDS.Repo alias BDS.Repo
use Gettext, backend: BDS.Gettext
embed_templates("media_editor_html/*") embed_templates("media_editor_html/*")
@@ -111,7 +112,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
def handle_event("replace_media_editor_file", _params, socket) do def handle_event("replace_media_editor_file", _params, socket) do
media = socket.assigns.media media = socket.assigns.media
case FilePicker.choose_file(translated("Replace Media File")) do case FilePicker.choose_file(dgettext("ui", "Replace Media File")) do
{:ok, source_path} -> {:ok, source_path} ->
case Media.replace_media_file(media.id, source_path) do case Media.replace_media_file(media.id, source_path) do
{:ok, %MediaRecord{} = updated_media} -> {:ok, %MediaRecord{} = updated_media} ->
@@ -130,7 +131,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Replace File"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Replace File"), inspect(reason), "error")
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
end end
@@ -138,7 +139,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, socket} {:noreply, socket}
{:error, %{message: message}} -> {:error, %{message: message}} ->
notify_output(socket, translated("Replace File"), message, "error") notify_output(socket, dgettext("ui", "Replace File"), message, "error")
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
end end
end end
@@ -147,8 +148,8 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
if socket.assigns.offline_mode do if socket.assigns.offline_mode do
notify_output( notify_output(
socket, socket,
translated("Detect Language"), dgettext("ui", "Detect Language"),
translated("Automatic AI actions stay gated by airplane mode."), dgettext("ui", "Automatic AI actions stay gated by airplane mode."),
"info" "info"
) )
@@ -186,19 +187,19 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, socket} {:noreply, socket}
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Detect Language"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Detect Language"), inspect(reason), "error")
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
end end
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Detect Language"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Detect Language"), inspect(reason), "error")
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
_other -> _other ->
notify_output( notify_output(
socket, socket,
translated("Detect Language"), dgettext("ui", "Detect Language"),
translated("Language detection failed."), dgettext("ui", "Language detection failed."),
"error" "error"
) )
@@ -240,7 +241,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, socket} {:noreply, socket}
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Link to Post"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Link to Post"), inspect(reason), "error")
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
end end
end end
@@ -253,7 +254,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Unlink from Post"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Unlink from Post"), inspect(reason), "error")
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
end end
end end
@@ -312,7 +313,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, socket} {:noreply, socket}
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Save Translation"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Save Translation"), inspect(reason), "error")
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
end end
@@ -336,8 +337,8 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
if socket.assigns.offline_mode do if socket.assigns.offline_mode do
notify_output( notify_output(
socket, socket,
translated("Translate"), dgettext("ui", "Translate"),
translated("Automatic AI actions stay gated by airplane mode."), dgettext("ui", "Automatic AI actions stay gated by airplane mode."),
"info" "info"
) )
@@ -350,12 +351,12 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Refresh Translation"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Refresh Translation"), inspect(reason), "error")
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
end end
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Refresh Translation"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Refresh Translation"), inspect(reason), "error")
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
end end
end end
@@ -374,7 +375,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, socket} {:noreply, socket}
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Delete Translation"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Delete Translation"), inspect(reason), "error")
{:noreply, build_data(socket)} {:noreply, build_data(socket)}
end end
end end
@@ -469,11 +470,11 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
notify_parent({:media_editor_dirty, media.id, false}) notify_parent({:media_editor_dirty, media.id, false})
notify_parent({:media_editor_tab_meta, media.id, display_title(updated_media), updated_media.original_name || updated_media.mime_type || ""}) notify_parent({:media_editor_tab_meta, media.id, display_title(updated_media), updated_media.original_name || updated_media.mime_type || ""})
notify_output(socket, translated("Media"), translated("Media saved")) notify_output(socket, dgettext("ui", "Media"), dgettext("ui", "Media saved"))
socket socket
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Media"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Media"), inspect(reason), "error")
|> build_data() |> build_data()
end end
end end
@@ -516,8 +517,8 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
if socket.assigns.offline_mode do if socket.assigns.offline_mode do
notify_output( notify_output(
socket, socket,
translated("Translate"), dgettext("ui", "Translate"),
translated("Automatic AI actions stay gated by airplane mode."), dgettext("ui", "Automatic AI actions stay gated by airplane mode."),
"info" "info"
) )
@@ -537,12 +538,12 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|> build_data() |> build_data()
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Translate"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Translate"), inspect(reason), "error")
|> build_data() |> build_data()
end end
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Translate"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Translate"), inspect(reason), "error")
|> build_data() |> build_data()
end end
end end
@@ -683,14 +684,11 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
end end
end end
@spec translated(term(), term()) :: term()
def translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
@spec media_editor_save_state_label(term()) :: term() @spec media_editor_save_state_label(term()) :: term()
def media_editor_save_state_label(:dirty), do: translated("Unsaved") def media_editor_save_state_label(:dirty), do: dgettext("ui", "Unsaved")
def media_editor_save_state_label(:saved), do: translated("Saved") def media_editor_save_state_label(:saved), do: dgettext("ui", "Saved")
def media_editor_save_state_label(_state), do: translated("Idle") def media_editor_save_state_label(_state), do: dgettext("ui", "Idle")
@spec language_label(term()) :: term() @spec language_label(term()) :: term()
def language_label(code) do def language_label(code) do

View File

@@ -8,7 +8,7 @@
]}> ]}>
<span class="editor-tab-title" data-testid="editor-title"><%= @media_editor.display_title %></span> <span class="editor-tab-title" data-testid="editor-title"><%= @media_editor.display_title %></span>
<%= if @media_editor.dirty? do %> <%= if @media_editor.dirty? do %>
<span class="editor-tab-dirty" title={translated("Unsaved")}>●</span> <span class="editor-tab-dirty" title={dgettext("ui", "Unsaved")}>●</span>
<% end %> <% end %>
</div> </div>
</div> </div>
@@ -26,7 +26,7 @@
phx-target={@myself} phx-target={@myself}
> >
<span class="quick-actions-btn-icon">⚡</span> <span class="quick-actions-btn-icon">⚡</span>
<span class="quick-actions-btn-label"><%= translated("Quick Actions") %></span> <span class="quick-actions-btn-label"><%= dgettext("ui", "Quick Actions") %></span>
</button> </button>
<%= if @media_editor.quick_actions_open? do %> <%= if @media_editor.quick_actions_open? do %>
@@ -40,8 +40,8 @@
phx-value-kind="ai_suggestions" phx-value-kind="ai_suggestions"
> >
<span class="quick-action-text"> <span class="quick-action-text">
<strong><%= translated("AI Suggestions") %></strong> <strong><%= dgettext("ui", "AI Suggestions") %></strong>
<small><%= translated("Review title, alt text, and caption suggestions") %></small> <small><%= dgettext("ui", "Review title, alt text, and caption suggestions") %></small>
</span> </span>
<span class="quick-action-icon">🤖</span> <span class="quick-action-icon">🤖</span>
</button> </button>
@@ -57,8 +57,8 @@
disabled={not @media_editor.can_detect_language?} disabled={not @media_editor.can_detect_language?}
> >
<span class="quick-action-text"> <span class="quick-action-text">
<strong><%= translated("Detect Language") %></strong> <strong><%= dgettext("ui", "Detect Language") %></strong>
<small><%= translated("Persist the detected language for this media item") %></small> <small><%= dgettext("ui", "Persist the detected language for this media item") %></small>
</span> </span>
<span class="quick-action-icon">🔍</span> <span class="quick-action-icon">🔍</span>
</button> </button>
@@ -74,8 +74,8 @@
disabled={not @media_editor.can_translate?} disabled={not @media_editor.can_translate?}
> >
<span class="quick-action-text"> <span class="quick-action-text">
<strong><%= translated("Translate") %></strong> <strong><%= dgettext("ui", "Translate") %></strong>
<small><%= translated("Select a target language for this media item") %></small> <small><%= dgettext("ui", "Select a target language for this media item") %></small>
</span> </span>
<span class="quick-action-icon">🌍</span> <span class="quick-action-icon">🌍</span>
</button> </button>
@@ -84,10 +84,10 @@
</div> </div>
<button class="secondary" type="button" phx-click="replace_media_editor_file" phx-target={@myself}> <button class="secondary" type="button" phx-click="replace_media_editor_file" phx-target={@myself}>
<%= translated("Replace File") %> <%= dgettext("ui", "Replace File") %>
</button> </button>
<button data-testid="media-save-button" type="button" phx-click="save_media_editor" phx-target={@myself}> <button data-testid="media-save-button" type="button" phx-click="save_media_editor" phx-target={@myself}>
<%= translated("Save") %> <%= dgettext("ui", "Save") %>
</button> </button>
<button <button
class="secondary danger" class="secondary danger"
@@ -96,7 +96,7 @@
phx-click="open_overlay" phx-click="open_overlay"
phx-value-kind="confirm_delete" phx-value-kind="confirm_delete"
> >
<%= translated("Delete") %> <%= dgettext("ui", "Delete") %>
</button> </button>
</div> </div>
</div> </div>
@@ -120,58 +120,58 @@
<div class="media-details"> <div class="media-details">
<form class="media-editor-details-form" data-testid="media-editor-form" phx-change="change_media_editor" phx-target={@myself}> <form class="media-editor-details-form" data-testid="media-editor-form" phx-change="change_media_editor" phx-target={@myself}>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("File Name") %></label> <label><%= dgettext("ui", "File Name") %></label>
<input class="post-editor-input disabled" type="text" value={@media_editor.original_name} disabled /> <input class="post-editor-input disabled" type="text" value={@media_editor.original_name} disabled />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("MIME Type") %></label> <label><%= dgettext("ui", "MIME Type") %></label>
<input class="post-editor-input disabled" type="text" value={@media_editor.mime_type} disabled /> <input class="post-editor-input disabled" type="text" value={@media_editor.mime_type} disabled />
</div> </div>
<div class="editor-field-row"> <div class="editor-field-row">
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Size") %></label> <label><%= dgettext("ui", "Size") %></label>
<input class="post-editor-input disabled" type="text" value={@media_editor.file_size} disabled /> <input class="post-editor-input disabled" type="text" value={@media_editor.file_size} disabled />
</div> </div>
<%= if @media_editor.dimensions do %> <%= if @media_editor.dimensions do %>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Dimensions") %></label> <label><%= dgettext("ui", "Dimensions") %></label>
<input class="post-editor-input disabled" type="text" value={@media_editor.dimensions} disabled /> <input class="post-editor-input disabled" type="text" value={@media_editor.dimensions} disabled />
</div> </div>
<% end %> <% end %>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Title") %></label> <label><%= dgettext("ui", "Title") %></label>
<input class="post-editor-input" type="text" name="media_editor[title]" value={@media_editor.form["title"]} /> <input class="post-editor-input" type="text" name="media_editor[title]" value={@media_editor.form["title"]} />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Alt Text") %></label> <label><%= dgettext("ui", "Alt Text") %></label>
<input class="post-editor-input" type="text" name="media_editor[alt]" value={@media_editor.form["alt"]} /> <input class="post-editor-input" type="text" name="media_editor[alt]" value={@media_editor.form["alt"]} />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Caption") %></label> <label><%= dgettext("ui", "Caption") %></label>
<textarea class="post-editor-textarea" name="media_editor[caption]" rows="3"><%= @media_editor.form["caption"] %></textarea> <textarea class="post-editor-textarea" name="media_editor[caption]" rows="3"><%= @media_editor.form["caption"] %></textarea>
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Tags") %></label> <label><%= dgettext("ui", "Tags") %></label>
<input class="post-editor-input" type="text" name="media_editor[tags]" value={@media_editor.form["tags"]} /> <input class="post-editor-input" type="text" name="media_editor[tags]" value={@media_editor.form["tags"]} />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Author") %></label> <label><%= dgettext("ui", "Author") %></label>
<input class="post-editor-input" type="text" name="media_editor[author]" value={@media_editor.form["author"]} /> <input class="post-editor-input" type="text" name="media_editor[author]" value={@media_editor.form["author"]} />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Language") %></label> <label><%= dgettext("ui", "Language") %></label>
<select class="post-editor-input" name="media_editor[language]"> <select class="post-editor-input" name="media_editor[language]">
<option value=""><%= translated("None") %></option> <option value=""><%= dgettext("ui", "None") %></option>
<%= for language <- @media_editor.languages do %> <%= for language <- @media_editor.languages do %>
<option value={language} selected={language == @media_editor.form["language"]}><%= language_label(language) %></option> <option value={language} selected={language == @media_editor.form["language"]}><%= language_label(language) %></option>
<% end %> <% end %>
@@ -181,10 +181,10 @@
<%= if @media_editor.form["language"] not in [nil, ""] do %> <%= if @media_editor.form["language"] not in [nil, ""] do %>
<div class="editor-field media-translations-section"> <div class="editor-field media-translations-section">
<label><%= translated("Translations") %></label> <label><%= dgettext("ui", "Translations") %></label>
<%= if Enum.empty?(@media_editor.translations) do %> <%= if Enum.empty?(@media_editor.translations) do %>
<div class="no-linked-posts"><%= translated("No translations") %></div> <div class="no-linked-posts"><%= dgettext("ui", "No translations") %></div>
<% else %> <% else %>
<div class="linked-posts-list"> <div class="linked-posts-list">
<%= for translation <- @media_editor.translations do %> <%= for translation <- @media_editor.translations do %>
@@ -199,7 +199,7 @@
<%= translation.flag %> <%= language_label(translation.language) %><%= if translation.title, do: " — #{translation.title}" %> <%= translation.flag %> <%= language_label(translation.language) %><%= if translation.title, do: " — #{translation.title}" %>
</button> </button>
<button class="secondary compact" type="button" phx-click="refresh_media_translation" phx-target={@myself} phx-value-language={translation.language}> <button class="secondary compact" type="button" phx-click="refresh_media_translation" phx-target={@myself} phx-value-language={translation.language}>
<%= translated("Refresh") %> <%= dgettext("ui", "Refresh") %>
</button> </button>
<button class="unlink-btn" type="button" phx-click="delete_media_translation" phx-target={@myself} phx-value-language={translation.language}>×</button> <button class="unlink-btn" type="button" phx-click="delete_media_translation" phx-target={@myself} phx-value-language={translation.language}>×</button>
</div> </div>
@@ -211,9 +211,9 @@
<div class="editor-field linked-posts-section"> <div class="editor-field linked-posts-section">
<label> <label>
<%= translated("Linked Posts") %> <%= dgettext("ui", "Linked Posts") %>
<button class="add-link-btn" type="button" phx-click="toggle_media_post_picker" phx-target={@myself}> <button class="add-link-btn" type="button" phx-click="toggle_media_post_picker" phx-target={@myself}>
<%= translated("Link to Post") %> <%= dgettext("ui", "Link to Post") %>
</button> </button>
</label> </label>
@@ -224,14 +224,14 @@
type="text" type="text"
name="media_post_picker[query]" name="media_post_picker[query]"
value={@media_editor.post_picker_query} value={@media_editor.post_picker_query}
placeholder={translated("Search posts")} placeholder={dgettext("ui", "Search posts")}
phx-change="change_media_post_picker" phx-change="change_media_post_picker"
phx-target={@myself} phx-target={@myself}
/> />
</div> </div>
<%= if Enum.empty?(@media_editor.post_picker_results) do %> <%= if Enum.empty?(@media_editor.post_picker_results) do %>
<div class="no-posts"><%= translated("No posts to link") %></div> <div class="no-posts"><%= dgettext("ui", "No posts to link") %></div>
<% else %> <% else %>
<div class="post-picker-list"> <div class="post-picker-list">
<%= for result <- @media_editor.post_picker_results do %> <%= for result <- @media_editor.post_picker_results do %>
@@ -240,7 +240,7 @@
</button> </button>
<% end %> <% end %>
<%= if @media_editor.post_picker_overflow_count > 0 do %> <%= if @media_editor.post_picker_overflow_count > 0 do %>
<div class="post-picker-more"><%= translated("and %{count} more", %{count: @media_editor.post_picker_overflow_count}) %></div> <div class="post-picker-more"><%= dgettext("ui", "and %{count} more", count: @media_editor.post_picker_overflow_count) %></div>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
@@ -248,7 +248,7 @@
<% end %> <% end %>
<%= if Enum.empty?(@media_editor.linked_posts) do %> <%= if Enum.empty?(@media_editor.linked_posts) do %>
<div class="no-linked-posts"><%= translated("Not linked to any posts") %></div> <div class="no-linked-posts"><%= dgettext("ui", "Not linked to any posts") %></div>
<% else %> <% else %>
<div class="linked-posts-list"> <div class="linked-posts-list">
<%= for linked_post <- @media_editor.linked_posts do %> <%= for linked_post <- @media_editor.linked_posts do %>
@@ -277,27 +277,27 @@
<div class="translation-modal-backdrop"> <div class="translation-modal-backdrop">
<div class="translation-modal"> <div class="translation-modal">
<div class="translation-modal-header"> <div class="translation-modal-header">
<h2><%= translated("Edit Translation") %></h2> <h2><%= dgettext("ui", "Edit Translation") %></h2>
<button class="translation-modal-close" type="button" phx-click="close_media_translation_editor" phx-target={@myself}>×</button> <button class="translation-modal-close" type="button" phx-click="close_media_translation_editor" phx-target={@myself}>×</button>
</div> </div>
<form class="translation-modal-body" phx-change="change_media_translation" phx-target={@myself}> <form class="translation-modal-body" phx-change="change_media_translation" phx-target={@myself}>
<input type="hidden" name="media_translation[language]" value={@media_editor.editing_translation["language"]} /> <input type="hidden" name="media_translation[language]" value={@media_editor.editing_translation["language"]} />
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Title") %></label> <label><%= dgettext("ui", "Title") %></label>
<input class="post-editor-input" type="text" name="media_translation[title]" value={@media_editor.editing_translation["title"]} /> <input class="post-editor-input" type="text" name="media_translation[title]" value={@media_editor.editing_translation["title"]} />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Alt Text") %></label> <label><%= dgettext("ui", "Alt Text") %></label>
<input class="post-editor-input" type="text" name="media_translation[alt]" value={@media_editor.editing_translation["alt"]} /> <input class="post-editor-input" type="text" name="media_translation[alt]" value={@media_editor.editing_translation["alt"]} />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Caption") %></label> <label><%= dgettext("ui", "Caption") %></label>
<textarea class="post-editor-textarea" name="media_translation[caption]" rows="3"><%= @media_editor.editing_translation["caption"] %></textarea> <textarea class="post-editor-textarea" name="media_translation[caption]" rows="3"><%= @media_editor.editing_translation["caption"] %></textarea>
</div> </div>
</form> </form>
<div class="translation-modal-footer"> <div class="translation-modal-footer">
<button class="secondary" type="button" phx-click="close_media_translation_editor" phx-target={@myself}><%= translated("Cancel") %></button> <button class="secondary" type="button" phx-click="close_media_translation_editor" phx-target={@myself}><%= dgettext("ui", "Cancel") %></button>
<button type="button" phx-click="save_media_translation" phx-target={@myself}><%= translated("Save") %></button> <button type="button" phx-click="save_media_translation" phx-target={@myself}><%= dgettext("ui", "Save") %></button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,7 +3,8 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
use Phoenix.LiveComponent use Phoenix.LiveComponent
alias BDS.Desktop.ShellData
use Gettext, backend: BDS.Gettext
alias BDS.Desktop.ShellLive.MenuEditor.{ alias BDS.Desktop.ShellLive.MenuEditor.{
DraftManagement, DraftManagement,
@@ -215,7 +216,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
Enum.map(state.items, &TreeOps.persisted_item/1) Enum.map(state.items, &TreeOps.persisted_item/1)
) )
notify_output(translated("menuEditor.tabTitle"), translated("menuEditor.saved"), "info") notify_output(dgettext("ui", "Blog Menu"), dgettext("ui", "Blog menu saved"), "info")
socket |> build_data() socket |> build_data()
end end
@@ -236,8 +237,8 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
tab_meta = tab_meta =
Map.put(socket.assigns.tab_meta, {:menu_editor, tab_id}, %{ Map.put(socket.assigns.tab_meta, {:menu_editor, tab_id}, %{
title: translated("menuEditor.tabTitle"), title: dgettext("ui", "Blog Menu"),
subtitle: translated("menuEditor.description") subtitle: dgettext("ui", "Manage the central blog navigation outline and save it to meta/menu.opml.")
}) })
socket socket
@@ -284,7 +285,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
phx-target={@myself} phx-target={@myself}
style={"--menu-editor-depth: #{@depth};"} style={"--menu-editor-depth: #{@depth};"}
> >
<span class="menu-editor-row-handle" data-menu-drag-handle="true" title={translated("menuEditor.dragHandle")}>⋮⋮</span> <span class="menu-editor-row-handle" data-menu-drag-handle="true" title={dgettext("ui", "Drag menu item")}>⋮⋮</span>
<span class="menu-editor-row-kind" title={kind_label(item.kind)} aria-label={kind_label(item.kind)}> <span class="menu-editor-row-kind" title={kind_label(item.kind)} aria-label={kind_label(item.kind)}>
<.kind_icon kind={item.kind} /> <.kind_icon kind={item.kind} />
</span> </span>
@@ -317,16 +318,16 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
<div class="menu-editor-inline-actions"> <div class="menu-editor-inline-actions">
<%= if @menu_editor.draft.type == :page do %> <%= if @menu_editor.draft.type == :page do %>
<button class="menu-editor-inline-action" data-testid="menu-editor-create-submenu" type="submit"> <button class="menu-editor-inline-action" data-testid="menu-editor-create-submenu" type="submit">
<%= translated("menuEditor.addSubmenu") %> <%= dgettext("ui", "Add Submenu") %>
</button> </button>
<% else %> <% else %>
<button class="menu-editor-inline-action" type="submit"> <button class="menu-editor-inline-action" type="submit">
<%= translated("menuEditor.addCategoryArchive") %> <%= dgettext("ui", "Add Category Archive") %>
</button> </button>
<% end %> <% end %>
<button class="menu-editor-inline-action" type="button" phx-click="cancel_menu_editor_entry" phx-target={@myself}> <button class="menu-editor-inline-action" type="button" phx-click="cancel_menu_editor_entry" phx-target={@myself}>
<%= translated("Cancel") %> <%= dgettext("ui", "Cancel") %>
</button> </button>
</div> </div>
</div> </div>
@@ -334,7 +335,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
<%= if @menu_editor.draft.type == :page do %> <%= if @menu_editor.draft.type == :page do %>
<div class="menu-editor-picker-list"> <div class="menu-editor-picker-list">
<%= if @menu_editor.filtered_pages == [] do %> <%= if @menu_editor.filtered_pages == [] do %>
<div class="menu-editor-picker-state"><%= translated("menuEditor.pagePicker.empty") %></div> <div class="menu-editor-picker-state"><%= dgettext("ui", "No matching pages found.") %></div>
<% else %> <% else %>
<%= for post <- @menu_editor.filtered_pages do %> <%= for post <- @menu_editor.filtered_pages do %>
<button <button
@@ -353,7 +354,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
<% else %> <% else %>
<div class="menu-editor-picker-list"> <div class="menu-editor-picker-list">
<%= if @menu_editor.filtered_categories == [] do %> <%= if @menu_editor.filtered_categories == [] do %>
<div class="menu-editor-picker-state"><%= translated("menuEditor.categoryPicker.empty") %></div> <div class="menu-editor-picker-state"><%= dgettext("ui", "No matching categories found.") %></div>
<% else %> <% else %>
<%= for category <- @menu_editor.filtered_categories do %> <%= for category <- @menu_editor.filtered_categories do %>
<button <button
@@ -406,9 +407,6 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
""" """
end end
@spec translated(term(), term()) :: term()
def translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
@spec row_label(term(), term()) :: term() @spec row_label(term(), term()) :: term()
def row_label(item, category_titles) do def row_label(item, category_titles) do
@@ -420,24 +418,24 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
end end
@spec kind_label(term()) :: term() @spec kind_label(term()) :: term()
def kind_label(:home), do: translated("menuEditor.type.home") def kind_label(:home), do: dgettext("ui", "Home")
def kind_label(:page), do: translated("menuEditor.type.page") def kind_label(:page), do: dgettext("ui", "Page")
def kind_label(:category_archive), do: translated("menuEditor.type.categoryArchive") def kind_label(:category_archive), do: dgettext("ui", "Category Archive")
def kind_label(:submenu), do: translated("menuEditor.type.submenu") def kind_label(:submenu), do: dgettext("ui", "Submenu")
defdelegate draft_item?(menu_editor, item_id), to: TreePredicates defdelegate draft_item?(menu_editor, item_id), to: TreePredicates
@spec editing_title(term()) :: term() @spec editing_title(term()) :: term()
def editing_title(%{draft: %{type: :category}}), do: translated("menuEditor.addCategoryArchive") def editing_title(%{draft: %{type: :category}}), do: dgettext("ui", "Add Category Archive")
def editing_title(_menu_editor), do: translated("menuEditor.pagePicker.title") def editing_title(_menu_editor), do: dgettext("ui", "Select Page")
@spec editing_hint(term()) :: term() @spec editing_hint(term()) :: term()
def editing_hint(%{draft: %{type: :category}}), do: translated("menuEditor.categoryPicker.hint") def editing_hint(%{draft: %{type: :category}}), do: dgettext("ui", "Select an existing category or press Enter to create a new archive entry")
def editing_hint(_menu_editor), do: translated("menuEditor.createHint") def editing_hint(_menu_editor), do: dgettext("ui", "Select a page below or press Enter to create a submenu")
@spec editing_placeholder(term()) :: term() @spec editing_placeholder(term()) :: term()
def editing_placeholder(%{draft: %{type: :category}}), def editing_placeholder(%{draft: %{type: :category}}),
do: translated("menuEditor.newCategoryPlaceholder") do: dgettext("ui", "Type a category name")
def editing_placeholder(_menu_editor), do: translated("menuEditor.newEntryPlaceholder") def editing_placeholder(_menu_editor), do: dgettext("ui", "Type a page title or submenu label")
end end

View File

@@ -1,10 +1,10 @@
defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
@moduledoc false @moduledoc false
alias BDS.Desktop.ShellData
alias BDS.Metadata alias BDS.Metadata
alias BDS.Desktop.ShellLive.MenuEditor.PageCategory alias BDS.Desktop.ShellLive.MenuEditor.PageCategory
alias BDS.Desktop.ShellLive.MenuEditor.TreeOps alias BDS.Desktop.ShellLive.MenuEditor.TreeOps
use Gettext, backend: BDS.Gettext
@spec current_draft(term()) :: term() @spec current_draft(term()) :: term()
def current_draft(assigns), do: Map.get(assigns.menu_editor_state || %{}, :draft) def current_draft(assigns), do: Map.get(assigns.menu_editor_state || %{}, :draft)
@@ -14,7 +14,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
item = %{ item = %{
item_id: Ecto.UUID.generate(), item_id: Ecto.UUID.generate(),
kind: :page, kind: :page,
label: translated("menuEditor.newPage"), label: dgettext("ui", "New Page"),
slug: nil, slug: nil,
children: [], children: [],
is_home: false is_home: false
@@ -57,7 +57,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
def finalize_submenu_draft(%{draft: %{item_id: item_id, query: query}} = state) do def finalize_submenu_draft(%{draft: %{item_id: item_id, query: query}} = state) do
label = label =
if(String.trim(query) == "", if(String.trim(query) == "",
do: translated("menuEditor.newSubmenu"), do: dgettext("ui", "New Submenu"),
else: String.trim(query) else: String.trim(query)
) )
@@ -144,7 +144,4 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
update_state_fun.(socket, &assign_category_to_draft(&1, category)) update_state_fun.(socket, &assign_category_to_draft(&1, category))
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -3,9 +3,9 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do
use Phoenix.Component use Phoenix.Component
alias BDS.Desktop.ShellData
alias BDS.Menu alias BDS.Menu
alias BDS.Desktop.ShellLive.MenuEditor.{PageCategory, TreeOps, TreePredicates} alias BDS.Desktop.ShellLive.MenuEditor.{PageCategory, TreeOps, TreePredicates}
use Gettext, backend: BDS.Gettext
@spec ensure_state(term()) :: term() @spec ensure_state(term()) :: term()
def ensure_state(assigns) do def ensure_state(assigns) do
@@ -30,8 +30,8 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do
draft_query = Map.get(draft || %{}, :query, "") draft_query = Map.get(draft || %{}, :query, "")
%{ %{
title: translated("menuEditor.title"), title: dgettext("ui", "Blog Menu Editor"),
description: translated("menuEditor.description"), description: dgettext("ui", "Manage the central blog navigation outline and save it to meta/menu.opml."),
items: state.items, items: state.items,
selected_id: state.selected_id, selected_id: state.selected_id,
draft: draft, draft: draft,
@@ -66,8 +66,8 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do
socket socket
|> append_output.( |> append_output.(
translated("menuEditor.tabTitle"), dgettext("ui", "Blog Menu"),
translated("menuEditor.saved"), dgettext("ui", "Blog menu saved"),
nil, nil,
"info" "info"
) )
@@ -94,7 +94,4 @@ defmodule BDS.Desktop.ShellLive.MenuEditor.State do
draft: nil draft: nil
} }
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -9,41 +9,41 @@
<div class="menu-editor-main"> <div class="menu-editor-main">
<div class="menu-editor-tree-wrap"> <div class="menu-editor-tree-wrap">
<div class="menu-editor-toolbar" data-testid="menu-editor-toolbar" role="toolbar" aria-label={@menu_editor.title}> <div class="menu-editor-toolbar" data-testid="menu-editor-toolbar" role="toolbar" aria-label={@menu_editor.title}>
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="add-entry" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="add-entry" phx-target={@myself} title={translated("menuEditor.addEntry")}> <button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="add-entry" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="add-entry" phx-target={@myself} title={dgettext("ui", "menuEditor.addEntry")}>
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M7 2h2v5h5v2H9v5H7V9H2V7h5V2z" /></svg> <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M7 2h2v5h5v2H9v5H7V9H2V7h5V2z" /></svg>
</button> </button>
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="save" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="save" phx-target={@myself} title={translated("menuEditor.save")}> <button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="save" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="save" phx-target={@myself} title={dgettext("ui", "menuEditor.save")}>
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2 2h9l3 3v9H2V2zm2 1v3h6V3H4zm0 9h8V7H4v5z" /></svg> <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2 2h9l3 3v9H2V2zm2 1v3h6V3H4zm0 9h8V7H4v5z" /></svg>
</button> </button>
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="add-category-archive" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="add-category-archive" phx-target={@myself} title={translated("menuEditor.addCategoryArchive")}> <button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="add-category-archive" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="add-category-archive" phx-target={@myself} title={dgettext("ui", "menuEditor.addCategoryArchive")}>
<span aria-hidden="true"><%= translated("menuEditor.addCategoryArchiveShort") %></span> <span aria-hidden="true"><%= dgettext("ui", "menuEditor.addCategoryArchiveShort") %></span>
</button> </button>
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="move-up" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="move-up" phx-target={@myself} title={translated("menuEditor.moveUp")} disabled={not @menu_editor.can_move_up?}> <button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="move-up" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="move-up" phx-target={@myself} title={dgettext("ui", "menuEditor.moveUp")} disabled={not @menu_editor.can_move_up?}>
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 3l4 4H9v6H7V7H4l4-4z" /></svg> <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 3l4 4H9v6H7V7H4l4-4z" /></svg>
</button> </button>
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="move-down" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="move-down" phx-target={@myself} title={translated("menuEditor.moveDown")} disabled={not @menu_editor.can_move_down?}> <button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="move-down" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="move-down" phx-target={@myself} title={dgettext("ui", "menuEditor.moveDown")} disabled={not @menu_editor.can_move_down?}>
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M7 3h2v6h3l-4 4-4-4h3V3z" /></svg> <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M7 3h2v6h3l-4 4-4-4h3V3z" /></svg>
</button> </button>
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="indent" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="indent" phx-target={@myself} title={translated("menuEditor.indent")} disabled={not @menu_editor.can_indent?}> <button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="indent" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="indent" phx-target={@myself} title={dgettext("ui", "menuEditor.indent")} disabled={not @menu_editor.can_indent?}>
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2 4h8v2H2V4zm0 3h4v2H2V7zm0 3h8v2H2v-2zm6-1 3 2-3 2V9z" /></svg> <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2 4h8v2H2V4zm0 3h4v2H2V7zm0 3h8v2H2v-2zm6-1 3 2-3 2V9z" /></svg>
</button> </button>
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="unindent" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="unindent" phx-target={@myself} title={translated("menuEditor.unindent")} disabled={not @menu_editor.can_unindent?}> <button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="unindent" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="unindent" phx-target={@myself} title={dgettext("ui", "menuEditor.unindent")} disabled={not @menu_editor.can_unindent?}>
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2 4h8v2H2V4zm0 3h4v2H2V7zm0 3h8v2H2v-2zm3-1-3 2 3 2V9z" /></svg> <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2 4h8v2H2V4zm0 3h4v2H2V7zm0 3h8v2H2v-2zm3-1-3 2 3 2V9z" /></svg>
</button> </button>
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="delete" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="delete" phx-target={@myself} title={translated("menuEditor.delete")} disabled={not @menu_editor.can_delete?}> <button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="delete" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="delete" phx-target={@myself} title={dgettext("ui", "menuEditor.delete")} disabled={not @menu_editor.can_delete?}>
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M6 2h4l1 1h3v2H2V3h3l1-1zm-1 4h2v6H5V6zm4 0h2v6H9V6z" /></svg> <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M6 2h4l1 1h3v2H2V3h3l1-1zm-1 4h2v6H5V6zm4 0h2v6H9V6z" /></svg>
</button> </button>
</div> </div>
<%= if @menu_editor.items == [] do %> <%= if @menu_editor.items == [] do %>
<div class="menu-editor-empty"><%= translated("menuEditor.empty") %></div> <div class="menu-editor-empty"><%= dgettext("ui", "menuEditor.empty") %></div>
<% else %> <% else %>
<div id="menu-editor-tree-shell" class="menu-editor-tree-shell" phx-hook="MenuEditorTree"> <div id="menu-editor-tree-shell" class="menu-editor-tree-shell" phx-hook="MenuEditorTree">
<ul class="menu-editor-tree-level"> <ul class="menu-editor-tree-level">

View File

@@ -6,9 +6,9 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
import Ecto.Query import Ecto.Query
alias BDS.{Embeddings, Generation, Git, Posts, Repo} alias BDS.{Embeddings, Generation, Git, Posts, Repo}
alias BDS.Desktop.ShellData
alias BDS.MapUtils alias BDS.MapUtils
alias BDS.Settings.Setting alias BDS.Settings.Setting
use Gettext, backend: BDS.Gettext
embed_templates("misc_editor_html/*") embed_templates("misc_editor_html/*")
@@ -87,13 +87,13 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
case Generation.apply_validation(project_id, report) do case Generation.apply_validation(project_id, report) do
{:ok, result} -> {:ok, result} ->
notify_output(translated("Site Validation"), translated("Validation changes applied"), inspect(result)) notify_output(dgettext("ui", "Site Validation"), dgettext("ui", "Validation changes applied"), inspect(result))
notify_command("validate_site") notify_command("validate_site")
{:noreply, socket} {:noreply, socket}
end end
rescue rescue
error -> error ->
notify_output(translated("Site Validation"), inspect(error), nil, "error") notify_output(dgettext("ui", "Site Validation"), inspect(error), nil, "error")
{:noreply, socket} {:noreply, socket}
end end
@@ -107,19 +107,19 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
{:ok, result} = Posts.fix_invalid_translations(report) {:ok, result} = Posts.fix_invalid_translations(report)
notify_output( notify_output(
translated("Translation Validation"), dgettext("ui", "Translation Validation"),
translated("translationValidation.toast.fixSuccess", %{ dgettext("ui", "Deleted %{dbRows} DB rows and %{files} files, flushed %{flushed} translations to disk",
dbRows: result.deleted_database_rows, dbRows: result.deleted_database_rows,
files: result.deleted_files, files: result.deleted_files,
flushed: result.flushed_translations flushed: result.flushed_translations
}) )
) )
notify_command("validate_translations") notify_command("validate_translations")
{:noreply, socket} {:noreply, socket}
rescue rescue
error -> error ->
notify_output(translated("Translation Validation"), inspect(error), nil, "error") notify_output(dgettext("ui", "Translation Validation"), inspect(error), nil, "error")
{:noreply, socket} {:noreply, socket}
end end
@@ -162,13 +162,13 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
next_payload = Map.put(payload, :pairs, next_pairs) next_payload = Map.put(payload, :pairs, next_pairs)
notify_tab_meta(tab_type, tab_id, %{payload: next_payload}) notify_tab_meta(tab_type, tab_id, %{payload: next_payload})
notify_output(translated("Find Duplicates"), translated("Pair dismissed")) notify_output(dgettext("ui", "Find Duplicates"), dgettext("ui", "Pair dismissed"))
selected = MapSet.delete(socket.assigns.selected_pairs, pair_id) selected = MapSet.delete(socket.assigns.selected_pairs, pair_id)
{:noreply, assign(socket, :selected_pairs, selected) |> build_data()} {:noreply, assign(socket, :selected_pairs, selected) |> build_data()}
{:error, reason} -> {:error, reason} ->
notify_output(translated("Find Duplicates"), inspect(reason), nil, "error") notify_output(dgettext("ui", "Find Duplicates"), inspect(reason), nil, "error")
{:noreply, socket} {:noreply, socket}
end end
end end
@@ -193,12 +193,12 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
next_payload = Map.put(payload, :pairs, next_pairs) next_payload = Map.put(payload, :pairs, next_pairs)
notify_tab_meta(tab_type, tab_id, %{payload: next_payload}) notify_tab_meta(tab_type, tab_id, %{payload: next_payload})
notify_output(translated("Find Duplicates"), translated("Selected pairs dismissed")) notify_output(dgettext("ui", "Find Duplicates"), dgettext("ui", "Selected pairs dismissed"))
{:noreply, assign(socket, :selected_pairs, MapSet.new()) |> build_data()} {:noreply, assign(socket, :selected_pairs, MapSet.new()) |> build_data()}
{:error, reason} -> {:error, reason} ->
notify_output(translated("Find Duplicates"), inspect(reason), nil, "error") notify_output(dgettext("ui", "Find Duplicates"), inspect(reason), nil, "error")
{:noreply, socket} {:noreply, socket}
end end
end end
@@ -210,7 +210,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
{:noreply, socket} {:noreply, socket}
{:error, message} -> {:error, message} ->
notify_output(translated("Metadata Diff"), message, nil, "error") notify_output(dgettext("ui", "Metadata Diff"), message, nil, "error")
{:noreply, socket} {:noreply, socket}
end end
end end
@@ -222,7 +222,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
{:noreply, socket} {:noreply, socket}
{:error, message} -> {:error, message} ->
notify_output(translated("Metadata Diff"), message, nil, "error") notify_output(dgettext("ui", "Metadata Diff"), message, nil, "error")
{:noreply, socket} {:noreply, socket}
end end
end end
@@ -248,9 +248,6 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
# ── Public helper functions (used by template) ───────────────────────────── # ── Public helper functions (used by template) ─────────────────────────────
@spec translated(String.t(), map()) :: String.t()
def translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
@spec misc_class(atom()) :: String.t() @spec misc_class(atom()) :: String.t()
def misc_class(:site_validation), do: "site-validation-view" def misc_class(:site_validation), do: "site-validation-view"
@@ -273,16 +270,16 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
def translation_issue_label(issue) do def translation_issue_label(issue) do
case issue_value(issue, :issue) do case issue_value(issue, :issue) do
"same-language-as-canonical" -> "same-language-as-canonical" ->
translated("translationValidation.issue.sameLanguage") dgettext("ui", "Translation language matches canonical post language")
"do-not-translate-has-translations" -> "do-not-translate-has-translations" ->
translated("translationValidation.issue.doNotTranslate") dgettext("ui", "Post is marked as do-not-translate but has translations")
"content-in-database" -> "content-in-database" ->
translated("translationValidation.issue.contentInDatabase") dgettext("ui", "Published translation has content stuck in DB instead of filesystem")
_other -> _other ->
translated("translationValidation.issue.missingSource") dgettext("ui", "Translation points to a missing source post")
end end
end end
@@ -294,10 +291,10 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
if canonical_language in [nil, ""] do if canonical_language in [nil, ""] do
translation_language translation_language
else else
translated("translationValidation.languagesWithCanonical", %{ dgettext("ui", "%{canonical} = %{translation}",
canonical: canonical_language, canonical: canonical_language,
translation: translation_language translation: translation_language
}) )
end end
end end
@@ -373,7 +370,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
%{ %{
kind: :site_validation, kind: :site_validation,
title: Map.get(meta, :title, translated("Site Validation")), title: Map.get(meta, :title, dgettext("ui", "Site Validation")),
subtitle: Map.get(meta, :subtitle, ""), subtitle: Map.get(meta, :subtitle, ""),
summary: %{ summary: %{
expected: Map.get(summary, :expected_count, 0), expected: Map.get(summary, :expected_count, 0),
@@ -409,7 +406,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
%{ %{
kind: :metadata_diff, kind: :metadata_diff,
title: Map.get(meta, :title, translated("Metadata Diff")), title: Map.get(meta, :title, dgettext("ui", "Metadata Diff")),
subtitle: Map.get(meta, :subtitle, ""), subtitle: Map.get(meta, :subtitle, ""),
summary: Map.get(payload, :summary, %{}), summary: Map.get(payload, :summary, %{}),
tabs: tabs:
@@ -420,7 +417,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
field_summaries: field_summaries(current_tab.items), field_summaries: field_summaries(current_tab.items),
items: filtered_items, items: filtered_items,
orphan_files: if(is_nil(active_field), do: current_tab.orphan_files, else: []), orphan_files: if(is_nil(active_field), do: current_tab.orphan_files, else: []),
empty_message: translated("No items") empty_message: dgettext("ui", "No items")
} }
end end
@@ -429,16 +426,16 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
%{ %{
kind: :translation_validation, kind: :translation_validation,
title: Map.get(meta, :title, translated("Translation Validation")), title: Map.get(meta, :title, dgettext("ui", "Translation Validation")),
subtitle: Map.get(meta, :subtitle, ""), subtitle: Map.get(meta, :subtitle, ""),
summary: %{}, summary: %{},
summary_text: summary_text:
translated("translationValidation.summary", %{ dgettext("ui", "Checked DB rows: %{dbRows} · Checked files: %{files} · Invalid DB rows: %{invalidDb} · Invalid files: %{invalidFiles}",
dbRows: report.checked_database_row_count, dbRows: report.checked_database_row_count,
files: report.checked_filesystem_file_count, files: report.checked_filesystem_file_count,
invalidDb: length(report.invalid_database_rows), invalidDb: length(report.invalid_database_rows),
invalidFiles: length(report.invalid_filesystem_files) invalidFiles: length(report.invalid_filesystem_files)
}), ),
invalid_database_rows: report.invalid_database_rows, invalid_database_rows: report.invalid_database_rows,
invalid_filesystem_files: report.invalid_filesystem_files, invalid_filesystem_files: report.invalid_filesystem_files,
can_fix?: report.invalid_database_rows != [] or report.invalid_filesystem_files != [] can_fix?: report.invalid_database_rows != [] or report.invalid_filesystem_files != []
@@ -448,7 +445,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
defp build_duplicates(assigns, meta, payload) do defp build_duplicates(assigns, meta, payload) do
%{ %{
kind: :find_duplicates, kind: :find_duplicates,
title: Map.get(meta, :title, translated("Find Duplicates")), title: Map.get(meta, :title, dgettext("ui", "Find Duplicates")),
subtitle: Map.get(meta, :subtitle, ""), subtitle: Map.get(meta, :subtitle, ""),
summary: Map.get(payload, :summary, %{}), summary: Map.get(payload, :summary, %{}),
pairs: Map.get(payload, :pairs, []), pairs: Map.get(payload, :pairs, []),
@@ -505,14 +502,14 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
%{ %{
kind: :git_diff, kind: :git_diff,
title: Map.get(meta, :title, translated("Git Diff")), title: Map.get(meta, :title, dgettext("ui", "Git Diff")),
subtitle: Map.get(meta, :subtitle, ""), subtitle: Map.get(meta, :subtitle, ""),
summary: %{}, summary: %{},
files: files, files: files,
selected_file_path: diff.file_path, selected_file_path: diff.file_path,
active_diff: Map.put(diff, :language, git_diff_language(diff.file_path)), active_diff: Map.put(diff, :language, git_diff_language(diff.file_path)),
preferences: preferences, preferences: preferences,
empty_message: error_message || translated("No unstaged changes") empty_message: error_message || dgettext("ui", "No unstaged changes")
} }
end end
@@ -534,10 +531,10 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
cond do cond do
not metadata_diff_repairable_tab?(active_tab) -> not metadata_diff_repairable_tab?(active_tab) ->
{:error, translated("No repair action available")} {:error, dgettext("ui", "No repair action available")}
repair_items == [] -> repair_items == [] ->
{:error, translated("No metadata diff items selected")} {:error, dgettext("ui", "No metadata diff items selected")}
true -> true ->
{:ok, {:ok,
@@ -566,7 +563,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|> Enum.map(&%{"file_path" => &1.file_path}) |> Enum.map(&%{"file_path" => &1.file_path})
if selected_orphans == [] do if selected_orphans == [] do
{:error, translated("No orphan files selected")} {:error, dgettext("ui", "No orphan files selected")}
else else
{:ok, %{"tab" => active_tab, "orphans" => selected_orphans}} {:ok, %{"tab" => active_tab, "orphans" => selected_orphans}}
end end
@@ -623,7 +620,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
defp empty_metadata_diff_tab do defp empty_metadata_diff_tab do
%{ %{
id: "posts", id: "posts",
label: translated("Posts"), label: dgettext("ui", "Posts"),
items: [], items: [],
orphan_files: [], orphan_files: [],
diff_count: 0, diff_count: 0,
@@ -692,17 +689,17 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
MapUtils.attr(item, :meta_label) || entity_id MapUtils.attr(item, :meta_label) || entity_id
end end
defp metadata_diff_item_type_label("post"), do: translated("Post") defp metadata_diff_item_type_label("post"), do: dgettext("ui", "Post")
defp metadata_diff_item_type_label("post_translation"), do: translated("Translations") defp metadata_diff_item_type_label("post_translation"), do: dgettext("ui", "Translations")
defp metadata_diff_item_type_label("media"), do: translated("Media") defp metadata_diff_item_type_label("media"), do: dgettext("ui", "Media")
defp metadata_diff_item_type_label("media_translation"), do: translated("Translations") defp metadata_diff_item_type_label("media_translation"), do: dgettext("ui", "Translations")
defp metadata_diff_item_type_label("script"), do: translated("Script") defp metadata_diff_item_type_label("script"), do: dgettext("ui", "Script")
defp metadata_diff_item_type_label("template"), do: translated("Template") defp metadata_diff_item_type_label("template"), do: dgettext("ui", "Template")
defp metadata_diff_item_type_label("project"), do: translated("Project") defp metadata_diff_item_type_label("project"), do: dgettext("ui", "Project")
defp metadata_diff_item_type_label("publishing"), do: translated("Publishing") defp metadata_diff_item_type_label("publishing"), do: dgettext("ui", "Publishing")
defp metadata_diff_item_type_label("categories"), do: translated("Categories") defp metadata_diff_item_type_label("categories"), do: dgettext("ui", "Categories")
defp metadata_diff_item_type_label("category_meta"), do: translated("Categories") defp metadata_diff_item_type_label("category_meta"), do: dgettext("ui", "Categories")
defp metadata_diff_item_type_label("embedding"), do: translated("Embeddings") defp metadata_diff_item_type_label("embedding"), do: dgettext("ui", "Embeddings")
defp metadata_diff_item_type_label(entity_type), defp metadata_diff_item_type_label(entity_type),
do: entity_type |> String.replace("_", " ") |> String.capitalize() do: entity_type |> String.replace("_", " ") |> String.capitalize()
@@ -720,12 +717,12 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
defp metadata_diff_tab_id("embedding"), do: "embeddings" defp metadata_diff_tab_id("embedding"), do: "embeddings"
defp metadata_diff_tab_id(_entity_type), do: "project" defp metadata_diff_tab_id(_entity_type), do: "project"
defp metadata_diff_tab_label("posts"), do: translated("Posts") defp metadata_diff_tab_label("posts"), do: dgettext("ui", "Posts")
defp metadata_diff_tab_label("media"), do: translated("Media") defp metadata_diff_tab_label("media"), do: dgettext("ui", "Media")
defp metadata_diff_tab_label("scripts"), do: translated("Scripts") defp metadata_diff_tab_label("scripts"), do: dgettext("ui", "Scripts")
defp metadata_diff_tab_label("templates"), do: translated("Templates") defp metadata_diff_tab_label("templates"), do: dgettext("ui", "Templates")
defp metadata_diff_tab_label("project"), do: translated("Project") defp metadata_diff_tab_label("project"), do: dgettext("ui", "Project")
defp metadata_diff_tab_label("embeddings"), do: translated("Embeddings") defp metadata_diff_tab_label("embeddings"), do: dgettext("ui", "Embeddings")
defp metadata_diff_tab_label(tab_id), defp metadata_diff_tab_label(tab_id),
do: tab_id |> String.replace("_", " ") |> String.capitalize() do: tab_id |> String.replace("_", " ") |> String.capitalize()

View File

@@ -5,12 +5,12 @@
<p><%= @misc_editor.subtitle %></p> <p><%= @misc_editor.subtitle %></p>
</div> </div>
<div class="misc-editor-actions"> <div class="misc-editor-actions">
<button class="secondary" type="button" phx-click="rerun_misc_editor" phx-target={@myself}><%= translated("Refresh") %></button> <button class="secondary" type="button" phx-click="rerun_misc_editor" phx-target={@myself}><%= dgettext("ui", "Refresh") %></button>
<%= if @misc_editor.kind == :site_validation do %> <%= if @misc_editor.kind == :site_validation do %>
<button class="primary" type="button" phx-click="apply_site_validation" phx-target={@myself} disabled={Enum.empty?(@misc_editor.missing_url_paths) and Enum.empty?(@misc_editor.extra_url_paths) and Enum.empty?(@misc_editor.updated_post_url_paths)}><%= translated("Apply") %></button> <button class="primary" type="button" phx-click="apply_site_validation" phx-target={@myself} disabled={Enum.empty?(@misc_editor.missing_url_paths) and Enum.empty?(@misc_editor.extra_url_paths) and Enum.empty?(@misc_editor.updated_post_url_paths)}><%= dgettext("ui", "Apply") %></button>
<% end %> <% end %>
<%= if @misc_editor.kind == :find_duplicates do %> <%= if @misc_editor.kind == :find_duplicates do %>
<button class="secondary" type="button" phx-click="dismiss_selected_duplicates" phx-target={@myself} disabled={MapSet.size(@misc_editor.selected_pairs) == 0}><%= translated("Dismiss Checked") %></button> <button class="secondary" type="button" phx-click="dismiss_selected_duplicates" phx-target={@myself} disabled={MapSet.size(@misc_editor.selected_pairs) == 0}><%= dgettext("ui", "Dismiss Checked") %></button>
<% end %> <% end %>
</div> </div>
</div> </div>
@@ -25,9 +25,9 @@
<%= case @misc_editor.kind do %> <%= case @misc_editor.kind do %>
<% :site_validation -> %> <% :site_validation -> %>
<div class="misc-columns"> <div class="misc-columns">
<section class="misc-card"><h3><%= translated("Missing URLs") %></h3><%= if Enum.empty?(@misc_editor.missing_url_paths) do %><p><%= translated("None found") %></p><% end %><ul><%= for path <- @misc_editor.missing_url_paths do %><li><%= path %></li><% end %></ul></section> <section class="misc-card"><h3><%= dgettext("ui", "Missing URLs") %></h3><%= if Enum.empty?(@misc_editor.missing_url_paths) do %><p><%= dgettext("ui", "None found") %></p><% end %><ul><%= for path <- @misc_editor.missing_url_paths do %><li><%= path %></li><% end %></ul></section>
<section class="misc-card"><h3><%= translated("Extra URLs") %></h3><%= if Enum.empty?(@misc_editor.extra_url_paths) do %><p><%= translated("None found") %></p><% end %><ul><%= for path <- @misc_editor.extra_url_paths do %><li><%= path %></li><% end %></ul></section> <section class="misc-card"><h3><%= dgettext("ui", "Extra URLs") %></h3><%= if Enum.empty?(@misc_editor.extra_url_paths) do %><p><%= dgettext("ui", "None found") %></p><% end %><ul><%= for path <- @misc_editor.extra_url_paths do %><li><%= path %></li><% end %></ul></section>
<section class="misc-card"><h3><%= translated("Updated URLs") %></h3><%= if Enum.empty?(@misc_editor.updated_post_url_paths) do %><p><%= translated("None found") %></p><% end %><ul><%= for path <- @misc_editor.updated_post_url_paths do %><li><%= path %></li><% end %></ul></section> <section class="misc-card"><h3><%= dgettext("ui", "Updated URLs") %></h3><%= if Enum.empty?(@misc_editor.updated_post_url_paths) do %><p><%= dgettext("ui", "None found") %></p><% end %><ul><%= for path <- @misc_editor.updated_post_url_paths do %><li><%= path %></li><% end %></ul></section>
</div> </div>
<% :metadata_diff -> %> <% :metadata_diff -> %>
@@ -81,7 +81,7 @@
phx-value-direction="db_to_file" phx-value-direction="db_to_file"
phx-value-field={field.field_name} phx-value-field={field.field_name}
> >
<%= translated("DB to File") %> <%= dgettext("ui", "DB to File") %>
</button> </button>
<button <button
@@ -95,7 +95,7 @@
phx-value-direction="file_to_db" phx-value-direction="file_to_db"
phx-value-field={field.field_name} phx-value-field={field.field_name}
> >
<%= translated("File to DB") %> <%= dgettext("ui", "File to DB") %>
</button> </button>
</div> </div>
<% end %> <% end %>
@@ -130,7 +130,7 @@
<span><%= diff.db_value %></span> <span><%= diff.db_value %></span>
</div> </div>
<div class="diff-field-value file-value"> <div class="diff-field-value file-value">
<span class="diff-source-label"><%= translated("File") %></span> <span class="diff-source-label"><%= dgettext("ui", "File") %></span>
<span><%= diff.file_value %></span> <span><%= diff.file_value %></span>
</div> </div>
</div> </div>
@@ -145,7 +145,7 @@
<%= if @misc_editor.active_field == nil and @misc_editor.orphan_files != [] do %> <%= if @misc_editor.active_field == nil and @misc_editor.orphan_files != [] do %>
<section class="orphan-files-section" data-testid="metadata-diff-orphans"> <section class="orphan-files-section" data-testid="metadata-diff-orphans">
<div class="orphan-files-header"> <div class="orphan-files-header">
<h3><%= translated("Orphan Files") %></h3> <h3><%= dgettext("ui", "Orphan Files") %></h3>
<div class="orphan-files-actions"> <div class="orphan-files-actions">
<span class="misc-summary-pill"><%= length(@misc_editor.orphan_files) %></span> <span class="misc-summary-pill"><%= length(@misc_editor.orphan_files) %></span>
<button <button
@@ -155,7 +155,7 @@
phx-click="import_metadata_diff_orphans" phx-click="import_metadata_diff_orphans"
phx-target={@myself} phx-target={@myself}
> >
<%= translated("Import") %> <%= dgettext("ui", "Import") %>
</button> </button>
</div> </div>
</div> </div>
@@ -165,13 +165,13 @@
<header class="diff-item-header"> <header class="diff-item-header">
<div> <div>
<strong><%= orphan.slug %></strong> <strong><%= orphan.slug %></strong>
<div class="diff-item-meta"><%= translated("Orphan Files") %></div> <div class="diff-item-meta"><%= dgettext("ui", "Orphan Files") %></div>
</div> </div>
</header> </header>
<div class="diff-item-fields"> <div class="diff-item-fields">
<div class="diff-field-row"> <div class="diff-field-row">
<div class="diff-field-name"><%= translated("Path") %></div> <div class="diff-field-name"><%= dgettext("ui", "Path") %></div>
<div class="diff-field-values"> <div class="diff-field-values">
<div class="diff-field-value file-value orphan-path"><span><%= orphan.file_path %></span></div> <div class="diff-field-value file-value orphan-path"><span><%= orphan.file_path %></span></div>
</div> </div>
@@ -192,30 +192,30 @@
</section> </section>
<section class="translation-validation-section"> <section class="translation-validation-section">
<h3><%= translated("translationValidation.databaseTitle") %></h3> <h3><%= dgettext("ui", "translationValidation.databaseTitle") %></h3>
<%= if @misc_editor.invalid_database_rows == [] do %> <%= if @misc_editor.invalid_database_rows == [] do %>
<p class="translation-validation-empty"><%= translated("translationValidation.noneDatabase") %></p> <p class="translation-validation-empty"><%= dgettext("ui", "translationValidation.noneDatabase") %></p>
<% else %> <% else %>
<div class="translation-validation-list"> <div class="translation-validation-list">
<%= for issue <- @misc_editor.invalid_database_rows do %> <%= for issue <- @misc_editor.invalid_database_rows do %>
<article class="translation-validation-card translation-validation-card-db" data-testid="translation-validation-card"> <article class="translation-validation-card translation-validation-card-db" data-testid="translation-validation-card">
<p class="translation-validation-card-title"><%= translation_issue_label(issue) %></p> <p class="translation-validation-card-title"><%= translation_issue_label(issue) %></p>
<dl class="translation-validation-card-meta"> <dl class="translation-validation-card-meta">
<dt><%= translated("translationValidation.field.translationFor") %></dt> <dt><%= dgettext("ui", "translationValidation.field.translationFor") %></dt>
<dd><%= translation_issue_value(issue, :translation_for) %></dd> <dd><%= translation_issue_value(issue, :translation_for) %></dd>
<%= if translation_issue_value(issue, :translation_id) do %> <%= if translation_issue_value(issue, :translation_id) do %>
<dt><%= translated("translationValidation.field.translationId") %></dt> <dt><%= dgettext("ui", "translationValidation.field.translationId") %></dt>
<dd><%= translation_issue_value(issue, :translation_id) %></dd> <dd><%= translation_issue_value(issue, :translation_id) %></dd>
<% end %> <% end %>
<%= if translation_issue_value(issue, :title) do %> <%= if translation_issue_value(issue, :title) do %>
<dt><%= translated("translationValidation.field.title") %></dt> <dt><%= dgettext("ui", "translationValidation.field.title") %></dt>
<dd><%= translation_issue_value(issue, :title) %></dd> <dd><%= translation_issue_value(issue, :title) %></dd>
<% end %> <% end %>
<dt><%= translated("translationValidation.field.languages") %></dt> <dt><%= dgettext("ui", "translationValidation.field.languages") %></dt>
<dd><%= translation_issue_languages(issue) %></dd> <dd><%= translation_issue_languages(issue) %></dd>
<%= if translation_issue_value(issue, :file_path) do %> <%= if translation_issue_value(issue, :file_path) do %>
<dt><%= translated("translationValidation.field.filePath") %></dt> <dt><%= dgettext("ui", "translationValidation.field.filePath") %></dt>
<dd><%= translation_issue_value(issue, :file_path) %></dd> <dd><%= translation_issue_value(issue, :file_path) %></dd>
<% end %> <% end %>
</dl> </dl>
@@ -226,26 +226,26 @@
</section> </section>
<section class="translation-validation-section"> <section class="translation-validation-section">
<h3><%= translated("translationValidation.filesystemTitle") %></h3> <h3><%= dgettext("ui", "translationValidation.filesystemTitle") %></h3>
<%= if @misc_editor.invalid_filesystem_files == [] do %> <%= if @misc_editor.invalid_filesystem_files == [] do %>
<p class="translation-validation-empty"><%= translated("translationValidation.noneFilesystem") %></p> <p class="translation-validation-empty"><%= dgettext("ui", "translationValidation.noneFilesystem") %></p>
<% else %> <% else %>
<div class="translation-validation-list"> <div class="translation-validation-list">
<%= for issue <- @misc_editor.invalid_filesystem_files do %> <%= for issue <- @misc_editor.invalid_filesystem_files do %>
<article class="translation-validation-card translation-validation-card-file" data-testid="translation-validation-card"> <article class="translation-validation-card translation-validation-card-file" data-testid="translation-validation-card">
<p class="translation-validation-card-title"><%= translation_issue_label(issue) %></p> <p class="translation-validation-card-title"><%= translation_issue_label(issue) %></p>
<dl class="translation-validation-card-meta"> <dl class="translation-validation-card-meta">
<dt><%= translated("translationValidation.field.translationFor") %></dt> <dt><%= dgettext("ui", "translationValidation.field.translationFor") %></dt>
<dd><%= translation_issue_value(issue, :translation_for) %></dd> <dd><%= translation_issue_value(issue, :translation_for) %></dd>
<%= if translation_issue_value(issue, :title) do %> <%= if translation_issue_value(issue, :title) do %>
<dt><%= translated("translationValidation.field.title") %></dt> <dt><%= dgettext("ui", "translationValidation.field.title") %></dt>
<dd><%= translation_issue_value(issue, :title) %></dd> <dd><%= translation_issue_value(issue, :title) %></dd>
<% end %> <% end %>
<dt><%= translated("translationValidation.field.languages") %></dt> <dt><%= dgettext("ui", "translationValidation.field.languages") %></dt>
<dd><%= translation_issue_languages(issue) %></dd> <dd><%= translation_issue_languages(issue) %></dd>
<%= if translation_issue_value(issue, :file_path) do %> <%= if translation_issue_value(issue, :file_path) do %>
<dt><%= translated("translationValidation.field.filePath") %></dt> <dt><%= dgettext("ui", "translationValidation.field.filePath") %></dt>
<dd><%= translation_issue_value(issue, :file_path) %></dd> <dd><%= translation_issue_value(issue, :file_path) %></dd>
<% end %> <% end %>
</dl> </dl>
@@ -256,8 +256,8 @@
</section> </section>
<div class="translation-validation-actions"> <div class="translation-validation-actions">
<button class="secondary" type="button" phx-click="rerun_misc_editor" phx-target={@myself} data-testid="translation-validation-revalidate"><%= translated("translationValidation.revalidate") %></button> <button class="secondary" type="button" phx-click="rerun_misc_editor" phx-target={@myself} data-testid="translation-validation-revalidate"><%= dgettext("ui", "translationValidation.revalidate") %></button>
<button class="primary" type="button" phx-click="fix_translation_validation" phx-target={@myself} data-testid="translation-validation-fix" disabled={not @misc_editor.can_fix?}><%= translated("translationValidation.fix") %></button> <button class="primary" type="button" phx-click="fix_translation_validation" phx-target={@myself} data-testid="translation-validation-fix" disabled={not @misc_editor.can_fix?}><%= dgettext("ui", "translationValidation.fix") %></button>
</div> </div>
</div> </div>
@@ -269,8 +269,8 @@
<button class="linkish" type="button" phx-click="open_duplicate_post" phx-target={@myself} phx-value-id={BDS.MapUtils.attr(pair, :post_id_a)} phx-value-title={BDS.MapUtils.attr(pair, :title_a)}><%= BDS.MapUtils.attr(pair, :title_a) %></button> <button class="linkish" type="button" phx-click="open_duplicate_post" phx-target={@myself} phx-value-id={BDS.MapUtils.attr(pair, :post_id_a)} phx-value-title={BDS.MapUtils.attr(pair, :title_a)}><%= BDS.MapUtils.attr(pair, :title_a) %></button>
<span>→</span> <span>→</span>
<button class="linkish" type="button" phx-click="open_duplicate_post" phx-target={@myself} phx-value-id={BDS.MapUtils.attr(pair, :post_id_b)} phx-value-title={BDS.MapUtils.attr(pair, :title_b)}><%= BDS.MapUtils.attr(pair, :title_b) %></button> <button class="linkish" type="button" phx-click="open_duplicate_post" phx-target={@myself} phx-value-id={BDS.MapUtils.attr(pair, :post_id_b)} phx-value-title={BDS.MapUtils.attr(pair, :title_b)}><%= BDS.MapUtils.attr(pair, :title_b) %></button>
<span class="misc-summary-pill"><%= if(BDS.MapUtils.attr(pair, :exact_match), do: translated("Exact Match"), else: "#{Float.round((BDS.MapUtils.attr(pair, :similarity) || 0.0) * 100, 1)}%") %></span> <span class="misc-summary-pill"><%= if(BDS.MapUtils.attr(pair, :exact_match), do: dgettext("ui", "Exact Match"), else: "#{Float.round((BDS.MapUtils.attr(pair, :similarity) || 0.0) * 100, 1)}%") %></span>
<button class="secondary" type="button" phx-click="dismiss_duplicate_pair" phx-target={@myself} phx-value-post-id-a={BDS.MapUtils.attr(pair, :post_id_a)} phx-value-post-id-b={BDS.MapUtils.attr(pair, :post_id_b)}><%= translated("Dismiss") %></button> <button class="secondary" type="button" phx-click="dismiss_duplicate_pair" phx-target={@myself} phx-value-post-id-a={BDS.MapUtils.attr(pair, :post_id_a)} phx-value-post-id-b={BDS.MapUtils.attr(pair, :post_id_b)}><%= dgettext("ui", "Dismiss") %></button>
</article> </article>
<% end %> <% end %>
</div> </div>
@@ -281,7 +281,7 @@
<p class="git-diff-empty"><%= @misc_editor.empty_message %></p> <p class="git-diff-empty"><%= @misc_editor.empty_message %></p>
<% else %> <% else %>
<form class="git-diff-toolbar" phx-change="select_git_diff_file" phx-target={@myself}> <form class="git-diff-toolbar" phx-change="select_git_diff_file" phx-target={@myself}>
<label for="git-diff-file-select"><%= translated("gitDiff.changedFiles") %></label> <label for="git-diff-file-select"><%= dgettext("ui", "gitDiff.changedFiles") %></label>
<select id="git-diff-file-select" data-testid="git-diff-file-select" name="path"> <select id="git-diff-file-select" data-testid="git-diff-file-select" name="path">
<%= for file_path <- @misc_editor.files do %> <%= for file_path <- @misc_editor.files do %>
<option value={file_path} selected={file_path == @misc_editor.selected_file_path}><%= file_path %></option> <option value={file_path} selected={file_path == @misc_editor.selected_file_path}><%= file_path %></option>

View File

@@ -2,10 +2,10 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
@moduledoc false @moduledoc false
use Phoenix.Component use Phoenix.Component
use Gettext, backend: BDS.Gettext
import Ecto.Query import Ecto.Query
alias BDS.Desktop.ShellData
alias BDS.{I18n, Media, Metadata, Posts, Repo} alias BDS.{I18n, Media, Metadata, Posts, Repo}
alias BDS.Media.Media, as: MediaRecord alias BDS.Media.Media, as: MediaRecord
alias BDS.Media.Translation, as: MediaTranslation alias BDS.Media.Translation, as: MediaTranslation
@@ -38,10 +38,10 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
language_names: language_names(), language_names: language_names(),
language_flags: language_flags(), language_flags: language_flags(),
existing_translations: existing_translations(current_tab), existing_translations: existing_translations(current_tab),
ai_title: ShellData.translate("AI Suggestions", %{}, page_language), ai_title: BDS.Gettext.lgettext(page_language, "ui", "AI Suggestions"),
insert_link_title: ShellData.translate("Insert Link", %{}, page_language), insert_link_title: BDS.Gettext.lgettext(page_language, "ui", "Insert Link"),
insert_media_title: ShellData.translate("Insert Media", %{}, page_language), insert_media_title: BDS.Gettext.lgettext(page_language, "ui", "Insert Media"),
language_picker_title: ShellData.translate("Translate", %{}, page_language), language_picker_title: BDS.Gettext.lgettext(page_language, "ui", "Translate"),
gallery_title: tab_title, gallery_title: tab_title,
ai_fields: ai_fields(current_tab, tab_title, tab_subtitle, page_language), ai_fields: ai_fields(current_tab, tab_title, tab_subtitle, page_language),
delete_details: delete_details(current_tab, page_language), delete_details: delete_details(current_tab, page_language),
@@ -64,8 +64,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, BDS.Desktop.UILocale.current())
def project_metadata(nil), do: %{main_language: "en", blog_languages: []} def project_metadata(nil), do: %{main_language: "en", blog_languages: []}
@@ -217,7 +216,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
[ [
%{ %{
key: "title", key: "title",
label: ShellData.translate("Title", %{}, page_language), label: BDS.Gettext.lgettext(page_language, "ui", "Title"),
current_value: post.title || title, current_value: post.title || title,
suggested_value: "", suggested_value: "",
locked: false, locked: false,
@@ -225,7 +224,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
}, },
%{ %{
key: "excerpt", key: "excerpt",
label: ShellData.translate("Excerpt", %{}, page_language), label: BDS.Gettext.lgettext(page_language, "ui", "Excerpt"),
current_value: post.excerpt || subtitle, current_value: post.excerpt || subtitle,
suggested_value: "", suggested_value: "",
locked: false, locked: false,
@@ -233,7 +232,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
}, },
%{ %{
key: "slug", key: "slug",
label: ShellData.translate("Slug", %{}, page_language), label: BDS.Gettext.lgettext(page_language, "ui", "Slug"),
current_value: post.slug || slugify(post.title || title), current_value: post.slug || slugify(post.title || title),
suggested_value: "", suggested_value: "",
locked: post.status == :published, locked: post.status == :published,
@@ -254,7 +253,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
[ [
%{ %{
key: "title", key: "title",
label: ShellData.translate("Title", %{}, page_language), label: BDS.Gettext.lgettext(page_language, "ui", "Title"),
current_value: media.title || title, current_value: media.title || title,
suggested_value: "", suggested_value: "",
locked: false, locked: false,
@@ -262,7 +261,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
}, },
%{ %{
key: "alt", key: "alt",
label: ShellData.translate("Alt Text", %{}, page_language), label: BDS.Gettext.lgettext(page_language, "ui", "Alt Text"),
current_value: media.alt || "", current_value: media.alt || "",
suggested_value: "", suggested_value: "",
locked: false, locked: false,
@@ -270,7 +269,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
}, },
%{ %{
key: "caption", key: "caption",
label: ShellData.translate("Caption", %{}, page_language), label: BDS.Gettext.lgettext(page_language, "ui", "Caption"),
current_value: media.caption || "", current_value: media.caption || "",
suggested_value: "", suggested_value: "",
locked: false, locked: false,
@@ -306,7 +305,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|> Enum.map(&(&1 || media_id)) |> Enum.map(&(&1 || media_id))
%{ %{
title: ShellData.translate("Delete Media", %{}, page_language), title: BDS.Gettext.lgettext(page_language, "ui", "Delete Media"),
entity_name: entity_name, entity_name: entity_name,
entity_type: "media", entity_type: "media",
reference_list: reference_list reference_list: reference_list
@@ -314,7 +313,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
rescue rescue
_error -> _error ->
%{ %{
title: ShellData.translate("Delete Media", %{}, page_language), title: BDS.Gettext.lgettext(page_language, "ui", "Delete Media"),
entity_name: media_id, entity_name: media_id,
entity_type: "media", entity_type: "media",
reference_list: [] reference_list: []
@@ -327,7 +326,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|> Kernel.||("tag") |> Kernel.||("tag")
%{ %{
title: ShellData.translate("Delete Tag", %{}, page_language), title: BDS.Gettext.lgettext(page_language, "ui", "Delete Tag"),
entity_name: tag_name, entity_name: tag_name,
entity_type: "tag", entity_type: "tag",
reference_list: [] reference_list: []
@@ -335,7 +334,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
rescue rescue
_error -> _error ->
%{ %{
title: ShellData.translate("Delete Tag", %{}, page_language), title: BDS.Gettext.lgettext(page_language, "ui", "Delete Tag"),
entity_name: "tag", entity_name: "tag",
entity_type: "tag", entity_type: "tag",
reference_list: [] reference_list: []
@@ -344,7 +343,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
defp delete_details(_tab, page_language) do defp delete_details(_tab, page_language) do
%{ %{
title: ShellData.translate("Delete", %{}, page_language), title: BDS.Gettext.lgettext(page_language, "ui", "Delete"),
entity_name: "", entity_name: "",
entity_type: "item", entity_type: "item",
reference_list: [] reference_list: []
@@ -366,16 +365,16 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
%{ %{
target: target, target: target,
count: max(length(tags), 1), count: max(length(tags), 1),
title: ShellData.translate("Merge Tags", %{}, page_language), title: BDS.Gettext.lgettext(page_language, "ui", "Merge Tags"),
message: ShellData.translate("Cannot be undone.", %{}, page_language) message: BDS.Gettext.lgettext(page_language, "ui", "Cannot be undone.")
} }
rescue rescue
_error -> _error ->
%{ %{
target: "tag", target: "tag",
count: 1, count: 1,
title: ShellData.translate("Merge Tags", %{}, page_language), title: BDS.Gettext.lgettext(page_language, "ui", "Merge Tags"),
message: ShellData.translate("Cannot be undone.", %{}, page_language) message: BDS.Gettext.lgettext(page_language, "ui", "Cannot be undone.")
} }
end end

View File

@@ -2,7 +2,7 @@
<%= case @shell_overlay.kind do %> <%= case @shell_overlay.kind do %>
<% :ai_suggestions -> %> <% :ai_suggestions -> %>
<div class="shell-overlay-backdrop ai-suggestions-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown"> <div class="shell-overlay-backdrop ai-suggestions-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown">
<button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={translated("Cancel")}></button> <button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={dgettext("ui", "Cancel")}></button>
<div class="ai-suggestions-modal" role="dialog" aria-modal="true"> <div class="ai-suggestions-modal" role="dialog" aria-modal="true">
<div class="ai-suggestions-modal-header"> <div class="ai-suggestions-modal-header">
<h2><%= @shell_overlay.title %></h2> <h2><%= @shell_overlay.title %></h2>
@@ -11,7 +11,7 @@
<div class="ai-suggestions-modal-body"> <div class="ai-suggestions-modal-body">
<%= if Map.get(@shell_overlay, :error) do %> <%= if Map.get(@shell_overlay, :error) do %>
<div class="ai-suggestions-error"> <div class="ai-suggestions-error">
<strong><%= translated("Error") %></strong> <strong><%= dgettext("ui", "Error") %></strong>
<span><%= @shell_overlay.error %></span> <span><%= @shell_overlay.error %></span>
</div> </div>
<% end %> <% end %>
@@ -39,27 +39,27 @@
</div> </div>
</div> </div>
<div class="ai-suggestions-modal-footer"> <div class="ai-suggestions-modal-footer">
<button class="button-cancel" type="button" phx-click="close_overlay"><%= translated("Cancel") %></button> <button class="button-cancel" type="button" phx-click="close_overlay"><%= dgettext("ui", "Cancel") %></button>
<button class="button-apply" type="button" phx-click="overlay_confirm"><%= translated("Apply Selected") %></button> <button class="button-apply" type="button" phx-click="overlay_confirm"><%= dgettext("ui", "Apply Selected") %></button>
</div> </div>
</div> </div>
</div> </div>
<% :insert_link -> %> <% :insert_link -> %>
<div class="shell-overlay-backdrop insert-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown"> <div class="shell-overlay-backdrop insert-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown">
<button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={translated("Cancel")}></button> <button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={dgettext("ui", "Cancel")}></button>
<div class="insert-modal" role="dialog" aria-modal="true"> <div class="insert-modal" role="dialog" aria-modal="true">
<div class="insert-modal-header"> <div class="insert-modal-header">
<h2 class="insert-modal-title"><%= @shell_overlay.title %></h2> <h2 class="insert-modal-title"><%= @shell_overlay.title %></h2>
<div class="insert-modal-tabs"> <div class="insert-modal-tabs">
<button class={["insert-modal-tab", if(@shell_overlay.active_tab == :internal, do: "active")]} type="button" phx-click="overlay_set_tab" phx-value-tab="internal"><%= translated("Internal") %></button> <button class={["insert-modal-tab", if(@shell_overlay.active_tab == :internal, do: "active")]} type="button" phx-click="overlay_set_tab" phx-value-tab="internal"><%= dgettext("ui", "Internal") %></button>
<button class={["insert-modal-tab", if(@shell_overlay.active_tab == :external, do: "active")]} type="button" phx-click="overlay_set_tab" phx-value-tab="external"><%= translated("External") %></button> <button class={["insert-modal-tab", if(@shell_overlay.active_tab == :external, do: "active")]} type="button" phx-click="overlay_set_tab" phx-value-tab="external"><%= dgettext("ui", "External") %></button>
</div> </div>
</div> </div>
<%= if @shell_overlay.active_tab == :internal do %> <%= if @shell_overlay.active_tab == :internal do %>
<form class="insert-modal-search" phx-change="overlay_set_search"> <form class="insert-modal-search" phx-change="overlay_set_search">
<input class="insert-modal-input" type="text" name="overlay[query]" value={@shell_overlay.search_query} placeholder={translated("sidebar.searchPostsPlaceholder")} /> <input class="insert-modal-input" type="text" name="overlay[query]" value={@shell_overlay.search_query} placeholder={dgettext("ui", "Search posts...")} />
</form> </form>
<div class="insert-modal-results"> <div class="insert-modal-results">
<%= for result <- if(String.length(@shell_overlay.search_query) >= 2, do: @shell_overlay.results, else: @shell_overlay.related_posts) do %> <%= for result <- if(String.length(@shell_overlay.search_query) >= 2, do: @shell_overlay.results, else: @shell_overlay.related_posts) do %>
@@ -69,20 +69,20 @@
</button> </button>
<% end %> <% end %>
<%= if Enum.empty?(if(String.length(@shell_overlay.search_query) >= 2, do: @shell_overlay.results, else: @shell_overlay.related_posts)) do %> <%= if Enum.empty?(if(String.length(@shell_overlay.search_query) >= 2, do: @shell_overlay.results, else: @shell_overlay.related_posts)) do %>
<div class="insert-modal-status"><%= translated("No items") %></div> <div class="insert-modal-status"><%= dgettext("ui", "No items") %></div>
<% end %> <% end %>
</div> </div>
<% else %> <% else %>
<form class="insert-modal-external" phx-change="overlay_update_form"> <form class="insert-modal-external" phx-change="overlay_update_form">
<label class="insert-modal-field"> <label class="insert-modal-field">
<span class="insert-modal-label"><%= translated("URL") %></span> <span class="insert-modal-label"><%= dgettext("ui", "URL") %></span>
<input class="insert-modal-input" type="text" name="overlay[url]" value={@shell_overlay.external_url} /> <input class="insert-modal-input" type="text" name="overlay[url]" value={@shell_overlay.external_url} />
</label> </label>
<label class="insert-modal-field"> <label class="insert-modal-field">
<span class="insert-modal-label"><%= translated("Display Text") %></span> <span class="insert-modal-label"><%= dgettext("ui", "Display Text") %></span>
<input class="insert-modal-input" type="text" name="overlay[text]" value={@shell_overlay.external_text} /> <input class="insert-modal-input" type="text" name="overlay[text]" value={@shell_overlay.external_text} />
</label> </label>
<button class="insert-modal-submit" type="button" phx-click="overlay_insert_external"><%= translated("Insert") %></button> <button class="insert-modal-submit" type="button" phx-click="overlay_insert_external"><%= dgettext("ui", "Insert") %></button>
</form> </form>
<% end %> <% end %>
</div> </div>
@@ -90,13 +90,13 @@
<% :insert_media -> %> <% :insert_media -> %>
<div class="shell-overlay-backdrop insert-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown"> <div class="shell-overlay-backdrop insert-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown">
<button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={translated("Cancel")}></button> <button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={dgettext("ui", "Cancel")}></button>
<div class="insert-modal" role="dialog" aria-modal="true"> <div class="insert-modal" role="dialog" aria-modal="true">
<div class="insert-modal-header"> <div class="insert-modal-header">
<h2 class="insert-modal-title"><%= @shell_overlay.title %></h2> <h2 class="insert-modal-title"><%= @shell_overlay.title %></h2>
</div> </div>
<form class="insert-modal-search" phx-change="overlay_set_search"> <form class="insert-modal-search" phx-change="overlay_set_search">
<input class="insert-modal-input" type="text" name="overlay[query]" value={@shell_overlay.search_query} placeholder={translated("sidebar.searchMediaPlaceholder")} /> <input class="insert-modal-input" type="text" name="overlay[query]" value={@shell_overlay.search_query} placeholder={dgettext("ui", "Search media...")} />
</form> </form>
<div class="insert-modal-results insert-modal-media-grid"> <div class="insert-modal-results insert-modal-media-grid">
<%= for result <- @shell_overlay.results do %> <%= for result <- @shell_overlay.results do %>
@@ -115,14 +115,14 @@
<% :language_picker -> %> <% :language_picker -> %>
<div class="shell-overlay-backdrop language-picker-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown"> <div class="shell-overlay-backdrop language-picker-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown">
<button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={translated("Cancel")}></button> <button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={dgettext("ui", "Cancel")}></button>
<div class="language-picker-modal" role="dialog" aria-modal="true"> <div class="language-picker-modal" role="dialog" aria-modal="true">
<div class="language-picker-modal-header"> <div class="language-picker-modal-header">
<h2><%= @shell_overlay.title %></h2> <h2><%= @shell_overlay.title %></h2>
<button class="language-picker-modal-close" type="button" phx-click="close_overlay">×</button> <button class="language-picker-modal-close" type="button" phx-click="close_overlay">×</button>
</div> </div>
<div class="language-picker-modal-body"> <div class="language-picker-modal-body">
<div class="language-picker-label"><%= translated("Available languages") %></div> <div class="language-picker-label"><%= dgettext("ui", "Available languages") %></div>
<div class="language-picker-options"> <div class="language-picker-options">
<%= for target <- @shell_overlay.available_targets do %> <%= for target <- @shell_overlay.available_targets do %>
<button class="language-picker-option" type="button" phx-click="overlay_select_language" phx-value-code={target.code}> <button class="language-picker-option" type="button" phx-click="overlay_select_language" phx-value-code={target.code}>
@@ -140,7 +140,7 @@
<% :confirm_delete -> %> <% :confirm_delete -> %>
<div class="shell-overlay-backdrop confirm-delete-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown"> <div class="shell-overlay-backdrop confirm-delete-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown">
<button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={translated("Cancel")}></button> <button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={dgettext("ui", "Cancel")}></button>
<div class="confirm-delete-modal" role="dialog" aria-modal="true"> <div class="confirm-delete-modal" role="dialog" aria-modal="true">
<div class="confirm-delete-modal-header"> <div class="confirm-delete-modal-header">
<h2><%= @shell_overlay.title %></h2> <h2><%= @shell_overlay.title %></h2>
@@ -151,7 +151,7 @@
<%= if @shell_overlay.reference_count > 0 do %> <%= if @shell_overlay.reference_count > 0 do %>
<div class="confirm-delete-warning"> <div class="confirm-delete-warning">
<div class="warning-content"> <div class="warning-content">
<strong><%= translated("This item is referenced by:") %></strong> <strong><%= dgettext("ui", "This item is referenced by:") %></strong>
<ul class="reference-list"> <ul class="reference-list">
<%= for title <- @shell_overlay.reference_list do %> <%= for title <- @shell_overlay.reference_list do %>
<li><span class="reference-title"><%= title %></span></li> <li><span class="reference-title"><%= title %></span></li>
@@ -162,15 +162,15 @@
<% end %> <% end %>
</div> </div>
<div class="confirm-delete-modal-footer"> <div class="confirm-delete-modal-footer">
<button class="button-cancel" type="button" phx-click="close_overlay"><%= translated("Cancel") %></button> <button class="button-cancel" type="button" phx-click="close_overlay"><%= dgettext("ui", "Cancel") %></button>
<button class="button-delete" type="button" phx-click="overlay_confirm"><%= translated("Delete") %></button> <button class="button-delete" type="button" phx-click="overlay_confirm"><%= dgettext("ui", "Delete") %></button>
</div> </div>
</div> </div>
</div> </div>
<% :confirm_dialog -> %> <% :confirm_dialog -> %>
<div class="shell-overlay-backdrop confirm-delete-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown"> <div class="shell-overlay-backdrop confirm-delete-modal-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown">
<button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={translated("Cancel")}></button> <button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={dgettext("ui", "Cancel")}></button>
<div class="confirm-delete-modal" role="dialog" aria-modal="true"> <div class="confirm-delete-modal" role="dialog" aria-modal="true">
<div class="confirm-delete-modal-header"> <div class="confirm-delete-modal-header">
<h2><%= @shell_overlay.title %></h2> <h2><%= @shell_overlay.title %></h2>
@@ -180,18 +180,18 @@
<div class="confirm-delete-message"><%= @shell_overlay.message %></div> <div class="confirm-delete-message"><%= @shell_overlay.message %></div>
</div> </div>
<div class="confirm-delete-modal-footer"> <div class="confirm-delete-modal-footer">
<button class="button-cancel" type="button" phx-click="close_overlay"><%= translated("Cancel") %></button> <button class="button-cancel" type="button" phx-click="close_overlay"><%= dgettext("ui", "Cancel") %></button>
<button class="button-apply" type="button" phx-click="overlay_confirm"><%= translated("Confirm") %></button> <button class="button-apply" type="button" phx-click="overlay_confirm"><%= dgettext("ui", "Confirm") %></button>
</div> </div>
</div> </div>
</div> </div>
<% :gallery -> %> <% :gallery -> %>
<div class="shell-overlay-backdrop gallery-overlay-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown"> <div class="shell-overlay-backdrop gallery-overlay-backdrop" data-testid="shell-overlay-backdrop" phx-window-keydown="overlay_keydown">
<button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={translated("Cancel")}></button> <button class="shell-overlay-dismiss" type="button" phx-click="close_overlay" aria-label={dgettext("ui", "Cancel")}></button>
<div class="gallery-overlay" role="dialog" aria-modal="true"> <div class="gallery-overlay" role="dialog" aria-modal="true">
<div class="gallery-overlay-header"> <div class="gallery-overlay-header">
<h2><%= translated("Gallery") %></h2> <h2><%= dgettext("ui", "Gallery") %></h2>
<button class="gallery-overlay-close" type="button" phx-click="close_overlay">×</button> <button class="gallery-overlay-close" type="button" phx-click="close_overlay">×</button>
</div> </div>
<div class="gallery-overlay-grid"> <div class="gallery-overlay-grid">
@@ -205,7 +205,7 @@
<%= if @shell_overlay.lightbox do %> <%= if @shell_overlay.lightbox do %>
<div class="lightbox-overlay"> <div class="lightbox-overlay">
<button class="shell-overlay-dismiss" type="button" phx-click="overlay_close_lightbox" aria-label={translated("Cancel")}></button> <button class="shell-overlay-dismiss" type="button" phx-click="overlay_close_lightbox" aria-label={dgettext("ui", "Cancel")}></button>
<div class="lightbox-container"> <div class="lightbox-container">
<button class="lightbox-close" type="button" phx-click="overlay_close_lightbox">×</button> <button class="lightbox-close" type="button" phx-click="overlay_close_lightbox">×</button>
<%= if @shell_overlay.lightbox.total_count > 1 do %> <%= if @shell_overlay.lightbox.total_count > 1 do %>

View File

@@ -7,7 +7,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
import Phoenix.LiveView, only: [send_update: 2] import Phoenix.LiveView, only: [send_update: 2]
alias BDS.{AI, Media, Metadata} alias BDS.{AI, Media, Metadata}
alias BDS.Desktop.{Overlay, ShellData, UILocale} alias BDS.Desktop.{Overlay}
alias BDS.Desktop.ShellLive.{ alias BDS.Desktop.ShellLive.{
MediaEditor, MediaEditor,
@@ -16,6 +16,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
} }
alias BDS.Desktop.ShellLive.OverlayComponents, as: ShellOverlayComponents alias BDS.Desktop.ShellLive.OverlayComponents, as: ShellOverlayComponents
use Gettext, backend: BDS.Gettext
# ── Event handlers ───────────────────────────────────────────────────────── # ── Event handlers ─────────────────────────────────────────────────────────
@@ -67,8 +68,8 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
if socket.assigns.offline_mode do if socket.assigns.offline_mode do
callbacks.append_output.( callbacks.append_output.(
socket, socket,
translated("AI Suggestions"), dgettext("ui", "AI Suggestions"),
translated("Automatic AI actions stay gated by airplane mode."), dgettext("ui", "Automatic AI actions stay gated by airplane mode."),
nil, nil,
"info" "info"
) )
@@ -265,7 +266,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
socket socket
|> assign(:shell_overlay, nil) |> assign(:shell_overlay, nil)
|> callbacks.append_output.( |> callbacks.append_output.(
translated("Delete Media"), dgettext("ui", "Delete Media"),
inspect(reason), inspect(reason),
nil, nil,
"error" "error"
@@ -392,7 +393,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
) :: Phoenix.LiveView.Socket.t() ) :: Phoenix.LiveView.Socket.t()
defp close_overlay_with_output(socket, append_output, title, details) do defp close_overlay_with_output(socket, append_output, title, details) do
socket socket
|> append_output.(title, translated("Command completed"), details, "info") |> append_output.(title, dgettext("ui", "Command completed"), details, "info")
|> assign(:shell_overlay, nil) |> assign(:shell_overlay, nil)
end end
@@ -444,7 +445,4 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
_error -> "en" _error -> "en"
end end
@spec translated(String.t(), map()) :: String.t()
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, UILocale.current())
end end

View File

@@ -10,6 +10,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
alias BDS.PostLinks alias BDS.PostLinks
alias BDS.Posts alias BDS.Posts
alias BDS.Posts.Post alias BDS.Posts.Post
use Gettext, backend: BDS.Gettext
@doc "Render the active panel tab body." @doc "Render the active panel tab body."
def render_panel_body(assigns) do def render_panel_body(assigns) do
@@ -38,7 +39,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
phx-click="open_overlay" phx-click="open_overlay"
phx-value-kind={button.kind} phx-value-kind={button.kind}
> >
<%= translated(button.label) %> <%= button.label %>
</button> </button>
<% end %> <% end %>
</div> </div>
@@ -50,8 +51,8 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
~H""" ~H"""
<%= if Enum.empty?(Map.get(@task_status, :tasks, [])) do %> <%= if Enum.empty?(Map.get(@task_status, :tasks, [])) do %>
<div class="panel-entry panel-empty-state"> <div class="panel-entry panel-empty-state">
<strong><%= translated("Tasks") %></strong> <strong><%= dgettext("ui", "Tasks") %></strong>
<span><%= translated("No background tasks running") %></span> <span><%= dgettext("ui", "No background tasks running") %></span>
</div> </div>
<% else %> <% else %>
<div class="task-list"> <div class="task-list">
@@ -79,8 +80,8 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
~H""" ~H"""
<%= if Enum.empty?(@output_entries) do %> <%= if Enum.empty?(@output_entries) do %>
<div class="panel-entry panel-empty-state output-list"> <div class="panel-entry panel-empty-state output-list">
<strong><%= translated("Output") %></strong> <strong><%= dgettext("ui", "Output") %></strong>
<span><%= translated("No shell output yet") %></span> <span><%= dgettext("ui", "No shell output yet") %></span>
</div> </div>
<% else %> <% else %>
<div class="output-list"> <div class="output-list">
@@ -113,13 +114,13 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
~H""" ~H"""
<%= if Enum.empty?(@backlinks) and Enum.empty?(@outlinks) do %> <%= if Enum.empty?(@backlinks) and Enum.empty?(@outlinks) do %>
<div class="panel-entry panel-empty-state"> <div class="panel-entry panel-empty-state">
<strong><%= translated("Post Links") %></strong> <strong><%= dgettext("ui", "Post Links") %></strong>
<span><%= translated("No post links yet") %></span> <span><%= dgettext("ui", "No post links yet") %></span>
</div> </div>
<% else %> <% else %>
<div class="git-log-list"> <div class="git-log-list">
<%= if Enum.any?(@backlinks) do %> <%= if Enum.any?(@backlinks) do %>
<div class="panel-entry"><strong><%= translated("Backlinks") %></strong></div> <div class="panel-entry"><strong><%= dgettext("ui", "Backlinks") %></strong></div>
<%= for entry <- @backlinks do %> <%= for entry <- @backlinks do %>
<button <button
class="panel-entry task-entry" class="panel-entry task-entry"
@@ -137,7 +138,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
<% end %> <% end %>
<%= if Enum.any?(@outlinks) do %> <%= if Enum.any?(@outlinks) do %>
<div class="panel-entry"><strong><%= translated("Links To") %></strong></div> <div class="panel-entry"><strong><%= dgettext("ui", "Links To") %></strong></div>
<%= for entry <- @outlinks do %> <%= for entry <- @outlinks do %>
<button <button
class="panel-entry task-entry" class="panel-entry task-entry"
@@ -166,15 +167,15 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
<%= if Enum.empty?(@git_entries) do %> <%= if Enum.empty?(@git_entries) do %>
<div class="git-log-list"> <div class="git-log-list">
<div class="panel-entry panel-empty-state"> <div class="panel-entry panel-empty-state">
<strong><%= translated("Git Log") %></strong> <strong><%= dgettext("ui", "Git Log") %></strong>
<span><%= translated("No git history yet") %></span> <span><%= dgettext("ui", "No git history yet") %></span>
</div> </div>
</div> </div>
<% else %> <% else %>
<div class="git-log-list"> <div class="git-log-list">
<%= for entry <- @git_entries do %> <%= for entry <- @git_entries do %>
<div class="panel-entry task-entry"> <div class="panel-entry task-entry">
<strong><%= short_commit_hash(entry.hash) %> <%= entry.subject || translated("No commit subject") %></strong> <strong><%= short_commit_hash(entry.hash) %> <%= entry.subject || dgettext("ui", "No commit subject") %></strong>
<span><%= entry.hash %></span> <span><%= entry.hash %></span>
</div> </div>
<% end %> <% end %>
@@ -189,7 +190,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
~H""" ~H"""
<div class="panel-entry"> <div class="panel-entry">
<strong><%= @panel_label %></strong> <strong><%= @panel_label %></strong>
<span><%= translated("The shared lower panel is available for tasks, output, git details, and editor-specific diagnostics.") %></span> <span><%= dgettext("ui", "The shared lower panel is available for tasks, output, git details, and editor-specific diagnostics.") %></span>
</div> </div>
""" """
end end
@@ -300,7 +301,4 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
defp progress_percent(_), do: "" defp progress_percent(_), 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, BDS.Desktop.UILocale.current())
end end

View File

@@ -6,7 +6,6 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
alias BDS.{AI, Posts, Preview} alias BDS.{AI, Posts, Preview}
alias BDS.Desktop.ShellData alias BDS.Desktop.ShellData
alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, ListValues, Persistence, PostMetadata} alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, ListValues, Persistence, PostMetadata}
alias BDS.Desktop.UILocale
alias BDS.Posts.Post alias BDS.Posts.Post
alias BDS.Tags alias BDS.Tags
@@ -47,6 +46,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
persist: 5 persist: 5
] ]
use Gettext, backend: BDS.Gettext
import PostMetadata, import PostMetadata,
only: [ only: [
blank?: 1, blank?: 1,
@@ -461,11 +461,11 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
) )
notify_parent({:post_editor_dirty, post.id, false}) notify_parent({:post_editor_dirty, post.id, false})
notify_output(socket, translated("Post"), translated("Post saved")) notify_output(socket, dgettext("ui", "Post"), dgettext("ui", "Post saved"))
socket socket
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Post"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Post"), inspect(reason), "error")
|> build_data() |> build_data()
end end
end end
@@ -502,11 +502,11 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
) )
notify_parent({:post_editor_dirty, post.id, false}) notify_parent({:post_editor_dirty, post.id, false})
notify_output(socket, translated("Post"), translated("Post published")) notify_output(socket, dgettext("ui", "Post"), dgettext("ui", "Post published"))
socket socket
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Post"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Post"), inspect(reason), "error")
|> build_data() |> build_data()
end end
end end
@@ -543,7 +543,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
socket socket
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Post"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Post"), inspect(reason), "error")
|> build_data() |> build_data()
end end
end end
@@ -558,7 +558,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
socket socket
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Post"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Post"), inspect(reason), "error")
|> build_data() |> build_data()
end end
end end
@@ -567,8 +567,8 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
if Map.get(socket.assigns, :offline_mode, true) do if Map.get(socket.assigns, :offline_mode, true) do
notify_output( notify_output(
socket, socket,
translated("Detect Language"), dgettext("ui", "Detect Language"),
translated("Automatic AI actions stay gated by airplane mode."), dgettext("ui", "Automatic AI actions stay gated by airplane mode."),
"info" "info"
) )
|> build_data() |> build_data()
@@ -593,14 +593,14 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|> build_data() |> build_data()
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Detect Language"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Detect Language"), inspect(reason), "error")
|> build_data() |> build_data()
_other -> _other ->
notify_output( notify_output(
socket, socket,
translated("Detect Language"), dgettext("ui", "Detect Language"),
translated("Language detection failed."), dgettext("ui", "Language detection failed."),
"error" "error"
) )
|> build_data() |> build_data()
@@ -613,8 +613,8 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
if Map.get(socket.assigns, :offline_mode, true) do if Map.get(socket.assigns, :offline_mode, true) do
notify_output( notify_output(
socket, socket,
translated("Translate"), dgettext("ui", "Translate"),
translated("Automatic AI actions stay gated by airplane mode."), dgettext("ui", "Automatic AI actions stay gated by airplane mode."),
"info" "info"
) )
|> build_data() |> build_data()
@@ -642,12 +642,12 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
socket socket
else else
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Translate"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Translate"), inspect(reason), "error")
|> build_data() |> build_data()
end end
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Translate"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Translate"), inspect(reason), "error")
|> build_data() |> build_data()
end end
end end
@@ -695,7 +695,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
socket socket
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("AI Suggestions"), inspect(reason), "error") notify_output(socket, dgettext("ui", "AI Suggestions"), inspect(reason), "error")
|> build_data() |> build_data()
end end
end end
@@ -813,17 +813,14 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
def post_status_label(status), do: ShellData.dashboard_status_label(status) def post_status_label(status), do: ShellData.dashboard_status_label(status)
@spec post_editor_save_state_label(term()) :: term() @spec post_editor_save_state_label(term()) :: term()
def post_editor_save_state_label(:dirty), do: translated("Unsaved") def post_editor_save_state_label(:dirty), do: dgettext("ui", "Unsaved")
def post_editor_save_state_label(:saved), do: translated("Saved") def post_editor_save_state_label(:saved), do: dgettext("ui", "Saved")
def post_editor_save_state_label(:published), do: translated("Published") def post_editor_save_state_label(:published), do: dgettext("ui", "Published")
def post_editor_save_state_label(:discarded), do: translated("Reverted") def post_editor_save_state_label(:discarded), do: dgettext("ui", "Reverted")
def post_editor_save_state_label(_state), do: translated("Idle") def post_editor_save_state_label(_state), do: dgettext("ui", "Idle")
@spec post_editor_mode_label(term()) :: term() @spec post_editor_mode_label(term()) :: term()
def post_editor_mode_label(:markdown), do: translated("Markdown") def post_editor_mode_label(:markdown), do: dgettext("ui", "Markdown")
def post_editor_mode_label(:preview), do: translated("Preview") def post_editor_mode_label(:preview), do: dgettext("ui", "Preview")
@spec translated(term(), term()) :: term()
def translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, UILocale.current())
end end

View File

@@ -3,8 +3,8 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
alias BDS.Posts alias BDS.Posts
alias BDS.Posts.Post alias BDS.Posts.Post
alias BDS.Desktop.ShellData
alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, PostMetadata} alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, PostMetadata}
use Gettext, backend: BDS.Gettext
@spec persist(term(), term(), term(), term(), term()) :: term() @spec persist(term(), term(), term(), term(), term()) :: term()
def persist(%Post{} = post, draft, active_language, metadata, action) do def persist(%Post{} = post, draft, active_language, metadata, action) do
@@ -54,15 +54,15 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
@spec discard_label(term()) :: term() @spec discard_label(term()) :: term()
def discard_label(%Post{} = post) do def discard_label(%Post{} = post) do
if has_published_version?(post), if has_published_version?(post),
do: translated("Discard Changes"), do: dgettext("ui", "Discard Changes"),
else: translated("Discard Draft") else: dgettext("ui", "Discard Draft")
end end
@spec discard_title(term()) :: term() @spec discard_title(term()) :: term()
def discard_title(%Post{} = post) do def discard_title(%Post{} = post) do
if has_published_version?(post), if has_published_version?(post),
do: translated("Discard changes and restore the published version"), do: dgettext("ui", "Discard changes and restore the published version"),
else: translated("Delete this unpublished draft") else: dgettext("ui", "Delete this unpublished draft")
end end
defp save_canonical_draft(%Post{id: post_id}, draft) do defp save_canonical_draft(%Post{id: post_id}, draft) do
@@ -112,7 +112,4 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
|> Enum.map(&String.trim/1) |> Enum.map(&String.trim/1)
|> Enum.reject(&(&1 == "")) |> Enum.reject(&(&1 == ""))
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -4,9 +4,9 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
import Ecto.Query import Ecto.Query
alias BDS.{I18n, Media, Metadata, PostLinks, Posts, Preview, Repo, Templates} alias BDS.{I18n, Media, Metadata, PostLinks, Posts, Preview, Repo, Templates}
alias BDS.Desktop.ShellData
alias BDS.Media.Media, as: MediaRecord alias BDS.Media.Media, as: MediaRecord
alias BDS.Posts.{Post, PostMedia} alias BDS.Posts.{Post, PostMedia}
use Gettext, backend: BDS.Gettext
@spec project_metadata(term()) :: term() @spec project_metadata(term()) :: term()
def project_metadata(nil), do: %{main_language: "en", blog_languages: []} def project_metadata(nil), do: %{main_language: "en", blog_languages: []}
@@ -163,7 +163,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
@spec display_title(term(), term(), term()) :: term() @spec display_title(term(), term(), term()) :: term()
def display_title(title, slug, fallback_id) do def display_title(title, slug, fallback_id) do
blank_to_nil(title) || blank_to_nil(slug) || fallback_id || translated("Untitled") blank_to_nil(title) || blank_to_nil(slug) || fallback_id || dgettext("ui", "Untitled")
end end
@spec gallery_count(term()) :: term() @spec gallery_count(term()) :: term()
@@ -219,7 +219,4 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
trimmed -> trimmed trimmed -> trimmed
end end
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -4,7 +4,7 @@
<div class={["editor-tab", "active", if(@post_editor.dirty?, do: "dirty")]}> <div class={["editor-tab", "active", if(@post_editor.dirty?, do: "dirty")]}>
<span class="editor-tab-title" data-testid="editor-title"><%= @post_editor.display_title %></span> <span class="editor-tab-title" data-testid="editor-title"><%= @post_editor.display_title %></span>
<%= if @post_editor.dirty? do %> <%= if @post_editor.dirty? do %>
<span class="editor-tab-dirty" title={translated("Unsaved")}>●</span> <span class="editor-tab-dirty" title={dgettext("ui", "Unsaved")}>●</span>
<% end %> <% end %>
</div> </div>
</div> </div>
@@ -25,7 +25,7 @@
phx-target={@myself} phx-target={@myself}
> >
<span class="quick-actions-btn-icon">⚡</span> <span class="quick-actions-btn-icon">⚡</span>
<span class="quick-actions-btn-label"><%= translated("Quick Actions") %></span> <span class="quick-actions-btn-label"><%= dgettext("ui", "Quick Actions") %></span>
</button> </button>
<%= if @post_editor.quick_actions_open? do %> <%= if @post_editor.quick_actions_open? do %>
@@ -40,8 +40,8 @@
> >
<span class="quick-action-icon">🤖</span> <span class="quick-action-icon">🤖</span>
<span class="quick-action-text"> <span class="quick-action-text">
<strong><%= translated("AI Suggestions") %></strong> <strong><%= dgettext("ui", "AI Suggestions") %></strong>
<small><%= translated("Review title, excerpt, and content suggestions") %></small> <small><%= dgettext("ui", "Review title, excerpt, and content suggestions") %></small>
</span> </span>
</button> </button>
@@ -57,8 +57,8 @@
> >
<span class="quick-action-icon">🌍</span> <span class="quick-action-icon">🌍</span>
<span class="quick-action-text"> <span class="quick-action-text">
<strong><%= translated("Translate") %></strong> <strong><%= dgettext("ui", "Translate") %></strong>
<small><%= translated("Select a target language for this post") %></small> <small><%= dgettext("ui", "Select a target language for this post") %></small>
</span> </span>
</button> </button>
</div> </div>
@@ -67,7 +67,7 @@
<%= if @post_editor.can_publish? do %> <%= if @post_editor.can_publish? do %>
<button class="success" data-testid="post-publish-button" type="button" phx-click="publish_post_editor" phx-target={@myself}> <button class="success" data-testid="post-publish-button" type="button" phx-click="publish_post_editor" phx-target={@myself}>
<%= translated("Publish") %> <%= dgettext("ui", "Publish") %>
</button> </button>
<% end %> <% end %>
<%= if @post_editor.can_publish? do %> <%= if @post_editor.can_publish? do %>
@@ -77,7 +77,7 @@
<% end %> <% end %>
<%= if @post_editor.can_delete? do %> <%= if @post_editor.can_delete? do %>
<button class="secondary danger" data-testid="post-delete-button" type="button" phx-click="delete_post_editor" phx-target={@myself}> <button class="secondary danger" data-testid="post-delete-button" type="button" phx-click="delete_post_editor" phx-target={@myself}>
<%= translated("Delete") %> <%= dgettext("ui", "Delete") %>
</button> </button>
<% end %> <% end %>
</div> </div>
@@ -87,10 +87,10 @@
<div class="metadata-toggle-header"> <div class="metadata-toggle-header">
<button class={["metadata-toggle", if(@post_editor.metadata_expanded, do: "expanded")]} type="button" phx-click="toggle_post_metadata" phx-target={@myself}> <button class={["metadata-toggle", if(@post_editor.metadata_expanded, do: "expanded")]} type="button" phx-click="toggle_post_metadata" phx-target={@myself}>
<span class="metadata-toggle-chevron"><%= if @post_editor.metadata_expanded, do: "▼", else: "▶" %></span> <span class="metadata-toggle-chevron"><%= if @post_editor.metadata_expanded, do: "▼", else: "▶" %></span>
<span><%= translated("Metadata") %></span> <span><%= dgettext("ui", "Metadata") %></span>
</button> </button>
<div class="editor-translations-flags" aria-label={translated("Translations")}> <div class="editor-translations-flags" aria-label={dgettext("ui", "Translations")}>
<%= for flag <- @post_editor.translation_flags do %> <%= for flag <- @post_editor.translation_flags do %>
<button <button
class={[ class={[
@@ -114,19 +114,19 @@
<div class={["editor-header-row", if(not @post_editor.metadata_expanded, do: "is-collapsed")]}> <div class={["editor-header-row", if(not @post_editor.metadata_expanded, do: "is-collapsed")]}>
<div class="editor-meta"> <div class="editor-meta">
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Title") %></label> <label><%= dgettext("ui", "Title") %></label>
<input class="post-editor-input" type="text" name="post_editor[title]" value={@post_editor.form["title"]} /> <input class="post-editor-input" type="text" name="post_editor[title]" value={@post_editor.form["title"]} />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Tags") %></label> <label><%= dgettext("ui", "Tags") %></label>
<div class="tag-input-container"> <div class="tag-input-container">
<input type="hidden" name="post_editor[tags]" value={@post_editor.form["tags"]} /> <input type="hidden" name="post_editor[tags]" value={@post_editor.form["tags"]} />
<div class="tag-input-wrapper"> <div class="tag-input-wrapper">
<%= for tag <- @post_editor.tag_chips do %> <%= for tag <- @post_editor.tag_chips do %>
<span class={["tag-chip", if(tag.color, do: "has-color")]} style={tag_chip_style(tag.color)}> <span class={["tag-chip", if(tag.color, do: "has-color")]} style={tag_chip_style(tag.color)}>
<span><%= tag.name %></span> <span><%= tag.name %></span>
<button class="tag-chip-remove" type="button" phx-click="remove_post_editor_tag" phx-value-tag={tag.name} phx-target={@myself} aria-label={translated("Remove tag")}>×</button> <button class="tag-chip-remove" type="button" phx-click="remove_post_editor_tag" phx-value-tag={tag.name} phx-target={@myself} aria-label={dgettext("ui", "Remove tag")}>×</button>
</span> </span>
<% end %> <% end %>
@@ -135,7 +135,7 @@
type="text" type="text"
name="post_editor[tag_query]" name="post_editor[tag_query]"
value={@post_editor.tag_query} value={@post_editor.tag_query}
placeholder={translated("Add tag")} placeholder={dgettext("ui", "Add tag")}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
@@ -154,7 +154,7 @@
<%= if @post_editor.tag_query_addable? do %> <%= if @post_editor.tag_query_addable? do %>
<button class="tag-suggestion create-new" type="button" phx-click="add_post_editor_tag" phx-value-tag={@post_editor.tag_query} phx-target={@myself}> <button class="tag-suggestion create-new" type="button" phx-click="add_post_editor_tag" phx-value-tag={@post_editor.tag_query} phx-target={@myself}>
<span class="tag-suggestion-icon">+</span> <span class="tag-suggestion-icon">+</span>
<span><%= translated("Create tag") %>: <strong><%= @post_editor.tag_query %></strong></span> <span><%= dgettext("ui", "Create tag") %>: <strong><%= @post_editor.tag_query %></strong></span>
</button> </button>
<% end %> <% end %>
</div> </div>
@@ -163,12 +163,12 @@
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Author") %></label> <label><%= dgettext("ui", "Author") %></label>
<input class="post-editor-input" type="text" name="post_editor[author]" value={@post_editor.form["author"]} /> <input class="post-editor-input" type="text" name="post_editor[author]" value={@post_editor.form["author"]} />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Language") %></label> <label><%= dgettext("ui", "Language") %></label>
<div class="editor-language-row"> <div class="editor-language-row">
<select class="post-editor-input" name="post_editor[language]"> <select class="post-editor-input" name="post_editor[language]">
<%= for language <- @post_editor.languages do %> <%= for language <- @post_editor.languages do %>
@@ -184,7 +184,7 @@
phx-target={@myself} phx-target={@myself}
disabled={not @post_editor.detect_language_enabled?} disabled={not @post_editor.detect_language_enabled?}
> >
<%= translated("Detect") %> <%= dgettext("ui", "Detect") %>
</button> </button>
</div> </div>
</div> </div>
@@ -193,25 +193,25 @@
<label class="editor-checkbox-label"> <label class="editor-checkbox-label">
<input type="hidden" name="post_editor[do_not_translate]" value="false" /> <input type="hidden" name="post_editor[do_not_translate]" value="false" />
<input type="checkbox" name="post_editor[do_not_translate]" value="true" checked={@post_editor.form["do_not_translate"]} /> <input type="checkbox" name="post_editor[do_not_translate]" value="true" checked={@post_editor.form["do_not_translate"]} />
<span><%= translated("Do Not Translate") %></span> <span><%= dgettext("ui", "Do Not Translate") %></span>
</label> </label>
</div> </div>
<div class="editor-field-row"> <div class="editor-field-row">
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Slug") %></label> <label><%= dgettext("ui", "Slug") %></label>
<input class="post-editor-input is-readonly" type="text" readonly value={@post_editor.slug} /> <input class="post-editor-input is-readonly" type="text" readonly value={@post_editor.slug} />
</div> </div>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Categories") %></label> <label><%= dgettext("ui", "Categories") %></label>
<div class="tag-input-container"> <div class="tag-input-container">
<input type="hidden" name="post_editor[categories]" value={@post_editor.form["categories"]} /> <input type="hidden" name="post_editor[categories]" value={@post_editor.form["categories"]} />
<div class="tag-input-wrapper"> <div class="tag-input-wrapper">
<%= for category <- @post_editor.category_values do %> <%= for category <- @post_editor.category_values do %>
<span class="tag-chip"> <span class="tag-chip">
<span><%= category %></span> <span><%= category %></span>
<button class="tag-chip-remove" type="button" phx-click="remove_post_editor_category" phx-value-category={category} phx-target={@myself} aria-label={translated("Remove category")}>×</button> <button class="tag-chip-remove" type="button" phx-click="remove_post_editor_category" phx-value-category={category} phx-target={@myself} aria-label={dgettext("ui", "Remove category")}>×</button>
</span> </span>
<% end %> <% end %>
@@ -220,7 +220,7 @@
type="text" type="text"
name="post_editor[category_query]" name="post_editor[category_query]"
value={@post_editor.category_query} value={@post_editor.category_query}
placeholder={translated("Add category")} placeholder={dgettext("ui", "Add category")}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
@@ -236,7 +236,7 @@
<%= if @post_editor.category_query_addable? do %> <%= if @post_editor.category_query_addable? do %>
<button class="tag-suggestion create-new" type="button" phx-click="add_post_editor_category" phx-value-category={@post_editor.category_query} phx-target={@myself}> <button class="tag-suggestion create-new" type="button" phx-click="add_post_editor_category" phx-value-category={@post_editor.category_query} phx-target={@myself}>
<span class="tag-suggestion-icon">+</span> <span class="tag-suggestion-icon">+</span>
<span><%= translated("Create category") %>: <strong><%= @post_editor.category_query %></strong></span> <span><%= dgettext("ui", "Create category") %>: <strong><%= @post_editor.category_query %></strong></span>
</button> </button>
<% end %> <% end %>
</div> </div>
@@ -247,9 +247,9 @@
<%= if @post_editor.show_template_selector? do %> <%= if @post_editor.show_template_selector? do %>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Template") %></label> <label><%= dgettext("ui", "Template") %></label>
<select class="post-editor-input" name="post_editor[template_slug]"> <select class="post-editor-input" name="post_editor[template_slug]">
<option value=""><%= translated("Default") %></option> <option value=""><%= dgettext("ui", "Default") %></option>
<%= for template <- @post_editor.template_options do %> <%= for template <- @post_editor.template_options do %>
<option value={template.slug} selected={template.slug == @post_editor.form["template_slug"]}><%= template.title %></option> <option value={template.slug} selected={template.slug == @post_editor.form["template_slug"]}><%= template.title %></option>
<% end %> <% end %>
@@ -258,10 +258,10 @@
<% end %> <% end %>
<div class="post-editor-links-panel"> <div class="post-editor-links-panel">
<strong><%= translated("Post Links") %></strong> <strong><%= dgettext("ui", "Post Links") %></strong>
<div class="post-editor-links-columns"> <div class="post-editor-links-columns">
<div> <div>
<span class="post-editor-links-label"><%= translated("Backlinks") %></span> <span class="post-editor-links-label"><%= dgettext("ui", "Backlinks") %></span>
<%= if Enum.any?(@post_editor.post_links.backlinks) do %> <%= if Enum.any?(@post_editor.post_links.backlinks) do %>
<ul class="editor-list compact"> <ul class="editor-list compact">
<%= for item <- @post_editor.post_links.backlinks do %> <%= for item <- @post_editor.post_links.backlinks do %>
@@ -269,11 +269,11 @@
<% end %> <% end %>
</ul> </ul>
<% else %> <% else %>
<span class="post-editor-empty"><%= translated("No items") %></span> <span class="post-editor-empty"><%= dgettext("ui", "No items") %></span>
<% end %> <% end %>
</div> </div>
<div> <div>
<span class="post-editor-links-label"><%= translated("Links To") %></span> <span class="post-editor-links-label"><%= dgettext("ui", "Links To") %></span>
<%= if Enum.any?(@post_editor.post_links.outlinks) do %> <%= if Enum.any?(@post_editor.post_links.outlinks) do %>
<ul class="editor-list compact"> <ul class="editor-list compact">
<%= for item <- @post_editor.post_links.outlinks do %> <%= for item <- @post_editor.post_links.outlinks do %>
@@ -281,7 +281,7 @@
<% end %> <% end %>
</ul> </ul>
<% else %> <% else %>
<span class="post-editor-empty"><%= translated("No items") %></span> <span class="post-editor-empty"><%= dgettext("ui", "No items") %></span>
<% end %> <% end %>
</div> </div>
</div> </div>
@@ -290,7 +290,7 @@
<aside class="editor-media-panel post-editor-side-panel"> <aside class="editor-media-panel post-editor-side-panel">
<div class="post-editor-side-panel-header"> <div class="post-editor-side-panel-header">
<strong><%= translated("Linked Media") %></strong> <strong><%= dgettext("ui", "Linked Media") %></strong>
</div> </div>
<%= if Enum.any?(@post_editor.linked_media) do %> <%= if Enum.any?(@post_editor.linked_media) do %>
@@ -298,24 +298,24 @@
<%= for item <- @post_editor.linked_media do %> <%= for item <- @post_editor.linked_media do %>
<li class="post-editor-media-item"> <li class="post-editor-media-item">
<span class="post-editor-media-title"><%= item.name %></span> <span class="post-editor-media-title"><%= item.name %></span>
<span class="post-editor-media-meta"><%= translated("Order") %>: <%= item.sort_order %></span> <span class="post-editor-media-meta"><%= dgettext("ui", "Order") %>: <%= item.sort_order %></span>
</li> </li>
<% end %> <% end %>
</ul> </ul>
<% else %> <% else %>
<div class="post-editor-empty"><%= translated("No linked media") %></div> <div class="post-editor-empty"><%= dgettext("ui", "No linked media") %></div>
<% end %> <% end %>
</aside> </aside>
</div> </div>
<button class={["metadata-toggle", if(@post_editor.excerpt_expanded, do: "expanded")]} type="button" phx-click="toggle_post_excerpt" phx-target={@myself}> <button class={["metadata-toggle", if(@post_editor.excerpt_expanded, do: "expanded")]} type="button" phx-click="toggle_post_excerpt" phx-target={@myself}>
<span class="metadata-toggle-chevron"><%= if @post_editor.excerpt_expanded, do: "▼", else: "▶" %></span> <span class="metadata-toggle-chevron"><%= if @post_editor.excerpt_expanded, do: "▼", else: "▶" %></span>
<span><%= translated("Excerpt") %></span> <span><%= dgettext("ui", "Excerpt") %></span>
</button> </button>
<div class={["editor-excerpt-panel", if(not @post_editor.excerpt_expanded, do: "is-collapsed")]}> <div class={["editor-excerpt-panel", if(not @post_editor.excerpt_expanded, do: "is-collapsed")]}>
<div class="editor-field"> <div class="editor-field">
<label><%= translated("Excerpt") %></label> <label><%= dgettext("ui", "Excerpt") %></label>
<textarea class="post-editor-textarea post-editor-excerpt" name="post_editor[excerpt]" rows="4"><%= @post_editor.form["excerpt"] %></textarea> <textarea class="post-editor-textarea post-editor-excerpt" name="post_editor[excerpt]" rows="4"><%= @post_editor.form["excerpt"] %></textarea>
</div> </div>
</div> </div>
@@ -323,7 +323,7 @@
<div class="editor-body"> <div class="editor-body">
<div class="editor-toolbar"> <div class="editor-toolbar">
<div class="editor-toolbar-left"> <div class="editor-toolbar-left">
<label><%= translated("Content") %></label> <label><%= dgettext("ui", "Content") %></label>
</div> </div>
<div class="editor-toolbar-center"> <div class="editor-toolbar-center">
@@ -351,7 +351,7 @@
phx-click="open_overlay" phx-click="open_overlay"
phx-value-kind="insert_link" phx-value-kind="insert_link"
> >
<%= translated("Insert Link") %> <%= dgettext("ui", "Insert Link") %>
</button> </button>
<button <button
class="insert-media-button" class="insert-media-button"
@@ -360,7 +360,7 @@
phx-click="open_overlay" phx-click="open_overlay"
phx-value-kind="insert_media" phx-value-kind="insert_media"
> >
<%= translated("Insert Media") %> <%= dgettext("ui", "Insert Media") %>
</button> </button>
<% end %> <% end %>
@@ -372,7 +372,7 @@
phx-click="open_overlay" phx-click="open_overlay"
phx-value-kind="gallery" phx-value-kind="gallery"
> >
<%= translated("Gallery") %> (<%= @post_editor.gallery_count %>) <%= dgettext("ui", "Gallery") %> (<%= @post_editor.gallery_count %>)
</button> </button>
<% end %> <% end %>
</div> </div>
@@ -383,7 +383,7 @@
<%= if @post_editor.preview_url do %> <%= if @post_editor.preview_url do %>
<iframe class="editor-preview-frame" src={@post_editor.preview_url}></iframe> <iframe class="editor-preview-frame" src={@post_editor.preview_url}></iframe>
<% else %> <% else %>
<div class="post-editor-empty"><%= translated("Preview unavailable") %></div> <div class="post-editor-empty"><%= dgettext("ui", "Preview unavailable") %></div>
<% end %> <% end %>
</div> </div>
<% else %> <% else %>
@@ -406,10 +406,10 @@
</form> </form>
<div class="editor-footer"> <div class="editor-footer">
<span><strong><%= translated("Created") %>:</strong> <%= @post_editor.footer.created_at %></span> <span><strong><%= dgettext("ui", "Created") %>:</strong> <%= @post_editor.footer.created_at %></span>
<span><strong><%= translated("Updated") %>:</strong> <%= @post_editor.footer.updated_at %></span> <span><strong><%= dgettext("ui", "Updated") %>:</strong> <%= @post_editor.footer.updated_at %></span>
<%= if @post_editor.footer.published_at do %> <%= if @post_editor.footer.published_at do %>
<span><strong><%= translated("Published") %>:</strong> <%= @post_editor.footer.published_at %></span> <span><strong><%= dgettext("ui", "Published") %>:</strong> <%= @post_editor.footer.published_at %></span>
<% end %> <% end %>
</div> </div>
</div> </div>

View File

@@ -4,8 +4,8 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do
use Phoenix.LiveComponent use Phoenix.LiveComponent
alias BDS.{Scripts, Scripting} alias BDS.{Scripts, Scripting}
alias BDS.Desktop.ShellData
alias BDS.Scripts.Script alias BDS.Scripts.Script
use Gettext, backend: BDS.Gettext
embed_templates("script_editor_html/*") embed_templates("script_editor_html/*")
@@ -115,18 +115,18 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do
socket socket
|> assign(:draft, nil) |> assign(:draft, nil)
|> build_data() |> build_data()
|> notify_output(translated("Scripts"), translated("Script saved")) |> notify_output(dgettext("ui", "Scripts"), dgettext("ui", "Script saved"))
|> notify_reload() |> notify_reload()
{:error, reason} -> {:error, reason} ->
socket socket
|> notify_output(translated("Scripts"), inspect(reason), "error") |> notify_output(dgettext("ui", "Scripts"), inspect(reason), "error")
|> notify_reload() |> notify_reload()
end end
{:error, reason} -> {:error, reason} ->
socket socket
|> notify_output(translated("Scripts"), inspect(reason), "error") |> notify_output(dgettext("ui", "Scripts"), inspect(reason), "error")
|> notify_reload() |> notify_reload()
end end
end end
@@ -151,24 +151,24 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do
socket socket
|> assign(:draft, nil) |> assign(:draft, nil)
|> build_data() |> build_data()
|> notify_output(translated("Scripts"), translated("Script published")) |> notify_output(dgettext("ui", "Scripts"), dgettext("ui", "Script published"))
|> notify_reload() |> notify_reload()
{:error, reason} -> {:error, reason} ->
socket socket
|> notify_output(translated("Scripts"), inspect(reason), "error") |> notify_output(dgettext("ui", "Scripts"), inspect(reason), "error")
|> notify_reload() |> notify_reload()
end end
{:error, reason} -> {:error, reason} ->
socket socket
|> notify_output(translated("Scripts"), inspect(reason), "error") |> notify_output(dgettext("ui", "Scripts"), inspect(reason), "error")
|> notify_reload() |> notify_reload()
end end
{:error, reason} -> {:error, reason} ->
socket socket
|> notify_output(translated("Scripts"), inspect(reason), "error") |> notify_output(dgettext("ui", "Scripts"), inspect(reason), "error")
|> notify_reload() |> notify_reload()
end end
end end
@@ -184,10 +184,10 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do
%Script{} = script -> %Script{} = script ->
case Scripting.validate(current_draft(socket.assigns, script)["content"] || "") do case Scripting.validate(current_draft(socket.assigns, script)["content"] || "") do
:ok -> :ok ->
notify_output(socket, translated("Scripts"), translated("Syntax is valid")) notify_output(socket, dgettext("ui", "Scripts"), dgettext("ui", "Syntax is valid"))
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Scripts"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Scripts"), inspect(reason), "error")
end end
end end
end end
@@ -209,10 +209,10 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do
[] []
) do ) do
{:ok, result} -> {:ok, result} ->
notify_output(socket, translated("Scripts"), inspect(result)) notify_output(socket, dgettext("ui", "Scripts"), inspect(result))
{:error, reason} -> {:error, reason} ->
notify_output(socket, translated("Scripts"), inspect(reason), "error") notify_output(socket, dgettext("ui", "Scripts"), inspect(reason), "error")
end end
end end
end end
@@ -227,7 +227,7 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do
{:error, reason} -> {:error, reason} ->
socket socket
|> notify_output(translated("Scripts"), inspect(reason), "error") |> notify_output(dgettext("ui", "Scripts"), inspect(reason), "error")
|> notify_reload() |> notify_reload()
end end
end end
@@ -287,7 +287,4 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do
send(self(), :reload_shell) send(self(), :reload_shell)
socket socket
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -7,30 +7,30 @@
"status-#{@script_editor.status}" "status-#{@script_editor.status}"
]} data-testid="script-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@script_editor.status) %></span> ]} data-testid="script-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@script_editor.status) %></span>
<%= if @script_editor.can_publish? do %> <%= if @script_editor.can_publish? do %>
<button class="success" data-testid="script-publish-button" type="button" phx-click="publish_script_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Publish", %{}, BDS.Desktop.UILocale.current()) %></button> <button class="success" data-testid="script-publish-button" type="button" phx-click="publish_script_editor" phx-target={@myself}><%= dgettext("ui", "Publish") %></button>
<% end %> <% end %>
<button class="secondary scripts-save-button" type="button" phx-click="save_script_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Save", %{}, BDS.Desktop.UILocale.current()) %></button> <button class="secondary scripts-save-button" type="button" phx-click="save_script_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button>
<button class="secondary scripts-run-button" type="button" phx-click="run_script_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Run", %{}, BDS.Desktop.UILocale.current()) %></button> <button class="secondary scripts-run-button" type="button" phx-click="run_script_editor" phx-target={@myself}><%= dgettext("ui", "Run") %></button>
<button class="secondary scripts-check-button" type="button" phx-click="check_script_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Check Syntax", %{}, BDS.Desktop.UILocale.current()) %></button> <button class="secondary scripts-check-button" type="button" phx-click="check_script_editor" phx-target={@myself}><%= dgettext("ui", "Check Syntax") %></button>
<button class="secondary danger" type="button" phx-click="delete_script_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Delete", %{}, BDS.Desktop.UILocale.current()) %></button> <button class="secondary danger" type="button" phx-click="delete_script_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
</div> </div>
</div> </div>
<form class="editor-content scripts-view" phx-change="change_script_editor" phx-target={@myself}> <form class="editor-content scripts-view" phx-change="change_script_editor" phx-target={@myself}>
<div class="editor-header-row scripts-meta-row"> <div class="editor-header-row scripts-meta-row">
<div class="editor-meta"> <div class="editor-meta">
<div class="editor-field-row"> <div class="editor-field-row">
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Title", %{}, BDS.Desktop.UILocale.current()) %></label><input type="text" name="script_editor[title]" value={@script_editor.title} /></div> <div class="editor-field"><label><%= dgettext("ui", "Title") %></label><input type="text" name="script_editor[title]" value={@script_editor.title} /></div>
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Slug", %{}, BDS.Desktop.UILocale.current()) %></label><input type="text" name="script_editor[slug]" value={@script_editor.slug} /></div> <div class="editor-field"><label><%= dgettext("ui", "Slug") %></label><input type="text" name="script_editor[slug]" value={@script_editor.slug} /></div>
</div> </div>
<div class="editor-field-row"> <div class="editor-field-row">
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Kind", %{}, BDS.Desktop.UILocale.current()) %></label><select name="script_editor[kind]"><option value="utility" selected={@script_editor.kind == "utility"}>utility</option><option value="macro" selected={@script_editor.kind == "macro"}>macro</option><option value="transform" selected={@script_editor.kind == "transform"}>transform</option></select></div> <div class="editor-field"><label><%= dgettext("ui", "Kind") %></label><select name="script_editor[kind]"><option value="utility" selected={@script_editor.kind == "utility"}>utility</option><option value="macro" selected={@script_editor.kind == "macro"}>macro</option><option value="transform" selected={@script_editor.kind == "transform"}>transform</option></select></div>
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Entrypoint", %{}, BDS.Desktop.UILocale.current()) %></label><select name="script_editor[entrypoint]"><%= for entrypoint <- @script_editor.entrypoints do %><option value={entrypoint} selected={entrypoint == @script_editor.entrypoint}><%= entrypoint %></option><% end %></select></div> <div class="editor-field"><label><%= dgettext("ui", "Entrypoint") %></label><select name="script_editor[entrypoint]"><%= for entrypoint <- @script_editor.entrypoints do %><option value={entrypoint} selected={entrypoint == @script_editor.entrypoint}><%= entrypoint %></option><% end %></select></div>
<div class="editor-field scripts-enabled-field"><label><input type="checkbox" name="script_editor[enabled]" checked={@script_editor.enabled} /> <%= BDS.Desktop.ShellData.translate("Enabled", %{}, BDS.Desktop.UILocale.current()) %></label></div> <div class="editor-field scripts-enabled-field"><label><input type="checkbox" name="script_editor[enabled]" checked={@script_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></div>
</div> </div>
</div> </div>
</div> </div>
<div class="editor-body scripts-editor"> <div class="editor-body scripts-editor">
<div class="editor-toolbar scripts-toolbar"><div class="editor-toolbar-left"><label><%= BDS.Desktop.ShellData.translate("Content", %{}, BDS.Desktop.UILocale.current()) %></label></div></div> <div class="editor-toolbar scripts-toolbar"><div class="editor-toolbar-left"><label><%= dgettext("ui", "Content") %></label></div></div>
<div <div
id={"script-editor-monaco-shell-#{@script_editor.id}"} id={"script-editor-monaco-shell-#{@script_editor.id}"}
class="scripts-monaco monaco-editor-shell" class="scripts-monaco monaco-editor-shell"
@@ -44,6 +44,6 @@
<textarea id={"script-editor-content-#{@script_editor.id}"} class="monaco-editor-input code-editor-textarea" name="script_editor[content]" spellcheck="false"><%= @script_editor.content %></textarea> <textarea id={"script-editor-content-#{@script_editor.id}"} class="monaco-editor-input code-editor-textarea" name="script_editor[content]" spellcheck="false"><%= @script_editor.content %></textarea>
</div> </div>
</div> </div>
<div class="editor-footer"><span class="text-muted text-small"><%= BDS.Desktop.ShellData.translate("Created", %{}, BDS.Desktop.UILocale.current()) %>: <%= BDS.Persistence.timestamp_to_iso8601(@script_editor.created_at) %></span><span class="text-muted text-small"><%= BDS.Desktop.ShellData.translate("Updated", %{}, BDS.Desktop.UILocale.current()) %>: <%= BDS.Persistence.timestamp_to_iso8601(@script_editor.updated_at) %></span></div> <div class="editor-footer"><span class="text-muted text-small"><%= dgettext("ui", "Created") %>: <%= BDS.Persistence.timestamp_to_iso8601(@script_editor.created_at) %></span><span class="text-muted text-small"><%= dgettext("ui", "Updated") %>: <%= BDS.Persistence.timestamp_to_iso8601(@script_editor.updated_at) %></span></div>
</form> </form>
</div> </div>

View File

@@ -2,10 +2,10 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
@moduledoc false @moduledoc false
use Phoenix.LiveComponent use Phoenix.LiveComponent
use Gettext, backend: BDS.Gettext
import Ecto.Query import Ecto.Query
alias BDS.Desktop.ShellData
alias BDS.Repo alias BDS.Repo
alias BDS.Templates.Template alias BDS.Templates.Template
@@ -386,7 +386,4 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
defp section_matches?(query, keywords), defp section_matches?(query, keywords),
do: Enum.any?(keywords, &String.contains?(&1, String.downcase(query))) do: Enum.any?(keywords, &String.contains?(&1, String.downcase(query)))
@spec translated(term(), term()) :: term()
def translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -4,8 +4,8 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
use Phoenix.Component use Phoenix.Component
alias BDS.AI alias BDS.AI
alias BDS.Desktop.ShellData
alias BDS.Desktop.ShellLive.SettingsEditor.EditorSettings alias BDS.Desktop.ShellLive.SettingsEditor.EditorSettings
use Gettext, backend: BDS.Gettext
@spec ai_form(term()) :: term() @spec ai_form(term()) :: term()
def ai_form(assigns) do def ai_form(assigns) do
@@ -88,7 +88,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
else else
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("AI Settings"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "AI Settings"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -153,7 +153,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
else else
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("AI Settings"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "AI Settings"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -168,7 +168,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("AI Settings"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "AI Settings"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -315,7 +315,4 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.AISettings do
trimmed -> trimmed trimmed -> trimmed
end end
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -4,7 +4,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.EditorSettings do
use Phoenix.Component use Phoenix.Component
alias BDS.Settings alias BDS.Settings
alias BDS.Desktop.ShellData use Gettext, backend: BDS.Gettext
@spec editor_form() :: term() @spec editor_form() :: term()
def editor_form do def editor_form do
@@ -42,7 +42,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.EditorSettings do
else else
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("Editor Settings"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Editor Settings"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -80,7 +80,4 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.EditorSettings do
defp truthy?(value), do: value in [true, "true", "on", "1", 1] defp truthy?(value), do: value in [true, "true", "on", "1", 1]
defp boolean_string(true), do: "true" defp boolean_string(true), do: "true"
defp boolean_string(false), do: "false" defp boolean_string(false), do: "false"
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -4,7 +4,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
use Phoenix.Component use Phoenix.Component
alias BDS.Metadata alias BDS.Metadata
alias BDS.Desktop.ShellData use Gettext, backend: BDS.Gettext
@protected_categories MapSet.new(["article", "aside", "page", "picture"]) @protected_categories MapSet.new(["article", "aside", "page", "picture"])
@default_category_settings %{ @default_category_settings %{
@@ -56,8 +56,8 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
name == "" -> name == "" ->
socket socket
|> append_output.( |> append_output.(
translated("Categories"), dgettext("ui", "Categories"),
translated("Category name is required"), dgettext("ui", "Category name is required"),
nil, nil,
"error" "error"
) )
@@ -72,7 +72,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("Categories"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Categories"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -104,7 +104,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
else else
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("Categories"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Categories"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -128,7 +128,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("Categories"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Categories"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -141,8 +141,8 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
MapSet.member?(@protected_categories, category) -> MapSet.member?(@protected_categories, category) ->
socket socket
|> append_output.( |> append_output.(
translated("Categories"), dgettext("ui", "Categories"),
translated("Protected categories cannot be removed"), dgettext("ui", "Protected categories cannot be removed"),
nil, nil,
"error" "error"
) )
@@ -155,7 +155,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("Categories"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Categories"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -197,7 +197,4 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories do
trimmed -> trimmed trimmed -> trimmed
end end
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -3,8 +3,8 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
use Phoenix.Component use Phoenix.Component
alias BDS.Desktop.ShellData
alias BDS.MCP.AgentConfig alias BDS.MCP.AgentConfig
use Gettext, backend: BDS.Gettext
@mcp_agents [ @mcp_agents [
%{id: :claude_code, label: "Claude Code", supported?: true}, %{id: :claude_code, label: "Claude Code", supported?: true},
@@ -47,15 +47,15 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("MCP"), format_config_error(reason), nil, "error") |> append_output.(dgettext("ui", "MCP"), format_config_error(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
_other -> _other ->
socket socket
|> append_output.( |> append_output.(
translated("MCP"), dgettext("ui", "MCP"),
translated("This MCP agent is not supported in the rewrite yet"), dgettext("ui", "This MCP agent is not supported in the rewrite yet"),
nil, nil,
"error" "error"
) )
@@ -70,28 +70,28 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
end end
defp format_config_error({:read_config, path, reason}) do defp format_config_error({:read_config, path, reason}) do
translated("Could not read MCP config %{path}: %{reason}", dgettext("ui", "Could not read MCP config %{path}: %{reason}",
path: path, path: path,
reason: inspect(reason) reason: inspect(reason)
) )
end end
defp format_config_error({:write_config, path, reason}) do defp format_config_error({:write_config, path, reason}) do
translated("Could not write MCP config %{path}: %{reason}", dgettext("ui", "Could not write MCP config %{path}: %{reason}",
path: path, path: path,
reason: inspect(reason) reason: inspect(reason)
) )
end end
defp format_config_error({:create_config_dir, path, reason}) do defp format_config_error({:create_config_dir, path, reason}) do
translated("Could not create MCP config folder %{path}: %{reason}", dgettext("ui", "Could not create MCP config folder %{path}: %{reason}",
path: path, path: path,
reason: inspect(reason) reason: inspect(reason)
) )
end end
defp format_config_error({:decode_config, path, _reason}) do defp format_config_error({:decode_config, path, _reason}) do
translated("Could not parse MCP config %{path}", path: path) dgettext("ui", "Could not parse MCP config %{path}", path: path)
end end
defp mcp_configured?(%{supported?: false}), do: false defp mcp_configured?(%{supported?: false}), do: false
@@ -124,7 +124,4 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
|> Map.get("mcpServers", %{}) |> Map.get("mcpServers", %{})
|> Map.has_key?("bDS") |> Map.has_key?("bDS")
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -4,7 +4,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ProjectSettings do
use Phoenix.Component use Phoenix.Component
alias BDS.Metadata alias BDS.Metadata
alias BDS.Desktop.ShellData use Gettext, backend: BDS.Gettext
@spec project_metadata(term()) :: term() @spec project_metadata(term()) :: term()
def project_metadata(assigns) do def project_metadata(assigns) do
@@ -56,7 +56,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ProjectSettings do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("Settings"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Settings"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -111,7 +111,4 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.ProjectSettings do
trimmed -> trimmed trimmed -> trimmed
end end
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -4,7 +4,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings do
use Phoenix.Component use Phoenix.Component
alias BDS.Metadata alias BDS.Metadata
alias BDS.Desktop.ShellData use Gettext, backend: BDS.Gettext
@spec publishing_form(term()) :: term() @spec publishing_form(term()) :: term()
def publishing_form(metadata) do def publishing_form(metadata) do
@@ -37,7 +37,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("Publishing"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Publishing"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -54,7 +54,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("Publishing"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Publishing"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -87,7 +87,4 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings do
trimmed -> trimmed trimmed -> trimmed
end end
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -4,7 +4,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.StyleEditor do
use Phoenix.Component use Phoenix.Component
alias BDS.Metadata alias BDS.Metadata
alias BDS.Desktop.ShellData use Gettext, backend: BDS.Gettext
@themes [ @themes [
"default", "default",
@@ -71,7 +71,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.StyleEditor do
{:error, reason} -> {:error, reason} ->
socket socket
|> append_output.(translated("Style"), inspect(reason), nil, "error") |> append_output.(dgettext("ui", "Style"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench) |> reload.(socket.assigns.workbench)
end end
end end
@@ -104,7 +104,4 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.StyleEditor do
dark_bg_color: "#0f172a" dark_bg_color: "#0f172a"
} }
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -8,51 +8,51 @@
> >
<div class="settings-view"> <div class="settings-view">
<div class="settings-header"> <div class="settings-header">
<h2 data-testid="editor-title"><%= translated("Settings") %></h2> <h2 data-testid="editor-title"><%= dgettext("ui", "Settings") %></h2>
<form class="settings-search" phx-change="change_settings_search" phx-target={@myself}> <form class="settings-search" phx-change="change_settings_search" phx-target={@myself}>
<input type="text" name="query" value={@settings_editor.search_query} placeholder={translated("Search settings")} /> <input type="text" name="query" value={@settings_editor.search_query} placeholder={dgettext("ui", "Search settings")} />
</form> </form>
</div> </div>
<div class="settings-content"> <div class="settings-content">
<%= if Enum.empty?(@settings_editor.active_sections) do %> <%= if Enum.empty?(@settings_editor.active_sections) do %>
<div class="settings-no-results"> <div class="settings-no-results">
<p><%= translated("No settings match the current search") %></p> <p><%= dgettext("ui", "No settings match the current search") %></p>
</div> </div>
<% end %> <% end %>
<%= if @settings_editor.project_visible? do %> <%= if @settings_editor.project_visible? do %>
<div class="setting-section" id="settings-section-project"> <div class="setting-section" id="settings-section-project">
<div class="setting-section-header"> <div class="setting-section-header">
<h3><%= translated("Project") %></h3> <h3><%= dgettext("ui", "Project") %></h3>
<p class="setting-section-description"><%= translated("Blog identity, URLs, authoring defaults, and bookmarklet setup") %></p> <p class="setting-section-description"><%= dgettext("ui", "Blog identity, URLs, authoring defaults, and bookmarklet setup") %></p>
</div> </div>
<form class="setting-section-content" phx-change="change_settings_project" phx-target={@myself}> <form class="setting-section-content" phx-change="change_settings_project" phx-target={@myself}>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
<label class="setting-label"><%= translated("Project Name") %></label> <label class="setting-label"><%= dgettext("ui", "Project Name") %></label>
</div> </div>
<div class="setting-control"><input type="text" name="settings_project[name]" value={@settings_editor.project["name"]} /></div> <div class="setting-control"><input type="text" name="settings_project[name]" value={@settings_editor.project["name"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Description") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Description") %></label></div>
<div class="setting-control"><textarea name="settings_project[description]" rows="3"><%= @settings_editor.project["description"] %></textarea></div> <div class="setting-control"><textarea name="settings_project[description]" rows="3"><%= @settings_editor.project["description"] %></textarea></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Data Path") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Data Path") %></label></div>
<div class="setting-control"> <div class="setting-control">
<div class="setting-input-group"> <div class="setting-input-group">
<input type="text" value={@settings_editor.project_data_path} readonly /> <input type="text" value={@settings_editor.project_data_path} readonly />
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="open_data_folder"><%= translated("Open") %></button> <button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="open_data_folder"><%= dgettext("ui", "Open") %></button>
</div> </div>
</div> </div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Public URL") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Public URL") %></label></div>
<div class="setting-control"><input type="url" name="settings_project[public_url]" value={@settings_editor.project["public_url"]} /></div> <div class="setting-control"><input type="url" name="settings_project[public_url]" value={@settings_editor.project["public_url"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Main Language") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Main Language") %></label></div>
<div class="setting-control"> <div class="setting-control">
<select name="settings_project[main_language]"> <select name="settings_project[main_language]">
<%= for language <- @settings_editor.supported_languages do %> <%= for language <- @settings_editor.supported_languages do %>
@@ -62,7 +62,7 @@
</div> </div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Blog Languages") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Blog Languages") %></label></div>
<div class="setting-control"> <div class="setting-control">
<div class="setting-input-group"> <div class="setting-input-group">
<%= for language <- @settings_editor.supported_languages do %> <%= for language <- @settings_editor.supported_languages do %>
@@ -75,15 +75,15 @@
</div> </div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Default Author") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Default Author") %></label></div>
<div class="setting-control"><input type="text" name="settings_project[default_author]" value={@settings_editor.project["default_author"]} /></div> <div class="setting-control"><input type="text" name="settings_project[default_author]" value={@settings_editor.project["default_author"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Max Posts Per Page") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Max Posts Per Page") %></label></div>
<div class="setting-control"><input type="number" min="1" max="500" name="settings_project[max_posts_per_page]" value={@settings_editor.project["max_posts_per_page"]} /></div> <div class="setting-control"><input type="number" min="1" max="500" name="settings_project[max_posts_per_page]" value={@settings_editor.project["max_posts_per_page"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Blogmark Category") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Blogmark Category") %></label></div>
<div class="setting-control"> <div class="setting-control">
<select name="settings_project[blogmark_category]"> <select name="settings_project[blogmark_category]">
<%= for category <- Enum.map(@settings_editor.categories, & &1.name) do %> <%= for category <- Enum.map(@settings_editor.categories, & &1.name) do %>
@@ -93,67 +93,67 @@
</div> </div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Blogmark Bookmarklet") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Blogmark Bookmarklet") %></label></div>
<div class="setting-control"><p class="setting-description"><%= translated("Bookmarklet copy support is wired through the desktop runtime and project public URL.") %></p></div> <div class="setting-control"><p class="setting-description"><%= dgettext("ui", "Bookmarklet copy support is wired through the desktop runtime and project public URL.") %></p></div>
</div> </div>
</form> </form>
<div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_project" phx-target={@myself}><%= translated("Save") %></button></div> <div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_project" phx-target={@myself}><%= dgettext("ui", "Save") %></button></div>
</div> </div>
<% end %> <% end %>
<%= if @settings_editor.editor_visible? do %> <%= if @settings_editor.editor_visible? do %>
<div class="setting-section" id="settings-section-editor"> <div class="setting-section" id="settings-section-editor">
<div class="setting-section-header"> <div class="setting-section-header">
<h3><%= translated("Editor") %></h3> <h3><%= dgettext("ui", "Editor") %></h3>
<p class="setting-section-description"><%= translated("Default editing mode and diff presentation") %></p> <p class="setting-section-description"><%= dgettext("ui", "Default editing mode and diff presentation") %></p>
</div> </div>
<form class="setting-section-content" phx-change="change_settings_editor" phx-target={@myself}> <form class="setting-section-content" phx-change="change_settings_editor" phx-target={@myself}>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Default Editor Mode") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Default Editor Mode") %></label></div>
<div class="setting-control"> <div class="setting-control">
<select name="settings_editor[default_mode]"> <select name="settings_editor[default_mode]">
<option value="wysiwyg" selected={@settings_editor.editor["default_mode"] == "wysiwyg"}><%= translated("WYSIWYG") %></option> <option value="wysiwyg" selected={@settings_editor.editor["default_mode"] == "wysiwyg"}><%= dgettext("ui", "WYSIWYG") %></option>
<option value="markdown" selected={@settings_editor.editor["default_mode"] == "markdown"}><%= translated("Markdown") %></option> <option value="markdown" selected={@settings_editor.editor["default_mode"] == "markdown"}><%= dgettext("ui", "Markdown") %></option>
<option value="preview" selected={@settings_editor.editor["default_mode"] == "preview"}><%= translated("Preview") %></option> <option value="preview" selected={@settings_editor.editor["default_mode"] == "preview"}><%= dgettext("ui", "Preview") %></option>
</select> </select>
</div> </div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Diff View Style") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Diff View Style") %></label></div>
<div class="setting-control"> <div class="setting-control">
<select name="settings_editor[diff_view_style]"> <select name="settings_editor[diff_view_style]">
<option value="inline" selected={@settings_editor.editor["diff_view_style"] == "inline"}><%= translated("Inline") %></option> <option value="inline" selected={@settings_editor.editor["diff_view_style"] == "inline"}><%= dgettext("ui", "Inline") %></option>
<option value="side-by-side" selected={@settings_editor.editor["diff_view_style"] == "side-by-side"}><%= translated("Side by Side") %></option> <option value="side-by-side" selected={@settings_editor.editor["diff_view_style"] == "side-by-side"}><%= dgettext("ui", "Side by Side") %></option>
</select> </select>
</div> </div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Wrap Long Lines") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Wrap Long Lines") %></label></div>
<div class="setting-control"><label><input type="checkbox" name="settings_editor[wrap_long_lines]" checked={@settings_editor.editor["wrap_long_lines"]} /> <%= translated("Enable line wrapping in diffs") %></label></div> <div class="setting-control"><label><input type="checkbox" name="settings_editor[wrap_long_lines]" checked={@settings_editor.editor["wrap_long_lines"]} /> <%= dgettext("ui", "Enable line wrapping in diffs") %></label></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Hide Unchanged Regions") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Hide Unchanged Regions") %></label></div>
<div class="setting-control"><label><input type="checkbox" name="settings_editor[hide_unchanged_regions]" checked={@settings_editor.editor["hide_unchanged_regions"]} /> <%= translated("Collapse unchanged diff hunks") %></label></div> <div class="setting-control"><label><input type="checkbox" name="settings_editor[hide_unchanged_regions]" checked={@settings_editor.editor["hide_unchanged_regions"]} /> <%= dgettext("ui", "Collapse unchanged diff hunks") %></label></div>
</div> </div>
</form> </form>
<div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_editor" phx-target={@myself}><%= translated("Save") %></button></div> <div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button></div>
</div> </div>
<% end %> <% end %>
<%= if @settings_editor.content_visible? do %> <%= if @settings_editor.content_visible? do %>
<div class="setting-section" id="settings-section-content"> <div class="setting-section" id="settings-section-content">
<div class="setting-section-header"><h3><%= translated("Content Categories") %></h3><p class="setting-section-description"><%= translated("Category defaults, rendering flags, and template wiring") %></p></div> <div class="setting-section-header"><h3><%= dgettext("ui", "Content Categories") %></h3><p class="setting-section-description"><%= dgettext("ui", "Category defaults, rendering flags, and template wiring") %></p></div>
<div class="setting-section-content"> <div class="setting-section-content">
<table class="categories-table"> <table class="categories-table">
<thead> <thead>
<tr> <tr>
<th><%= translated("Category") %></th> <th><%= dgettext("ui", "Category") %></th>
<th><%= translated("Title") %></th> <th><%= dgettext("ui", "Title") %></th>
<th><%= translated("Render in Lists") %></th> <th><%= dgettext("ui", "Render in Lists") %></th>
<th><%= translated("Show Titles") %></th> <th><%= dgettext("ui", "Show Titles") %></th>
<th><%= translated("Post Template") %></th> <th><%= dgettext("ui", "Post Template") %></th>
<th><%= translated("List Template") %></th> <th><%= dgettext("ui", "List Template") %></th>
<th><%= translated("Actions") %></th> <th><%= dgettext("ui", "Actions") %></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -167,7 +167,7 @@
<td><input type="checkbox" name="category_settings[show_title]" checked={category.show_title} form={"category-form-#{category.name}"} /></td> <td><input type="checkbox" name="category_settings[show_title]" checked={category.show_title} form={"category-form-#{category.name}"} /></td>
<td> <td>
<select name="category_settings[post_template_slug]" form={"category-form-#{category.name}"}> <select name="category_settings[post_template_slug]" form={"category-form-#{category.name}"}>
<option value=""><%= translated("Default") %></option> <option value=""><%= dgettext("ui", "Default") %></option>
<%= for template <- @settings_editor.template_options.post do %> <%= for template <- @settings_editor.template_options.post do %>
<option value={template.slug} selected={template.slug == category.post_template_slug}><%= template.title %></option> <option value={template.slug} selected={template.slug == category.post_template_slug}><%= template.title %></option>
<% end %> <% end %>
@@ -175,7 +175,7 @@
</td> </td>
<td> <td>
<select name="category_settings[list_template_slug]" form={"category-form-#{category.name}"}> <select name="category_settings[list_template_slug]" form={"category-form-#{category.name}"}>
<option value=""><%= translated("Default") %></option> <option value=""><%= dgettext("ui", "Default") %></option>
<%= for template <- @settings_editor.template_options.list do %> <%= for template <- @settings_editor.template_options.list do %>
<option value={template.slug} selected={template.slug == category.list_template_slug}><%= template.title %></option> <option value={template.slug} selected={template.slug == category.list_template_slug}><%= template.title %></option>
<% end %> <% end %>
@@ -186,8 +186,8 @@
<form id={"category-form-#{category.name}"} phx-submit="save_settings_category" phx-target={@myself}> <form id={"category-form-#{category.name}"} phx-submit="save_settings_category" phx-target={@myself}>
<input type="hidden" name="category_settings[category]" value={category.name} /> <input type="hidden" name="category_settings[category]" value={category.name} />
</form> </form>
<button class="secondary" type="submit" form={"category-form-#{category.name}"}><%= translated("Save") %></button> <button class="secondary" type="submit" form={"category-form-#{category.name}"}><%= dgettext("ui", "Save") %></button>
<button class="secondary" type="button" phx-click="remove_settings_category" phx-target={@myself} phx-value-category={category.name} disabled={category.protected?}><%= translated("Remove") %></button> <button class="secondary" type="button" phx-click="remove_settings_category" phx-target={@myself} phx-value-category={category.name} disabled={category.protected?}><%= dgettext("ui", "Remove") %></button>
</div> </div>
</td> </td>
</tr> </tr>
@@ -195,103 +195,103 @@
</tbody> </tbody>
</table> </table>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Add Category") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Add Category") %></label></div>
<div class="setting-control"> <div class="setting-control">
<div class="setting-input-group"> <div class="setting-input-group">
<input type="text" value={@settings_editor.new_category} phx-change="change_settings_new_category" phx-target={@myself} name="name" /> <input type="text" value={@settings_editor.new_category} phx-change="change_settings_new_category" phx-target={@myself} name="name" />
<button class="primary" type="button" phx-click="add_settings_category" phx-target={@myself}><%= translated("Add") %></button> <button class="primary" type="button" phx-click="add_settings_category" phx-target={@myself}><%= dgettext("ui", "Add") %></button>
</div> </div>
</div> </div>
</div> </div>
<div class="setting-actions"><button class="secondary" type="button" phx-click="reset_settings_categories" phx-target={@myself}><%= translated("Reset to Defaults") %></button></div> <div class="setting-actions"><button class="secondary" type="button" phx-click="reset_settings_categories" phx-target={@myself}><%= dgettext("ui", "Reset to Defaults") %></button></div>
</div> </div>
</div> </div>
<% end %> <% end %>
<%= if @settings_editor.ai_visible? do %> <%= if @settings_editor.ai_visible? do %>
<div class="setting-section" id="settings-section-ai"> <div class="setting-section" id="settings-section-ai">
<div class="setting-section-header"><h3><%= translated("AI") %></h3><p class="setting-section-description"><%= translated("OpenAI-compatible endpoints, model routing, airplane mode, and system prompt") %></p></div> <div class="setting-section-header"><h3><%= dgettext("ui", "AI") %></h3><p class="setting-section-description"><%= dgettext("ui", "OpenAI-compatible endpoints, model routing, airplane mode, and system prompt") %></p></div>
<form class="setting-section-content" phx-change="change_settings_ai" phx-target={@myself}> <form class="setting-section-content" phx-change="change_settings_ai" phx-target={@myself}>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Online Endpoint URL") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Endpoint URL") %></label></div>
<div class="setting-control"> <div class="setting-control">
<div class="setting-input-group"> <div class="setting-input-group">
<input type="url" name="settings_ai[online_url]" value={@settings_editor.ai["online_url"]} /> <input type="url" name="settings_ai[online_url]" value={@settings_editor.ai["online_url"]} />
<button class="secondary" type="button" phx-click="refresh_settings_ai_models" phx-target={@myself} phx-value-endpoint="online"><%= translated("Refresh Online Models") %></button> <button class="secondary" type="button" phx-click="refresh_settings_ai_models" phx-target={@myself} phx-value-endpoint="online"><%= dgettext("ui", "Refresh Online Models") %></button>
</div> </div>
</div> </div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Online API Key") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online API Key") %></label></div>
<div class="setting-control"><input type="password" name="settings_ai[online_api_key]" value={@settings_editor.ai["online_api_key"]} /></div> <div class="setting-control"><input type="password" name="settings_ai[online_api_key]" value={@settings_editor.ai["online_api_key"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Online Chat Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Chat Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-online-models" name="settings_ai[online_chat_model]" value={@settings_editor.ai["online_chat_model"]} /></div> <div class="setting-control"><input type="text" list="settings-ai-online-models" name="settings_ai[online_chat_model]" value={@settings_editor.ai["online_chat_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Online Chat Tools") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Chat Tools") %></label></div>
<div class="setting-control"><label><input type="checkbox" name="settings_ai[online_chat_tools]" checked={@settings_editor.ai["online_chat_tools"]} /> <%= translated("Enable tool calls for the online chat model") %></label></div> <div class="setting-control"><label><input type="checkbox" name="settings_ai[online_chat_tools]" checked={@settings_editor.ai["online_chat_tools"]} /> <%= dgettext("ui", "Enable tool calls for the online chat model") %></label></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Online Chat Reasoning") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Chat Reasoning") %></label></div>
<div class="setting-control"><label><input type="checkbox" name="settings_ai[online_chat_disable_reasoning]" checked={@settings_editor.ai["online_chat_disable_reasoning"]} /> <%= translated("Disable reasoning output for the online chat model") %></label></div> <div class="setting-control"><label><input type="checkbox" name="settings_ai[online_chat_disable_reasoning]" checked={@settings_editor.ai["online_chat_disable_reasoning"]} /> <%= dgettext("ui", "Disable reasoning output for the online chat model") %></label></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Online Title Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Title Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-online-models" name="settings_ai[online_title_model]" value={@settings_editor.ai["online_title_model"]} /></div> <div class="setting-control"><input type="text" list="settings-ai-online-models" name="settings_ai[online_title_model]" value={@settings_editor.ai["online_title_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Online Image Analysis Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Image Analysis Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-online-models" name="settings_ai[online_image_analysis_model]" value={@settings_editor.ai["online_image_analysis_model"]} /></div> <div class="setting-control"><input type="text" list="settings-ai-online-models" name="settings_ai[online_image_analysis_model]" value={@settings_editor.ai["online_image_analysis_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Online Image Support") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Image Support") %></label></div>
<div class="setting-control"><label><input type="checkbox" name="settings_ai[online_chat_images]" checked={@settings_editor.ai["online_chat_images"]} /> <%= translated("Enable image analysis for the online image analysis model") %></label></div> <div class="setting-control"><label><input type="checkbox" name="settings_ai[online_chat_images]" checked={@settings_editor.ai["online_chat_images"]} /> <%= dgettext("ui", "Enable image analysis for the online image analysis model") %></label></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Offline Endpoint URL") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Endpoint URL") %></label></div>
<div class="setting-control"> <div class="setting-control">
<div class="setting-input-group"> <div class="setting-input-group">
<input type="url" name="settings_ai[offline_url]" value={@settings_editor.ai["offline_url"]} /> <input type="url" name="settings_ai[offline_url]" value={@settings_editor.ai["offline_url"]} />
<button class="secondary" type="button" phx-click="refresh_settings_ai_models" phx-target={@myself} phx-value-endpoint="airplane"><%= translated("Refresh Offline Models") %></button> <button class="secondary" type="button" phx-click="refresh_settings_ai_models" phx-target={@myself} phx-value-endpoint="airplane"><%= dgettext("ui", "Refresh Offline Models") %></button>
</div> </div>
</div> </div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Offline API Key") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline API Key") %></label></div>
<div class="setting-control"><input type="password" name="settings_ai[offline_api_key]" value={@settings_editor.ai["offline_api_key"]} /></div> <div class="setting-control"><input type="password" name="settings_ai[offline_api_key]" value={@settings_editor.ai["offline_api_key"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Airplane Mode") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Airplane Mode") %></label></div>
<div class="setting-control"><label><input type="checkbox" name="settings_ai[offline_mode]" checked={@settings_editor.ai["offline_mode"]} /> <%= translated("Route AI tasks through the airplane endpoint") %></label></div> <div class="setting-control"><label><input type="checkbox" name="settings_ai[offline_mode]" checked={@settings_editor.ai["offline_mode"]} /> <%= dgettext("ui", "Route AI tasks through the airplane endpoint") %></label></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Offline Chat Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Chat Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-offline-models" name="settings_ai[offline_chat_model]" value={@settings_editor.ai["offline_chat_model"]} /></div> <div class="setting-control"><input type="text" list="settings-ai-offline-models" name="settings_ai[offline_chat_model]" value={@settings_editor.ai["offline_chat_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Offline Chat Tools") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Chat Tools") %></label></div>
<div class="setting-control"><label><input type="checkbox" name="settings_ai[offline_chat_tools]" checked={@settings_editor.ai["offline_chat_tools"]} /> <%= translated("Enable tool calls for the offline chat model") %></label></div> <div class="setting-control"><label><input type="checkbox" name="settings_ai[offline_chat_tools]" checked={@settings_editor.ai["offline_chat_tools"]} /> <%= dgettext("ui", "Enable tool calls for the offline chat model") %></label></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Offline Chat Reasoning") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Chat Reasoning") %></label></div>
<div class="setting-control"><label><input type="checkbox" name="settings_ai[offline_chat_disable_reasoning]" checked={@settings_editor.ai["offline_chat_disable_reasoning"]} /> <%= translated("Disable reasoning output for the offline chat model") %></label></div> <div class="setting-control"><label><input type="checkbox" name="settings_ai[offline_chat_disable_reasoning]" checked={@settings_editor.ai["offline_chat_disable_reasoning"]} /> <%= dgettext("ui", "Disable reasoning output for the offline chat model") %></label></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Offline Title Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Title Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-offline-models" name="settings_ai[offline_title_model]" value={@settings_editor.ai["offline_title_model"]} /></div> <div class="setting-control"><input type="text" list="settings-ai-offline-models" name="settings_ai[offline_title_model]" value={@settings_editor.ai["offline_title_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Offline Image Analysis Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Image Analysis Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-offline-models" name="settings_ai[offline_image_analysis_model]" value={@settings_editor.ai["offline_image_analysis_model"]} /></div> <div class="setting-control"><input type="text" list="settings-ai-offline-models" name="settings_ai[offline_image_analysis_model]" value={@settings_editor.ai["offline_image_analysis_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Offline Image Support") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Image Support") %></label></div>
<div class="setting-control"><label><input type="checkbox" name="settings_ai[offline_chat_images]" checked={@settings_editor.ai["offline_chat_images"]} /> <%= translated("Enable image analysis for the offline image analysis model") %></label></div> <div class="setting-control"><label><input type="checkbox" name="settings_ai[offline_chat_images]" checked={@settings_editor.ai["offline_chat_images"]} /> <%= dgettext("ui", "Enable image analysis for the offline image analysis model") %></label></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("System Prompt") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "System Prompt") %></label></div>
<div class="setting-control"><textarea name="settings_ai[system_prompt]" rows="12"><%= @settings_editor.ai["system_prompt"] %></textarea></div> <div class="setting-control"><textarea name="settings_ai[system_prompt]" rows="12"><%= @settings_editor.ai["system_prompt"] %></textarea></div>
</div> </div>
<datalist id="settings-ai-online-models"> <datalist id="settings-ai-online-models">
@@ -305,53 +305,53 @@
<% end %> <% end %>
</datalist> </datalist>
</form> </form>
<div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_ai" phx-target={@myself}><%= translated("Save") %></button><button class="secondary" type="button" phx-click="reset_settings_ai_prompt" phx-target={@myself}><%= translated("Reset to Default") %></button></div> <div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_ai" phx-target={@myself}><%= dgettext("ui", "Save") %></button><button class="secondary" type="button" phx-click="reset_settings_ai_prompt" phx-target={@myself}><%= dgettext("ui", "Reset to Default") %></button></div>
</div> </div>
<% end %> <% end %>
<%= if @settings_editor.technology_visible? do %> <%= if @settings_editor.technology_visible? do %>
<div class="setting-section" id="settings-section-technology"> <div class="setting-section" id="settings-section-technology">
<div class="setting-section-header"><h3><%= translated("Technology") %></h3><p class="setting-section-description"><%= translated("Application-level runtime behavior and semantic indexing") %></p></div> <div class="setting-section-header"><h3><%= dgettext("ui", "Technology") %></h3><p class="setting-section-description"><%= dgettext("ui", "Application-level runtime behavior and semantic indexing") %></p></div>
<form class="setting-section-content" phx-change="change_settings_project" phx-target={@myself}> <form class="setting-section-content" phx-change="change_settings_project" phx-target={@myself}>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Semantic Similarity") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Semantic Similarity") %></label></div>
<div class="setting-control"><label><input type="checkbox" name="settings_project[semantic_similarity_enabled]" checked={@settings_editor.technology["semantic_similarity_enabled"]} /> <%= translated("Enable duplicate search and related-post embeddings") %></label></div> <div class="setting-control"><label><input type="checkbox" name="settings_project[semantic_similarity_enabled]" checked={@settings_editor.technology["semantic_similarity_enabled"]} /> <%= dgettext("ui", "Enable duplicate search and related-post embeddings") %></label></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("Scripting Runtime") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Scripting Runtime") %></label></div>
<div class="setting-control"><p class="setting-description"><%= translated("Scripting capabilities are configured at the application layer in the rewrite and do not expose runtime switching here.") %></p></div> <div class="setting-control"><p class="setting-description"><%= dgettext("ui", "Scripting capabilities are configured at the application layer in the rewrite and do not expose runtime switching here.") %></p></div>
</div> </div>
</form> </form>
<div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_project" phx-target={@myself}><%= translated("Save") %></button></div> <div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_project" phx-target={@myself}><%= dgettext("ui", "Save") %></button></div>
</div> </div>
<% end %> <% end %>
<%= if @settings_editor.publishing_visible? do %> <%= if @settings_editor.publishing_visible? do %>
<div class="setting-section" id="settings-section-publishing"> <div class="setting-section" id="settings-section-publishing">
<div class="setting-section-header"><h3><%= translated("Publishing") %></h3><p class="setting-section-description"><%= translated("Deployment credentials for upload tasks") %></p></div> <div class="setting-section-header"><h3><%= dgettext("ui", "Publishing") %></h3><p class="setting-section-description"><%= dgettext("ui", "Deployment credentials for upload tasks") %></p></div>
<form class="setting-section-content" phx-change="change_settings_publishing" phx-target={@myself}> <form class="setting-section-content" phx-change="change_settings_publishing" phx-target={@myself}>
<div class="setting-row"><div class="setting-info"><label class="setting-label"><%= translated("SSH Mode") %></label></div><div class="setting-control"><select name="settings_publishing[ssh_mode]"><option value="scp" selected={@settings_editor.publishing["ssh_mode"] == "scp"}>scp</option><option value="rsync" selected={@settings_editor.publishing["ssh_mode"] == "rsync"}>rsync</option></select></div></div> <div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "SSH Mode") %></label></div><div class="setting-control"><select name="settings_publishing[ssh_mode]"><option value="scp" selected={@settings_editor.publishing["ssh_mode"] == "scp"}>scp</option><option value="rsync" selected={@settings_editor.publishing["ssh_mode"] == "rsync"}>rsync</option></select></div></div>
<div class="setting-row"><div class="setting-info"><label class="setting-label"><%= translated("Host") %></label></div><div class="setting-control"><input type="text" name="settings_publishing[ssh_host]" value={@settings_editor.publishing["ssh_host"]} /></div></div> <div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Host") %></label></div><div class="setting-control"><input type="text" name="settings_publishing[ssh_host]" value={@settings_editor.publishing["ssh_host"]} /></div></div>
<div class="setting-row"><div class="setting-info"><label class="setting-label"><%= translated("Username") %></label></div><div class="setting-control"><input type="text" name="settings_publishing[ssh_user]" value={@settings_editor.publishing["ssh_user"]} /></div></div> <div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Username") %></label></div><div class="setting-control"><input type="text" name="settings_publishing[ssh_user]" value={@settings_editor.publishing["ssh_user"]} /></div></div>
<div class="setting-row"><div class="setting-info"><label class="setting-label"><%= translated("Remote Path") %></label></div><div class="setting-control"><input type="text" name="settings_publishing[ssh_remote_path]" value={@settings_editor.publishing["ssh_remote_path"]} /></div></div> <div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Remote Path") %></label></div><div class="setting-control"><input type="text" name="settings_publishing[ssh_remote_path]" value={@settings_editor.publishing["ssh_remote_path"]} /></div></div>
</form> </form>
<div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_publishing" phx-target={@myself}><%= translated("Save") %></button><button class="secondary" type="button" phx-click="clear_settings_publishing" phx-target={@myself}><%= translated("Clear") %></button></div> <div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_publishing" phx-target={@myself}><%= dgettext("ui", "Save") %></button><button class="secondary" type="button" phx-click="clear_settings_publishing" phx-target={@myself}><%= dgettext("ui", "Clear") %></button></div>
</div> </div>
<% end %> <% end %>
<%= if @settings_editor.mcp_visible? do %> <%= if @settings_editor.mcp_visible? do %>
<div class="setting-section" id="settings-section-mcp"> <div class="setting-section" id="settings-section-mcp">
<div class="setting-section-header"><h3><%= translated("MCP") %></h3><p class="setting-section-description"><%= translated("Agent configuration files for the built-in bDS MCP server") %></p></div> <div class="setting-section-header"><h3><%= dgettext("ui", "MCP") %></h3><p class="setting-section-description"><%= dgettext("ui", "Agent configuration files for the built-in bDS MCP server") %></p></div>
<div class="setting-section-content"> <div class="setting-section-content">
<%= for agent <- @settings_editor.mcp do %> <%= for agent <- @settings_editor.mcp do %>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
<label class="setting-label"><%= agent.label %></label> <label class="setting-label"><%= agent.label %></label>
<p class="setting-description"><%= agent.config_path || translated("Not supported in the rewrite yet") %></p> <p class="setting-description"><%= agent.config_path || dgettext("ui", "Not supported in the rewrite yet") %></p>
</div> </div>
<div class="setting-control"> <div class="setting-control">
<button class="secondary" type="button" phx-click="toggle_settings_mcp_agent" phx-target={@myself} phx-value-agent={agent.id} disabled={not agent.supported?}> <button class="secondary" type="button" phx-click="toggle_settings_mcp_agent" phx-target={@myself} phx-value-agent={agent.id} disabled={not agent.supported?}>
<%= if agent.configured?, do: translated("Remove"), else: translated("Add") %> <%= if agent.configured?, do: dgettext("ui", "Remove"), else: dgettext("ui", "Add") %>
</button> </button>
</div> </div>
</div> </div>
@@ -362,16 +362,16 @@
<%= if @settings_editor.data_visible? do %> <%= if @settings_editor.data_visible? do %>
<div class="setting-section" id="settings-section-data"> <div class="setting-section" id="settings-section-data">
<div class="setting-section-header"><h3><%= translated("Data Maintenance") %></h3><p class="setting-section-description"><%= translated("Rebuild filesystem-backed records and thumbnails") %></p></div> <div class="setting-section-header"><h3><%= dgettext("ui", "Data Maintenance") %></h3><p class="setting-section-description"><%= dgettext("ui", "Rebuild filesystem-backed records and thumbnails") %></p></div>
<div class="setting-actions"> <div class="setting-actions">
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_posts_from_files"><%= translated("Rebuild Posts From Files") %></button> <button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_posts_from_files"><%= dgettext("ui", "Rebuild Posts From Files") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_media_from_files"><%= translated("Rebuild Media From Files") %></button> <button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_media_from_files"><%= dgettext("ui", "Rebuild Media From Files") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_scripts_from_files"><%= translated("Rebuild Scripts From Files") %></button> <button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_scripts_from_files"><%= dgettext("ui", "Rebuild Scripts From Files") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_templates_from_files"><%= translated("Rebuild Templates From Files") %></button> <button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_templates_from_files"><%= dgettext("ui", "Rebuild Templates From Files") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_post_links"><%= translated("Rebuild Links") %></button> <button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_post_links"><%= dgettext("ui", "Rebuild Links") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="regenerate_missing_thumbnails"><%= translated("Regenerate Missing Thumbnails") %></button> <button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="regenerate_missing_thumbnails"><%= dgettext("ui", "Regenerate Missing Thumbnails") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_embedding_index"><%= translated("Rebuild Embedding Index") %></button> <button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_embedding_index"><%= dgettext("ui", "Rebuild Embedding Index") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="open_data_folder"><%= translated("Open Data Folder") %></button> <button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="open_data_folder"><%= dgettext("ui", "Open Data Folder") %></button>
</div> </div>
</div> </div>
<% end %> <% end %>

View File

@@ -1,10 +1,10 @@
<div class="style-view" data-testid="style-editor"> <div class="style-view" data-testid="style-editor">
<div class="style-view-header"> <div class="style-view-header">
<h2 data-testid="editor-title"><%= translated("Style") %></h2> <h2 data-testid="editor-title"><%= dgettext("ui", "Style") %></h2>
<p><%= translated("Theme preview and renderer theme selection") %></p> <p><%= dgettext("ui", "Theme preview and renderer theme selection") %></p>
</div> </div>
<div class="style-theme-picker" role="group" aria-label={translated("Theme picker")}> <div class="style-theme-picker" role="group" aria-label={dgettext("ui", "Theme picker")}>
<%= for theme <- @style_editor.themes do %> <%= for theme <- @style_editor.themes do %>
<button type="button" class={["style-theme-option", if(theme.name == @style_editor.selected_theme, do: "selected")]} phx-click="select_style_theme" phx-target={@myself} phx-value-theme={theme.name} aria-pressed={theme.name == @style_editor.selected_theme}> <button type="button" class={["style-theme-option", if(theme.name == @style_editor.selected_theme, do: "selected")]} phx-click="select_style_theme" phx-target={@myself} phx-value-theme={theme.name} aria-pressed={theme.name == @style_editor.selected_theme}>
<span class="style-theme-swatch"> <span class="style-theme-swatch">
@@ -21,17 +21,17 @@
<div class="style-apply-row"> <div class="style-apply-row">
<label class="style-preview-mode-control"> <label class="style-preview-mode-control">
<span><%= translated("Preview Mode") %></span> <span><%= dgettext("ui", "Preview Mode") %></span>
<select phx-change="change_style_preview_mode" phx-target={@myself} name="mode"> <select phx-change="change_style_preview_mode" phx-target={@myself} name="mode">
<option value="auto" selected={@style_editor.preview_mode == "auto"}><%= translated("Auto") %></option> <option value="auto" selected={@style_editor.preview_mode == "auto"}><%= dgettext("ui", "Auto") %></option>
<option value="light" selected={@style_editor.preview_mode == "light"}><%= translated("Light") %></option> <option value="light" selected={@style_editor.preview_mode == "light"}><%= dgettext("ui", "Light") %></option>
<option value="dark" selected={@style_editor.preview_mode == "dark"}><%= translated("Dark") %></option> <option value="dark" selected={@style_editor.preview_mode == "dark"}><%= dgettext("ui", "Dark") %></option>
</select> </select>
</label> </label>
<button class="primary" type="button" phx-click="apply_style_theme" phx-target={@myself} disabled={@style_editor.selected_theme == @style_editor.applied_theme}><%= translated("Apply Theme") %></button> <button class="primary" type="button" phx-click="apply_style_theme" phx-target={@myself} disabled={@style_editor.selected_theme == @style_editor.applied_theme}><%= dgettext("ui", "Apply Theme") %></button>
</div> </div>
<div class="style-preview-container"> <div class="style-preview-container">
<iframe title={translated("Theme Preview")} class="style-preview-frame" src={@style_editor.preview_url}></iframe> <iframe title={dgettext("ui", "Theme Preview")} class="style-preview-frame" src={@style_editor.preview_url}></iframe>
</div> </div>
</div> </div>

View File

@@ -6,6 +6,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
alias BDS.Desktop.ShellData alias BDS.Desktop.ShellData
alias BDS.Desktop.UILocale alias BDS.Desktop.UILocale
alias BDS.UI.Registry alias BDS.UI.Registry
use Gettext, backend: BDS.Gettext
def sidebar_content(assigns) do def sidebar_content(assigns) do
UILocale.put(assigns.page_language) UILocale.put(assigns.page_language)
@@ -60,9 +61,9 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
type="text" type="text"
name="sidebar_filters[search]" name="sidebar_filters[search]"
value={Map.get(@selected_filters, :search) || ""} value={Map.get(@selected_filters, :search) || ""}
placeholder={translated(@sidebar_filters_config.search_placeholder)} placeholder={@sidebar_filters_config.search_placeholder}
/> />
<button type="submit" title={translated("sidebar.search")}> <button type="submit" title={dgettext("ui", "Search")}>
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"> <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
<path d="M15.7 14.3l-4.2-4.2c-.2-.2-.5-.3-.8-.3.9-1.1 1.5-2.5 1.5-4C12.2 2.6 9.6 0 6.4 0S.6 2.6.6 5.8s2.6 5.8 5.8 5.8c1.5 0 2.9-.5 4-1.4 0 .3.1.6.3.8l4.2 4.2c.2.2.5.3.7.3s.5-.1.7-.3c.4-.4.4-1 0-1.4zm-9.3-4c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5 4.5 2 4.5 4.5-2 4.5-4.5 4.5z"/> <path d="M15.7 14.3l-4.2-4.2c-.2-.2-.5-.3-.8-.3.9-1.1 1.5-2.5 1.5-4C12.2 2.6 9.6 0 6.4 0S.6 2.6.6 5.8s2.6 5.8 5.8 5.8c1.5 0 2.9-.5 4-1.4 0 .3.1.6.3.8l4.2 4.2c.2.2.5.3.7.3s.5-.1.7-.3c.4-.4.4-1 0-1.4zm-9.3-4c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5 4.5 2 4.5 4.5-2 4.5-4.5 4.5z"/>
</svg> </svg>
@@ -75,10 +76,10 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
<%= if Map.get(@sidebar_filters_config, :has_active_filters) do %> <%= if Map.get(@sidebar_filters_config, :has_active_filters) do %>
<div class="filter-status"> <div class="filter-status">
<span> <span>
<%= translated(@sidebar_filters_config.results_label) %>: <%= @sidebar_filters_config.loaded_count %>/<%= @sidebar_filters_config.total_count %> <%= @sidebar_filters_config.results_label %>: <%= @sidebar_filters_config.loaded_count %>/<%= @sidebar_filters_config.total_count %>
</span> </span>
<button data-testid="sidebar-clear-filters" type="button" phx-click="clear_sidebar_filters"> <button data-testid="sidebar-clear-filters" type="button" phx-click="clear_sidebar_filters">
<%= translated(@sidebar_filters_config.clear_filters_label) %> <%= @sidebar_filters_config.clear_filters_label %>
</button> </button>
</div> </div>
<% end %> <% end %>
@@ -96,7 +97,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
phx-click="toggle_sidebar_archive" phx-click="toggle_sidebar_archive"
> >
<span class="collapse-icon"><%= if @archive_collapsed, do: "▶", else: "▼" %></span> <span class="collapse-icon"><%= if @archive_collapsed, do: "▶", else: "▼" %></span>
<span><%= translated(@sidebar_filters_config.archive_label) %></span> <span><%= @sidebar_filters_config.archive_label %></span>
<%= if Map.get(@selected_filters, :year) do %> <%= if Map.get(@selected_filters, :year) do %>
<button class="clear-filter" type="button" phx-click="clear_sidebar_month" phx-stop-propagation>✕</button> <button class="clear-filter" type="button" phx-click="clear_sidebar_month" phx-stop-propagation>✕</button>
<% end %> <% end %>
@@ -157,9 +158,9 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
phx-click="toggle_sidebar_tags" phx-click="toggle_sidebar_tags"
> >
<span class="collapse-icon"><%= if @tags_collapsed, do: "▶", else: "▼" %></span> <span class="collapse-icon"><%= if @tags_collapsed, do: "▶", else: "▼" %></span>
<span><%= translated(@sidebar_filters_config.tags_label) %></span> <span><%= @sidebar_filters_config.tags_label %></span>
<%= if Enum.any?(Map.get(@selected_filters, :tags, [])) do %> <%= if Enum.any?(Map.get(@selected_filters, :tags, [])) do %>
<button class="clear-filter" type="button" phx-click="clear_sidebar_tags" phx-stop-propagation title={translated(@sidebar_filters_config.clear_tags_label)}>✕</button> <button class="clear-filter" type="button" phx-click="clear_sidebar_tags" phx-stop-propagation title={@sidebar_filters_config.clear_tags_label}>✕</button>
<% end %> <% end %>
</div> </div>
<%= unless @tags_collapsed do %> <%= unless @tags_collapsed do %>
@@ -198,9 +199,9 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
phx-click="toggle_sidebar_categories" phx-click="toggle_sidebar_categories"
> >
<span class="collapse-icon"><%= if @categories_collapsed, do: "▶", else: "▼" %></span> <span class="collapse-icon"><%= if @categories_collapsed, do: "▶", else: "▼" %></span>
<span><%= translated(@sidebar_filters_config.categories_label) %></span> <span><%= @sidebar_filters_config.categories_label %></span>
<%= if Enum.any?(Map.get(@selected_filters, :categories, [])) do %> <%= if Enum.any?(Map.get(@selected_filters, :categories, [])) do %>
<button class="clear-filter" type="button" phx-click="clear_sidebar_categories" phx-stop-propagation title={translated(@sidebar_filters_config.clear_categories_label)}>✕</button> <button class="clear-filter" type="button" phx-click="clear_sidebar_categories" phx-stop-propagation title={@sidebar_filters_config.clear_categories_label}>✕</button>
<% end %> <% end %>
</div> </div>
<%= unless @categories_collapsed do %> <%= unless @categories_collapsed do %>
@@ -240,7 +241,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
~H""" ~H"""
<div class="sidebar-load-more"> <div class="sidebar-load-more">
<button class="load-more-button" data-testid="sidebar-load-more" type="button" phx-click="load_more_sidebar"> <button class="load-more-button" data-testid="sidebar-load-more" type="button" phx-click="load_more_sidebar">
<%= translated("Load more") %> <%= dgettext("ui", "Load more") %>
</button> </button>
</div> </div>
""" """
@@ -266,7 +267,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
<section class="sidebar-section"> <section class="sidebar-section">
<div class="sidebar-section-title"> <div class="sidebar-section-title">
<span class={"section-icon status-#{Map.get(section, :status, "draft")}"}>●</span> <span class={"section-icon status-#{Map.get(section, :status, "draft")}"}>●</span>
<span data-testid="sidebar-section-title"><%= translated(section.title) %></span> <span data-testid="sidebar-section-title"><%= section.title %></span>
<span class="sidebar-section-count"><%= Map.get(section, :count, length(Map.get(section, :items, []))) %></span> <span class="sidebar-section-count"><%= Map.get(section, :count, length(Map.get(section, :items, []))) %></span>
</div> </div>
<div class="sidebar-list"> <div class="sidebar-list">
@@ -316,7 +317,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
<% end %> <% end %>
<%= if Enum.empty?(Map.get(@sidebar_data, :sections, [])) do %> <%= if Enum.empty?(Map.get(@sidebar_data, :sections, [])) do %>
<div class="sidebar-empty"> <div class="sidebar-empty">
<p><%= translated(Map.get(@sidebar_data, :empty_message, "No items")) %></p> <p><%= Map.get(@sidebar_data, :empty_message, dgettext("ui", "No items")) %></p>
</div> </div>
<% end %> <% end %>
""" """
@@ -376,7 +377,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
</div> </div>
<% else %> <% else %>
<div class="sidebar-empty"> <div class="sidebar-empty">
<p><%= translated(Map.get(@sidebar_data, :empty_message, "No items")) %></p> <p><%= Map.get(@sidebar_data, :empty_message, dgettext("ui", "No items")) %></p>
</div> </div>
<% end %> <% end %>
""" """
@@ -398,17 +399,17 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
data-route={item.route} data-route={item.route}
data-item-id={item.id} data-item-id={item.id}
data-open-title={item.title} data-open-title={item.title}
data-open-subtitle={translated(item.meta || "")} data-open-subtitle={item.meta || ""}
type="button" type="button"
phx-click="open_sidebar_item" phx-click="open_sidebar_item"
phx-value-route={item.route} phx-value-route={item.route}
phx-value-id={item.id} phx-value-id={item.id}
phx-value-title={item.title} phx-value-title={item.title}
phx-value-subtitle={translated(item.meta || "")} phx-value-subtitle={item.meta || ""}
> >
<span class="chat-item-content"> <span class="chat-item-content">
<span class="chat-item-title"><%= item.title %></span> <span class="chat-item-title"><%= item.title %></span>
<span class="chat-item-date"><%= translated(item.meta || "") %></span> <span class="chat-item-date"><%= item.meta || "" %></span>
</span> </span>
</button> </button>
<button <button
@@ -432,17 +433,17 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
data-route={item.route} data-route={item.route}
data-item-id={item.id} data-item-id={item.id}
data-open-title={item.title} data-open-title={item.title}
data-open-subtitle={translated(item.meta || "")} data-open-subtitle={item.meta || ""}
type="button" type="button"
phx-click="open_sidebar_item" phx-click="open_sidebar_item"
phx-value-route={item.route} phx-value-route={item.route}
phx-value-id={item.id} phx-value-id={item.id}
phx-value-title={item.title} phx-value-title={item.title}
phx-value-subtitle={translated(item.meta || "")} phx-value-subtitle={item.meta || ""}
> >
<span class="chat-item-content"> <span class="chat-item-content">
<span class="chat-item-title"><%= item.title %></span> <span class="chat-item-title"><%= item.title %></span>
<span class="chat-item-date"><%= translated(item.meta || "") %></span> <span class="chat-item-date"><%= item.meta || "" %></span>
</span> </span>
</button> </button>
<% end %> <% end %>
@@ -450,7 +451,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
</div> </div>
<% else %> <% else %>
<div class="sidebar-empty"> <div class="sidebar-empty">
<p><%= translated(Map.get(@sidebar_data, :empty_message, "No items")) %></p> <p><%= Map.get(@sidebar_data, :empty_message, dgettext("ui", "No items")) %></p>
</div> </div>
<% end %> <% end %>
""" """
@@ -465,17 +466,17 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
data-testid="sidebar-open-item" data-testid="sidebar-open-item"
data-route={item.route} data-route={item.route}
data-item-id={item.id} data-item-id={item.id}
data-open-title={translated(item.title)} data-open-title={item.title}
data-open-subtitle={translated(Map.get(@sidebar_data, :subtitle, ""))} data-open-subtitle={Map.get(@sidebar_data, :subtitle, "")}
type="button" type="button"
phx-click="open_sidebar_item" phx-click="open_sidebar_item"
phx-value-route={item.route} phx-value-route={item.route}
phx-value-id={item.id} phx-value-id={item.id}
phx-value-title={translated(item.title)} phx-value-title={item.title}
phx-value-subtitle={translated(Map.get(@sidebar_data, :subtitle, ""))} phx-value-subtitle={Map.get(@sidebar_data, :subtitle, "")}
> >
<span class="settings-nav-entry-icon"><%= Map.get(item, :icon, "") %></span> <span class="settings-nav-entry-icon"><%= Map.get(item, :icon, "") %></span>
<span><%= translated(item.title) %></span> <span><%= item.title %></span>
</button> </button>
<% end %> <% end %>
</div> </div>
@@ -487,7 +488,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
<%= for section <- Map.get(@sidebar_data, :sections, []) do %> <%= for section <- Map.get(@sidebar_data, :sections, []) do %>
<section class="sidebar-section"> <section class="sidebar-section">
<div class="sidebar-section-header"> <div class="sidebar-section-header">
<span data-testid="sidebar-section-title"><%= translated(section.title) %></span> <span data-testid="sidebar-section-title"><%= section.title %></span>
</div> </div>
<div class="sidebar-section-items"> <div class="sidebar-section-items">
<%= for item <- Map.get(section, :items, []) do %> <%= for item <- Map.get(section, :items, []) do %>
@@ -499,8 +500,6 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
""" """
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
defp sidebar_deletable?(route), do: route in ["post", "media", "scripts", "templates", "chat", "import"] defp sidebar_deletable?(route), do: route in ["post", "media", "scripts", "templates", "chat", "import"]
@@ -512,13 +511,13 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
defp sidebar_delete_testid("import"), do: "sidebar-delete-import" defp sidebar_delete_testid("import"), do: "sidebar-delete-import"
defp sidebar_delete_testid(route), do: "sidebar-delete-#{route}" defp sidebar_delete_testid(route), do: "sidebar-delete-#{route}"
defp sidebar_delete_title("chat"), do: translated("sidebar.chat.deleteConversation") defp sidebar_delete_title("chat"), do: dgettext("ui", "Delete conversation")
defp sidebar_delete_title("post"), do: translated("Delete") <> " " <> translated("Post") defp sidebar_delete_title("post"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Post")
defp sidebar_delete_title("media"), do: translated("Delete") <> " " <> translated("Media") defp sidebar_delete_title("media"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Media")
defp sidebar_delete_title("scripts"), do: translated("Delete") <> " " <> translated("Script") defp sidebar_delete_title("scripts"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Script")
defp sidebar_delete_title("templates"), do: translated("Delete") <> " " <> translated("Template") defp sidebar_delete_title("templates"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Template")
defp sidebar_delete_title("import"), do: translated("Delete") <> " " <> translated("Import") defp sidebar_delete_title("import"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Import")
defp sidebar_delete_title(_route), do: translated("Delete") defp sidebar_delete_title(_route), do: dgettext("ui", "Delete")
defp template_sidebar?(sidebar_data), do: Map.get(sidebar_data, :title) == "Templates" defp template_sidebar?(sidebar_data), do: Map.get(sidebar_data, :title) == "Templates"

View File

@@ -1,11 +1,12 @@
defmodule BDS.Desktop.ShellLive.SidebarCreate do defmodule BDS.Desktop.ShellLive.SidebarCreate do
@moduledoc false @moduledoc false
alias BDS.Desktop.{FilePicker, ShellData} alias BDS.Desktop.{FilePicker}
alias BDS.AI alias BDS.AI
alias BDS.ImportDefinitions alias BDS.ImportDefinitions
alias BDS.Scripts alias BDS.Scripts
alias BDS.Templates alias BDS.Templates
use Gettext, backend: BDS.Gettext
@doc """ @doc """
Create a new sidebar item of the given kind for the active project. Create a new sidebar item of the given kind for the active project.
@@ -35,13 +36,13 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
{:error, reason} -> {:error, reason} ->
socket socket
|> callbacks.append_output.(translated("sidebar.newPost"), inspect(reason), nil, "error") |> callbacks.append_output.(dgettext("ui", "New Post"), inspect(reason), nil, "error")
|> callbacks.reload.(socket.assigns.workbench) |> callbacks.reload.(socket.assigns.workbench)
end end
end end
def create(socket, project_id, "media", callbacks) do def create(socket, project_id, "media", callbacks) do
case FilePicker.choose_file(translated("sidebar.importMedia")) do case FilePicker.choose_file(dgettext("ui", "Import media")) do
{:ok, source_path} -> {:ok, source_path} ->
case BDS.Media.import_media(%{project_id: project_id, source_path: source_path}) do case BDS.Media.import_media(%{project_id: project_id, source_path: source_path}) do
{:ok, _media} -> {:ok, _media} ->
@@ -50,7 +51,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
{:error, reason} -> {:error, reason} ->
socket socket
|> callbacks.append_output.( |> callbacks.append_output.(
translated("sidebar.importMedia"), dgettext("ui", "Import media"),
inspect(reason), inspect(reason),
nil, nil,
"error" "error"
@@ -63,7 +64,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
{:error, %{message: message}} -> {:error, %{message: message}} ->
socket socket
|> callbacks.append_output.(translated("sidebar.importMedia"), message, nil, "error") |> callbacks.append_output.(dgettext("ui", "Import media"), message, nil, "error")
|> callbacks.reload.(socket.assigns.workbench) |> callbacks.reload.(socket.assigns.workbench)
end end
end end
@@ -71,7 +72,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
def create(socket, project_id, "script", callbacks) do def create(socket, project_id, "script", callbacks) do
case Scripts.create_script(%{ case Scripts.create_script(%{
project_id: project_id, project_id: project_id,
title: translated("sidebar.scripts.newScript"), title: dgettext("ui", "New Script"),
kind: :utility, kind: :utility,
content: "print(\"new script\")", content: "print(\"new script\")",
entrypoint: "main", entrypoint: "main",
@@ -92,7 +93,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
{:error, reason} -> {:error, reason} ->
socket socket
|> callbacks.append_output.( |> callbacks.append_output.(
translated("sidebar.scripts.newScript"), dgettext("ui", "New Script"),
inspect(reason), inspect(reason),
nil, nil,
"error" "error"
@@ -104,7 +105,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
def create(socket, project_id, "template", callbacks) do def create(socket, project_id, "template", callbacks) do
case Templates.create_template(%{ case Templates.create_template(%{
project_id: project_id, project_id: project_id,
title: translated("sidebar.templates.newTemplate"), title: dgettext("ui", "New Template"),
kind: :post, kind: :post,
content: "", content: "",
enabled: true enabled: true
@@ -124,7 +125,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
{:error, reason} -> {:error, reason} ->
socket socket
|> callbacks.append_output.( |> callbacks.append_output.(
translated("sidebar.templates.newTemplate"), dgettext("ui", "New Template"),
inspect(reason), inspect(reason),
nil, nil,
"error" "error"
@@ -149,7 +150,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
{:error, reason} -> {:error, reason} ->
socket socket
|> callbacks.append_output.(translated("chat.newChat"), inspect(reason), nil, "error") |> callbacks.append_output.(dgettext("ui", "New Chat"), inspect(reason), nil, "error")
|> callbacks.reload.(socket.assigns.workbench) |> callbacks.reload.(socket.assigns.workbench)
end end
end end
@@ -157,7 +158,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
def create(socket, project_id, "import", callbacks) do def create(socket, project_id, "import", callbacks) do
case ImportDefinitions.create_definition(%{ case ImportDefinitions.create_definition(%{
project_id: project_id, project_id: project_id,
name: translated("sidebar.import.newDefinition") name: dgettext("ui", "New Import Definition")
}) do }) do
{:ok, definition} -> {:ok, definition} ->
callbacks.open_sidebar.( callbacks.open_sidebar.(
@@ -174,7 +175,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
{:error, reason} -> {:error, reason} ->
socket socket
|> callbacks.append_output.( |> callbacks.append_output.(
translated("sidebar.import.newDefinition"), dgettext("ui", "New Import Definition"),
inspect(reason), inspect(reason),
nil, nil,
"error" "error"
@@ -193,6 +194,4 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
def action(:chat), do: %{kind: "chat", label: "chat.newChat"} def action(:chat), do: %{kind: "chat", label: "chat.newChat"}
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, %{}, BDS.Desktop.UILocale.current())
end end

View File

@@ -4,8 +4,7 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
import Phoenix.Component, only: [assign: 3] import Phoenix.Component, only: [assign: 3]
alias BDS.{AI, ImportDefinitions, Media, Posts, Scripts, Templates} alias BDS.{AI, ImportDefinitions, Media, Posts, Scripts, Templates}
alias BDS.Desktop.ShellData use Gettext, backend: BDS.Gettext
alias BDS.Desktop.UILocale
@spec request_delete(Phoenix.LiveView.Socket.t(), String.t(), String.t(), String.t() | nil, map()) :: @spec request_delete(Phoenix.LiveView.Socket.t(), String.t(), String.t(), String.t() | nil, map()) ::
Phoenix.LiveView.Socket.t() Phoenix.LiveView.Socket.t()
@@ -57,7 +56,7 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
_other -> _other ->
socket socket
|> assign(:shell_overlay, nil) |> assign(:shell_overlay, nil)
|> callbacks.append_output.(translated("Delete"), inspect(:unsupported_route), nil, "error") |> callbacks.append_output.(dgettext("ui", "Delete"), inspect(:unsupported_route), nil, "error")
|> callbacks.reload.(socket.assigns.workbench) |> callbacks.reload.(socket.assigns.workbench)
end end
end end
@@ -152,13 +151,13 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
end end
@spec delete_title(String.t()) :: String.t() @spec delete_title(String.t()) :: String.t()
defp delete_title("chat"), do: translated("sidebar.chat.deleteConversation") defp delete_title("chat"), do: dgettext("ui", "Delete conversation")
defp delete_title("post"), do: translated("Delete") <> " " <> translated("Post") defp delete_title("post"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Post")
defp delete_title("media"), do: translated("Delete") <> " " <> translated("Media") defp delete_title("media"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Media")
defp delete_title("scripts"), do: translated("Delete") <> " " <> translated("Script") defp delete_title("scripts"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Script")
defp delete_title("templates"), do: translated("Delete") <> " " <> translated("Template") defp delete_title("templates"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Template")
defp delete_title("import"), do: translated("Delete") <> " " <> translated("Import") defp delete_title("import"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Import")
defp delete_title(_route), do: translated("Delete") defp delete_title(_route), do: dgettext("ui", "Delete")
@spec present_title(String.t() | nil) :: String.t() | nil @spec present_title(String.t() | nil) :: String.t() | nil
defp present_title(value) when is_binary(value) do defp present_title(value) when is_binary(value) do
@@ -170,7 +169,4 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
defp present_title(_value), do: nil defp present_title(_value), do: nil
@spec translated(String.t(), map()) :: String.t()
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, UILocale.current())
end end

View File

@@ -6,8 +6,9 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
alias BDS.Media.Media, as: MediaRecord alias BDS.Media.Media, as: MediaRecord
alias BDS.Posts.Post alias BDS.Posts.Post
alias BDS.UI.Registry alias BDS.UI.Registry
use Gettext, backend: BDS.Gettext
def tab_title(nil, _tab_meta), do: translated("Dashboard") def tab_title(nil, _tab_meta), do: dgettext("ui", "Dashboard")
def tab_title(tab, tab_meta) do def tab_title(tab, tab_meta) do
case Map.get(tab_meta, {tab.type, tab.id}) do case Map.get(tab_meta, {tab.type, tab.id}) do
@@ -16,7 +17,7 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
end end
end end
def tab_subtitle(nil, _tab_meta), do: translated("dashboard.subtitle") def tab_subtitle(nil, _tab_meta), do: dgettext("ui", "Overview of your blog database")
def tab_subtitle(tab, tab_meta) do def tab_subtitle(tab, tab_meta) do
case Map.get(tab_meta, {tab.type, tab.id}) do case Map.get(tab_meta, {tab.type, tab.id}) do
@@ -50,7 +51,7 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
defp default_tab_subtitle(_tab), do: "Desktop workbench content routed through the Elixir shell." defp default_tab_subtitle(_tab), do: "Desktop workbench content routed through the Elixir shell."
def tab_route_label(nil), do: translated("Dashboard") def tab_route_label(nil), do: dgettext("ui", "Dashboard")
def tab_route_label(%{type: type}), do: ShellData.route_label(type) def tab_route_label(%{type: type}), do: ShellData.route_label(type)
def tab_icon_id(nil), do: "posts" def tab_icon_id(nil), do: "posts"
@@ -135,7 +136,7 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
defp derived_tab_meta(%{type: :scripts, id: script_id}) do defp derived_tab_meta(%{type: :scripts, id: script_id}) do
case Scripts.get_script(script_id) do case Scripts.get_script(script_id) do
%{title: title, id: id} -> %{title: title, id: id} ->
%{title: blank_to_nil(title) || id, subtitle: translated("Automation helpers")} %{title: blank_to_nil(title) || id, subtitle: dgettext("ui", "Automation helpers")}
_other -> _other ->
%{} %{}
@@ -145,7 +146,7 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
defp derived_tab_meta(%{type: :templates, id: template_id}) do defp derived_tab_meta(%{type: :templates, id: template_id}) do
case Templates.get_template(template_id) do case Templates.get_template(template_id) do
%{title: title, id: id} -> %{title: title, id: id} ->
%{title: blank_to_nil(title) || id, subtitle: translated("Site rendering")} %{title: blank_to_nil(title) || id, subtitle: dgettext("ui", "Site rendering")}
_other -> _other ->
%{} %{}
@@ -155,7 +156,7 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
defp derived_tab_meta(%{type: :chat, id: conversation_id}) do defp derived_tab_meta(%{type: :chat, id: conversation_id}) do
case AI.get_chat_conversation(conversation_id) do case AI.get_chat_conversation(conversation_id) do
conversation when is_map(conversation) -> conversation when is_map(conversation) ->
%{title: chat_record_title(conversation), subtitle: translated("AI conversations")} %{title: chat_record_title(conversation), subtitle: dgettext("ui", "AI conversations")}
_other -> _other ->
%{} %{}
@@ -166,8 +167,8 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
case ImportDefinitions.get_definition(definition_id) do case ImportDefinitions.get_definition(definition_id) do
%{name: name} -> %{name: name} ->
%{ %{
title: blank_to_nil(name) || translated("importAnalysis.untitledImport"), title: blank_to_nil(name) || dgettext("ui", "Untitled Import"),
subtitle: translated("importAnalysis.headerDescription") subtitle: dgettext("ui", "Select a WordPress export file (WXR) and an uploads folder to analyze what would be imported.")
} }
_other -> _other ->
@@ -176,13 +177,13 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
end end
defp derived_tab_meta(%{type: :git_diff, id: "git-working-tree"}) do defp derived_tab_meta(%{type: :git_diff, id: "git-working-tree"}) do
%{title: translated("Working tree"), subtitle: translated("Working tree and history")} %{title: dgettext("ui", "Working tree"), subtitle: dgettext("ui", "Working tree and history")}
end end
defp derived_tab_meta(%{type: :menu_editor}) do defp derived_tab_meta(%{type: :menu_editor}) do
%{ %{
title: translated("menuEditor.tabTitle"), title: dgettext("ui", "Blog Menu"),
subtitle: translated("menuEditor.description") subtitle: dgettext("ui", "Manage the central blog navigation outline and save it to meta/menu.opml.")
} }
end end
@@ -234,6 +235,4 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
trimmed -> trimmed trimmed -> trimmed
end end
end end
defp translated(text), do: ShellData.translate(text, %{}, BDS.Desktop.UILocale.current())
end end

View File

@@ -6,11 +6,10 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
import Ecto.Query import Ecto.Query
alias BDS.{Repo, Tags} alias BDS.{Repo, Tags}
alias BDS.Desktop.ShellData
alias BDS.Desktop.UILocale
alias BDS.Posts.Post alias BDS.Posts.Post
alias BDS.Tags.Tag alias BDS.Tags.Tag
alias BDS.Templates.Template alias BDS.Templates.Template
use Gettext, backend: BDS.Gettext
embed_templates("tags_editor_html/*") embed_templates("tags_editor_html/*")
@@ -91,7 +90,7 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|> noreply() |> noreply()
{:error, reason} -> {:error, reason} ->
notify_output(translated("Tags"), inspect(reason), "error") notify_output(dgettext("ui", "Tags"), inspect(reason), "error")
{:noreply, socket} {:noreply, socket}
end end
end end
@@ -133,7 +132,7 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|> noreply() |> noreply()
{:error, reason} -> {:error, reason} ->
notify_output(translated("Tags"), inspect(reason), "error") notify_output(dgettext("ui", "Tags"), inspect(reason), "error")
{:noreply, socket} {:noreply, socket}
end end
end end
@@ -186,7 +185,7 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|> noreply() |> noreply()
{:error, reason} -> {:error, reason} ->
notify_output(translated("Tags"), inspect(reason), "error") notify_output(dgettext("ui", "Tags"), inspect(reason), "error")
{:noreply, socket} {:noreply, socket}
end end
end end
@@ -200,7 +199,7 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
{:noreply, load_data(socket)} {:noreply, load_data(socket)}
{:error, reason} -> {:error, reason} ->
notify_output(translated("Tags"), inspect(reason), "error") notify_output(dgettext("ui", "Tags"), inspect(reason), "error")
{:noreply, socket} {:noreply, socket}
end end
end end
@@ -231,7 +230,7 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|> load_data() |> load_data()
else else
{:error, reason} -> {:error, reason} ->
notify_output(translated("Tags"), inspect(reason), "error") notify_output(dgettext("ui", "Tags"), inspect(reason), "error")
socket socket
end end
end end
@@ -383,7 +382,4 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
trimmed -> trimmed trimmed -> trimmed
end end
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, UILocale.current())
end end

View File

@@ -8,17 +8,17 @@
> >
<div class="tags-view"> <div class="tags-view">
<div class="tags-view-header"> <div class="tags-view-header">
<h2><%= translated("Tags") %></h2> <h2><%= dgettext("ui", "Tags") %></h2>
</div> </div>
<div class="tags-view-content"> <div class="tags-view-content">
<div class="tags-section" id="tags-section-cloud"> <div class="tags-section" id="tags-section-cloud">
<div class="tags-section-header"><h3><%= translated("Tag Cloud") %></h3></div> <div class="tags-section-header"><h3><%= dgettext("ui", "Tag Cloud") %></h3></div>
<div class="tags-section-content"> <div class="tags-section-content">
<%= if Enum.empty?(@tags_editor.tags) do %> <%= if Enum.empty?(@tags_editor.tags) do %>
<div class="tags-empty-state"> <div class="tags-empty-state">
<p><%= translated("No tags found") %></p> <p><%= dgettext("ui", "No tags found") %></p>
<button class="secondary" type="button" phx-click="sync_tags_editor" phx-target={@myself}><%= translated("Discover") %></button> <button class="secondary" type="button" phx-click="sync_tags_editor" phx-target={@myself}><%= dgettext("ui", "Discover") %></button>
</div> </div>
<% else %> <% else %>
<div class="tag-cloud"> <div class="tag-cloud">
@@ -33,13 +33,13 @@
</div> </div>
<div class="tags-section" id="tags-section-manage"> <div class="tags-section" id="tags-section-manage">
<div class="tags-section-header"><h3><%= translated("Create / Edit") %></h3></div> <div class="tags-section-header"><h3><%= dgettext("ui", "Create / Edit") %></h3></div>
<div class="tags-section-content"> <div class="tags-section-content">
<form class="tag-create-form" phx-change="change_new_tag_editor" phx-target={@myself}> <form class="tag-create-form" phx-change="change_new_tag_editor" phx-target={@myself}>
<div class="tag-form-row"> <div class="tag-form-row">
<input type="text" name="new_tag[name]" value={@tags_editor.new_tag["name"]} placeholder={translated("Tag name")} /> <input type="text" name="new_tag[name]" value={@tags_editor.new_tag["name"]} placeholder={dgettext("ui", "Tag name")} />
<input type="color" name="new_tag[color]" value={if(@tags_editor.new_tag["color"] in [nil, ""], do: "#3b82f6", else: @tags_editor.new_tag["color"])} /> <input type="color" name="new_tag[color]" value={if(@tags_editor.new_tag["color"] in [nil, ""], do: "#3b82f6", else: @tags_editor.new_tag["color"])} />
<button class="primary" type="button" phx-click="create_tag_editor" phx-target={@myself}><%= translated("Create") %></button> <button class="primary" type="button" phx-click="create_tag_editor" phx-target={@myself}><%= dgettext("ui", "Create") %></button>
</div> </div>
</form> </form>
@@ -49,13 +49,13 @@
<input type="text" name="edit_tag[name]" value={@tags_editor.edit_draft["name"]} /> <input type="text" name="edit_tag[name]" value={@tags_editor.edit_draft["name"]} />
<input type="color" name="edit_tag[color]" value={if(@tags_editor.edit_draft["color"] in [nil, ""], do: "#3b82f6", else: @tags_editor.edit_draft["color"])} /> <input type="color" name="edit_tag[color]" value={if(@tags_editor.edit_draft["color"] in [nil, ""], do: "#3b82f6", else: @tags_editor.edit_draft["color"])} />
<select name="edit_tag[post_template_slug]"> <select name="edit_tag[post_template_slug]">
<option value=""><%= translated("No Template") %></option> <option value=""><%= dgettext("ui", "No Template") %></option>
<%= for template <- @tags_editor.templates do %> <%= for template <- @tags_editor.templates do %>
<option value={template.slug} selected={template.slug == @tags_editor.edit_draft["post_template_slug"]}><%= template.title %></option> <option value={template.slug} selected={template.slug == @tags_editor.edit_draft["post_template_slug"]}><%= template.title %></option>
<% end %> <% end %>
</select> </select>
<button class="primary" type="button" phx-click="save_tag_editor" phx-target={@myself}><%= translated("Save") %></button> <button class="primary" type="button" phx-click="save_tag_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button>
<button class="danger" type="button" phx-click="delete_tag_editor" phx-target={@myself}><%= translated("Delete") %></button> <button class="danger" type="button" phx-click="delete_tag_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
</div> </div>
</form> </form>
<% end %> <% end %>
@@ -63,7 +63,7 @@
</div> </div>
<div class="tags-section" id="tags-section-merge"> <div class="tags-section" id="tags-section-merge">
<div class="tags-section-header"><h3><%= translated("Merge Tags") %></h3></div> <div class="tags-section-header"><h3><%= dgettext("ui", "Merge Tags") %></h3></div>
<div class="tags-section-content"> <div class="tags-section-content">
<div class="merge-form"> <div class="merge-form">
<div class="tag-form-row"> <div class="tag-form-row">
@@ -72,16 +72,16 @@
<option value={tag_name} selected={tag_name == @tags_editor.merge_target}><%= tag_name %></option> <option value={tag_name} selected={tag_name == @tags_editor.merge_target}><%= tag_name %></option>
<% end %> <% end %>
</select> </select>
<button class="primary" type="button" phx-click="merge_tags_editor" disabled={length(@tags_editor.selected) < 2} phx-target={@myself}><%= translated("Merge") %></button> <button class="primary" type="button" phx-click="merge_tags_editor" disabled={length(@tags_editor.selected) < 2} phx-target={@myself}><%= dgettext("ui", "Merge") %></button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="tags-section" id="tags-section-sync"> <div class="tags-section" id="tags-section-sync">
<div class="tags-section-header"><h3><%= translated("Sync") %></h3></div> <div class="tags-section-header"><h3><%= dgettext("ui", "Sync") %></h3></div>
<div class="tags-section-content"> <div class="tags-section-content">
<button class="secondary" type="button" phx-click="sync_tags_editor" phx-target={@myself}><%= translated("Discover") %></button> <button class="secondary" type="button" phx-click="sync_tags_editor" phx-target={@myself}><%= dgettext("ui", "Discover") %></button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,6 @@
defmodule BDS.Desktop.ShellLive.TaskLocalization do defmodule BDS.Desktop.ShellLive.TaskLocalization do
@moduledoc false @moduledoc false
alias BDS.Desktop.ShellData
def localize_task_status(task_status, locale) do def localize_task_status(task_status, locale) do
tasks = Enum.map(Map.get(task_status, :tasks, []), &localize_task(&1, locale)) tasks = Enum.map(Map.get(task_status, :tasks, []), &localize_task(&1, locale))
@@ -15,13 +14,13 @@ defmodule BDS.Desktop.ShellLive.TaskLocalization do
def translate_editor_meta(items, locale) do def translate_editor_meta(items, locale) do
Enum.map(items, fn item -> Enum.map(items, fn item ->
item item
|> Map.update(:label, nil, &ShellData.translate(&1, %{}, locale)) |> Map.update(:label, nil, &BDS.Gettext.lgettext(locale, "ui", &1))
|> Map.update(:value, nil, &translate_editor_meta_value(&1, locale)) |> Map.update(:value, nil, &translate_editor_meta_value(&1, locale))
end) end)
end end
def translate_for_socket(socket, text) when is_binary(text), def translate_for_socket(socket, text) when is_binary(text),
do: ShellData.translate(text, %{}, socket.assigns.page_language) do: BDS.Gettext.lgettext(socket.assigns.page_language, "ui", text)
def translate_for_socket(_socket, text), do: text def translate_for_socket(_socket, text), do: text
@@ -42,7 +41,7 @@ defmodule BDS.Desktop.ShellLive.TaskLocalization do
progress = Map.get(task, :progress) progress = Map.get(task, :progress)
task task
|> Map.put(:name, ShellData.translate(task.name, %{}, locale)) |> Map.put(:name, BDS.Gettext.lgettext(locale, "ui", task.name))
|> Map.put(:message, localize_task_message(Map.get(task, :message), locale)) |> Map.put(:message, localize_task_message(Map.get(task, :message), locale))
|> Map.put(:group_name, localize_task_group(Map.get(task, :group_name), locale)) |> Map.put(:group_name, localize_task_group(Map.get(task, :group_name), locale))
|> Map.put(:status_label, localize_task_status_label(task.status, locale)) |> Map.put(:status_label, localize_task_status_label(task.status, locale))
@@ -54,30 +53,30 @@ defmodule BDS.Desktop.ShellLive.TaskLocalization do
defp localize_task_message(nil, _locale), do: nil defp localize_task_message(nil, _locale), do: nil
defp localize_task_message("", _locale), do: "" defp localize_task_message("", _locale), do: ""
defp localize_task_message(message, locale), do: ShellData.translate(message, %{}, locale) defp localize_task_message(message, locale), do: BDS.Gettext.lgettext(locale, "ui", message)
defp localize_task_group(nil, _locale), do: nil defp localize_task_group(nil, _locale), do: nil
defp localize_task_group(group, locale), do: ShellData.translate(group, %{}, locale) defp localize_task_group(group, locale), do: BDS.Gettext.lgettext(locale, "ui", group)
defp localize_task_status_label(status, locale) do defp localize_task_status_label(status, locale) do
status status
|> to_string() |> to_string()
|> String.capitalize() |> String.capitalize()
|> ShellData.translate(%{}, locale) |> then(&BDS.Gettext.lgettext(locale, "ui", &1))
end end
defp localized_running_task_message([], _locale), do: nil defp localized_running_task_message([], _locale), do: nil
defp localized_running_task_message([task | _rest], locale) do defp localized_running_task_message([task | _rest], locale) do
cond do cond do
task.status == :pending -> ShellData.translate("Queued", %{}, locale) <> ": " <> task.name task.status == :pending -> BDS.Gettext.lgettext(locale, "ui", "Queued") <> ": " <> task.name
is_binary(task.message) and task.message != "" -> task.name <> ": " <> task.message is_binary(task.message) and task.message != "" -> task.name <> ": " <> task.message
true -> task.name true -> task.name
end end
end end
defp translate_editor_meta_value(value, locale) when is_binary(value), defp translate_editor_meta_value(value, locale) when is_binary(value),
do: ShellData.translate(value, %{}, locale) do: BDS.Gettext.lgettext(locale, "ui", value)
defp translate_editor_meta_value(value, _locale), do: value defp translate_editor_meta_value(value, _locale), do: value
end end

View File

@@ -4,8 +4,8 @@ defmodule BDS.Desktop.ShellLive.TemplateEditor do
use Phoenix.LiveComponent use Phoenix.LiveComponent
alias BDS.{MCP, Templates} alias BDS.{MCP, Templates}
alias BDS.Desktop.ShellData
alias BDS.Templates.Template alias BDS.Templates.Template
use Gettext, backend: BDS.Gettext
embed_templates("template_editor_html/*") embed_templates("template_editor_html/*")
@@ -107,17 +107,17 @@ defmodule BDS.Desktop.ShellLive.TemplateEditor do
socket socket
|> assign(:draft, nil) |> assign(:draft, nil)
|> build_data() |> build_data()
|> notify_output(translated("Templates"), translated("Template saved")) |> notify_output(dgettext("ui", "Templates"), dgettext("ui", "Template saved"))
|> notify_reload() |> notify_reload()
else else
{:ok, %{valid: false, errors: errors}} -> {:ok, %{valid: false, errors: errors}} ->
socket socket
|> notify_output(translated("Templates"), Enum.join(errors, "; "), "error") |> notify_output(dgettext("ui", "Templates"), Enum.join(errors, "; "), "error")
|> notify_reload() |> notify_reload()
{:error, reason} -> {:error, reason} ->
socket socket
|> notify_output(translated("Templates"), inspect(reason), "error") |> notify_output(dgettext("ui", "Templates"), inspect(reason), "error")
|> notify_reload() |> notify_reload()
end end
end end
@@ -139,17 +139,17 @@ defmodule BDS.Desktop.ShellLive.TemplateEditor do
socket socket
|> assign(:draft, nil) |> assign(:draft, nil)
|> build_data() |> build_data()
|> notify_output(translated("Templates"), translated("Template published")) |> notify_output(dgettext("ui", "Templates"), dgettext("ui", "Template published"))
|> notify_reload() |> notify_reload()
else else
{:ok, %{valid: false, errors: errors}} -> {:ok, %{valid: false, errors: errors}} ->
socket socket
|> notify_output(translated("Templates"), Enum.join(errors, "; "), "error") |> notify_output(dgettext("ui", "Templates"), Enum.join(errors, "; "), "error")
|> notify_reload() |> notify_reload()
{:error, reason} -> {:error, reason} ->
socket socket
|> notify_output(translated("Templates"), inspect(reason), "error") |> notify_output(dgettext("ui", "Templates"), inspect(reason), "error")
|> notify_reload() |> notify_reload()
end end
end end
@@ -165,10 +165,10 @@ defmodule BDS.Desktop.ShellLive.TemplateEditor do
%Template{} = template -> %Template{} = template ->
case MCP.validate_template(current_draft(socket.assigns, template)["content"] || "") do case MCP.validate_template(current_draft(socket.assigns, template)["content"] || "") do
{:ok, %{valid: true}} -> {:ok, %{valid: true}} ->
notify_output(socket, translated("Templates"), translated("Template syntax is valid")) notify_output(socket, dgettext("ui", "Templates"), dgettext("ui", "Template syntax is valid"))
{:ok, %{valid: false, errors: errors}} -> {:ok, %{valid: false, errors: errors}} ->
notify_output(socket, translated("Templates"), Enum.join(errors, "; "), "error") notify_output(socket, dgettext("ui", "Templates"), Enum.join(errors, "; "), "error")
end end
end end
end end
@@ -183,7 +183,7 @@ defmodule BDS.Desktop.ShellLive.TemplateEditor do
{:error, reason} -> {:error, reason} ->
socket socket
|> notify_output(translated("Templates"), inspect(reason), "error") |> notify_output(dgettext("ui", "Templates"), inspect(reason), "error")
|> notify_reload() |> notify_reload()
end end
end end
@@ -235,7 +235,4 @@ defmodule BDS.Desktop.ShellLive.TemplateEditor do
send(self(), :reload_shell) send(self(), :reload_shell)
socket socket
end end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end end

View File

@@ -7,28 +7,28 @@
"status-#{@template_editor.status}" "status-#{@template_editor.status}"
]} data-testid="template-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@template_editor.status) %></span> ]} data-testid="template-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@template_editor.status) %></span>
<%= if @template_editor.can_publish? do %> <%= if @template_editor.can_publish? do %>
<button class="success" data-testid="template-publish-button" type="button" phx-click="publish_template_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Publish", %{}, BDS.Desktop.UILocale.current()) %></button> <button class="success" data-testid="template-publish-button" type="button" phx-click="publish_template_editor" phx-target={@myself}><%= dgettext("ui", "Publish") %></button>
<% end %> <% end %>
<button class="secondary templates-save-button" type="button" phx-click="save_template_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Save", %{}, BDS.Desktop.UILocale.current()) %></button> <button class="secondary templates-save-button" type="button" phx-click="save_template_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button>
<button class="secondary templates-validate-button" type="button" phx-click="validate_template_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Validate", %{}, BDS.Desktop.UILocale.current()) %></button> <button class="secondary templates-validate-button" type="button" phx-click="validate_template_editor" phx-target={@myself}><%= dgettext("ui", "Validate") %></button>
<button class="secondary danger" type="button" phx-click="delete_template_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Delete", %{}, BDS.Desktop.UILocale.current()) %></button> <button class="secondary danger" type="button" phx-click="delete_template_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
</div> </div>
</div> </div>
<form class="editor-content templates-view" phx-change="change_template_editor" phx-target={@myself}> <form class="editor-content templates-view" phx-change="change_template_editor" phx-target={@myself}>
<div class="editor-header-row templates-meta-row"> <div class="editor-header-row templates-meta-row">
<div class="editor-meta"> <div class="editor-meta">
<div class="editor-field-row"> <div class="editor-field-row">
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Title", %{}, BDS.Desktop.UILocale.current()) %></label><input type="text" name="template_editor[title]" value={@template_editor.title} /></div> <div class="editor-field"><label><%= dgettext("ui", "Title") %></label><input type="text" name="template_editor[title]" value={@template_editor.title} /></div>
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Slug", %{}, BDS.Desktop.UILocale.current()) %></label><input type="text" name="template_editor[slug]" value={@template_editor.slug} /></div> <div class="editor-field"><label><%= dgettext("ui", "Slug") %></label><input type="text" name="template_editor[slug]" value={@template_editor.slug} /></div>
</div> </div>
<div class="editor-field-row"> <div class="editor-field-row">
<div class="editor-field"><label><%= BDS.Desktop.ShellData.translate("Kind", %{}, BDS.Desktop.UILocale.current()) %></label><select name="template_editor[kind]"><option value="post" selected={@template_editor.kind == :post or @template_editor.kind == "post"}>post</option><option value="list" selected={@template_editor.kind == :list or @template_editor.kind == "list"}>list</option><option value="not-found" selected={@template_editor.kind == :"not-found" or @template_editor.kind == "not-found"}>not-found</option><option value="partial" selected={@template_editor.kind == :partial or @template_editor.kind == "partial"}>partial</option></select></div> <div class="editor-field"><label><%= dgettext("ui", "Kind") %></label><select name="template_editor[kind]"><option value="post" selected={@template_editor.kind == :post or @template_editor.kind == "post"}>post</option><option value="list" selected={@template_editor.kind == :list or @template_editor.kind == "list"}>list</option><option value="not-found" selected={@template_editor.kind == :"not-found" or @template_editor.kind == "not-found"}>not-found</option><option value="partial" selected={@template_editor.kind == :partial or @template_editor.kind == "partial"}>partial</option></select></div>
<div class="editor-field templates-enabled-field"><label><input type="checkbox" name="template_editor[enabled]" checked={@template_editor.enabled} /> <%= BDS.Desktop.ShellData.translate("Enabled", %{}, BDS.Desktop.UILocale.current()) %></label></div> <div class="editor-field templates-enabled-field"><label><input type="checkbox" name="template_editor[enabled]" checked={@template_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></div>
</div> </div>
</div> </div>
</div> </div>
<div class="editor-body templates-editor"> <div class="editor-body templates-editor">
<div class="editor-toolbar templates-toolbar"><div class="editor-toolbar-left"><label><%= BDS.Desktop.ShellData.translate("Content", %{}, BDS.Desktop.UILocale.current()) %></label></div></div> <div class="editor-toolbar templates-toolbar"><div class="editor-toolbar-left"><label><%= dgettext("ui", "Content") %></label></div></div>
<div <div
id={"template-editor-monaco-shell-#{@template_editor.id}"} id={"template-editor-monaco-shell-#{@template_editor.id}"}
class="templates-monaco monaco-editor-shell" class="templates-monaco monaco-editor-shell"
@@ -42,6 +42,6 @@
<textarea id={"template-editor-content-#{@template_editor.id}"} class="monaco-editor-input code-editor-textarea" name="template_editor[content]" spellcheck="false"><%= @template_editor.content %></textarea> <textarea id={"template-editor-content-#{@template_editor.id}"} class="monaco-editor-input code-editor-textarea" name="template_editor[content]" spellcheck="false"><%= @template_editor.content %></textarea>
</div> </div>
</div> </div>
<div class="editor-footer"><span class="text-muted text-small"><%= BDS.Desktop.ShellData.translate("Created", %{}, BDS.Desktop.UILocale.current()) %>: <%= BDS.Persistence.timestamp_to_iso8601(@template_editor.created_at) %></span><span class="text-muted text-small"><%= BDS.Desktop.ShellData.translate("Updated", %{}, BDS.Desktop.UILocale.current()) %>: <%= BDS.Persistence.timestamp_to_iso8601(@template_editor.updated_at) %></span></div> <div class="editor-footer"><span class="text-muted text-small"><%= dgettext("ui", "Created") %>: <%= BDS.Persistence.timestamp_to_iso8601(@template_editor.created_at) %></span><span class="text-muted text-small"><%= dgettext("ui", "Updated") %>: <%= BDS.Persistence.timestamp_to_iso8601(@template_editor.updated_at) %></span></div>
</form> </form>
</div> </div>

View File

@@ -30,6 +30,7 @@ defmodule BDS.Desktop.UILocale do
@spec put(locale()) :: :ok @spec put(locale()) :: :ok
def put(locale) do def put(locale) do
Process.put(@key, locale) Process.put(@key, locale)
BDS.Gettext.put_locale(locale)
:ok :ok
end end

42
lib/bds/gettext.ex Normal file
View File

@@ -0,0 +1,42 @@
defmodule BDS.Gettext do
@moduledoc """
Gettext backend for bDS2.
Two domains are used:
- "ui" : application chrome, menus, settings, dashboard, toasts
- "render" : blog output (Liquid templates, archive labels, pagination, etc.)
The UI locale is managed per-process via `put_locale/1`.
The render locale is passed explicitly via `lgettext/4` so that a single
process can resolve UI strings in the OS language and render strings in
the project's mainLanguage simultaneously.
"""
use Gettext.Backend, otp_app: :bds
@doc """
Convenience wrapper around `lgettext/4` with empty bindings.
Returns the resolved string directly (not a tuple).
"""
def lgettext(locale, domain, msgid) do
case lgettext(locale, domain, msgid, %{}) do
{:ok, text} -> text
{:default, text} -> text
end
end
@doc """
Sets the locale for this backend in the current process.
"""
def put_locale(locale) do
Gettext.put_locale(__MODULE__, locale)
end
@doc """
Gets the locale for this backend in the current process.
"""
def get_locale do
Gettext.get_locale(__MODULE__)
end
end

View File

@@ -28,18 +28,6 @@ defmodule BDS.I18n do
@default_language "en" @default_language "en"
@default_format_locale "en-US" @default_format_locale "en-US"
@locale_files Path.expand("../../priv/i18n/locales/*.json", __DIR__)
|> Path.wildcard()
|> Enum.sort()
for file <- @locale_files do
@external_resource file
end
@catalogs Enum.into(@locale_files, %{}, fn file ->
locale = file |> Path.basename(".json") |> String.downcase()
{locale, Jason.decode!(File.read!(file))}
end)
def supported_languages, do: @supported_languages def supported_languages, do: @supported_languages
@@ -81,33 +69,6 @@ defmodule BDS.I18n do
} }
end end
def get_render_translations(language) do
language
|> resolve_render_locale()
|> catalog_for_locale()
end
def get_ui_translations(locale) do
locale
|> resolve_ui_locale()
|> catalog_for_locale()
end
def translate(language, key) do
key = key |> to_string() |> String.trim()
case resolve_supported_locale(language) do
nil ->
Map.get(catalog_for_locale(@default_language), key, key)
locale ->
Map.get(catalog_for_locale(locale), key, key)
end
end
def translate_render(language, key), do: translate(language, key)
def translate_ui(locale, key), do: translate(locale, key)
def flag(language) do def flag(language) do
language language
|> resolve_supported_locale() |> resolve_supported_locale()
@@ -151,8 +112,4 @@ defmodule BDS.I18n do
|> String.split("-", parts: 2) |> String.split("-", parts: 2)
|> List.first() |> List.first()
end end
defp catalog_for_locale(locale) do
Map.get(@catalogs, locale, Map.get(@catalogs, @default_language, %{}))
end
end end

View File

@@ -3,7 +3,6 @@ defmodule BDS.Rendering.Filters do
use Liquex.Filter use Liquex.Filter
alias BDS.I18n
alias BDS.Slug alias BDS.Slug
def i18n(value, language, _context) do def i18n(value, language, _context) do
@@ -12,7 +11,7 @@ defmodule BDS.Rendering.Filters do
if key == "" do if key == "" do
"" ""
else else
I18n.translate(language, key) BDS.Gettext.lgettext(language, "render", key)
end end
end end
@@ -59,7 +58,7 @@ defmodule BDS.Rendering.Filters do
default_macro_title( default_macro_title(
Map.get(params, "title"), Map.get(params, "title"),
language, language,
"render.video.youtubeTitle" "YouTube video"
) )
}, },
context context
@@ -71,7 +70,7 @@ defmodule BDS.Rendering.Filters do
%{ %{
"id" => Map.get(params, "id", ""), "id" => Map.get(params, "id", ""),
"title" => "title" =>
default_macro_title(Map.get(params, "title"), language, "render.video.vimeoTitle") default_macro_title(Map.get(params, "title"), language, "Vimeo video")
}, },
context context
) )
@@ -82,13 +81,17 @@ defmodule BDS.Rendering.Filters do
end) end)
end end
defp default_macro_title(nil, language, translation_key), defp default_macro_title(nil, language, translation),
do: I18n.translate(language, translation_key) do: translated_macro_title(language, translation)
defp default_macro_title("", language, translation_key), defp default_macro_title("", language, translation),
do: I18n.translate(language, translation_key) do: translated_macro_title(language, translation)
defp default_macro_title(title, _language, _translation_key), do: title defp default_macro_title(title, _language, _translation), do: title
defp translated_macro_title(language, translation) do
BDS.Gettext.lgettext(language, "render", translation)
end
defp parse_macro_params(nil), do: %{} defp parse_macro_params(nil), do: %{}
defp parse_macro_params(""), do: %{} defp parse_macro_params(""), do: %{}

View File

@@ -1,8 +0,0 @@
defmodule BDS.Rendering.I18n do
@moduledoc false
defdelegate supported_languages(), to: BDS.I18n
defdelegate normalize_language(language), to: BDS.I18n
defdelegate translate(language, key), to: BDS.I18n
defdelegate flag(language), to: BDS.I18n
end

View File

@@ -0,0 +1,88 @@
defmodule BDS.Rendering.Labels do
@moduledoc """
Pre-translated render labels passed into Liquid template context.
All strings use constant msgids so mix gettext.extract can discover them.
The render locale is bound explicitly via Gettext.with_locale/3.
"""
use Gettext, backend: BDS.Gettext
def for_language(language) do
Gettext.with_locale(BDS.Gettext, language, fn ->
%{
taxonomy_label: dgettext("render", "Taxonomy"),
backlinks_label: dgettext("render", "Backlinks"),
linked_from_label: dgettext("render", "Linked from"),
archive_label: dgettext("render", "Archive"),
pagination_label: dgettext("render", "Pagination"),
newer_label: dgettext("render", "newer"),
older_label: dgettext("render", "older"),
calendar_open_label: dgettext("render", "Open calendar"),
calendar_loading_label: dgettext("render", "Loading calendar…"),
calendar_error_label: dgettext("render", "Calendar data could not be loaded."),
calendar_title_label: dgettext("render", "Archive calendar"),
calendar_close_label: dgettext("render", "Close calendar"),
language_switcher_label: dgettext("render", "Language"),
site_search_label: dgettext("render", "Site search"),
search_placeholder: dgettext("render", "Search..."),
not_found_message: dgettext("render", "The requested preview page could not be found."),
not_found_back_label: dgettext("render", "Back to preview home"),
youtube_video: dgettext("render", "YouTube video"),
vimeo_video: dgettext("render", "Vimeo video")
}
end)
end
def month_name(nil, _language), do: nil
def month_name(1, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "January") end)
end
def month_name(2, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "February") end)
end
def month_name(3, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "March") end)
end
def month_name(4, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "April") end)
end
def month_name(5, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "May") end)
end
def month_name(6, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "June") end)
end
def month_name(7, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "July") end)
end
def month_name(8, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "August") end)
end
def month_name(9, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "September") end)
end
def month_name(10, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "October") end)
end
def month_name(11, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "November") end)
end
def month_name(12, language) do
Gettext.with_locale(BDS.Gettext, language, fn -> dgettext("render", "December") end)
end
def month_name(_month, _language), do: nil
end

View File

@@ -1,13 +1,14 @@
defmodule BDS.Rendering.ListArchive do defmodule BDS.Rendering.ListArchive do
@moduledoc false @moduledoc false
alias BDS.I18n
alias BDS.MapUtils alias BDS.MapUtils
alias BDS.Persistence alias BDS.Persistence
alias BDS.Rendering.Labels
alias BDS.Rendering.LinksAndLanguages alias BDS.Rendering.LinksAndLanguages
alias BDS.Rendering.Metadata, as: RenderMetadata alias BDS.Rendering.Metadata, as: RenderMetadata
alias BDS.Rendering.PostRendering alias BDS.Rendering.PostRendering
alias BDS.Rendering.TemplateSelection alias BDS.Rendering.TemplateSelection
use Gettext, backend: BDS.Gettext
def list_assigns(project_id, assigns) do def list_assigns(project_id, assigns) do
metadata = RenderMetadata.project_metadata(project_id) metadata = RenderMetadata.project_metadata(project_id)
@@ -95,7 +96,9 @@ defmodule BDS.Rendering.ListArchive do
canonical_media_path_by_source_path: canonical_media_paths, canonical_media_path_by_source_path: canonical_media_paths,
post_data_json_by_id: post_data_json_by_id:
Enum.into(posts, %{}, fn post -> {post.id, PostRendering.post_data_json_value(post)} end), Enum.into(posts, %{}, fn post -> {post.id, PostRendering.post_data_json_value(post)} end),
day_blocks: day_blocks day_blocks: day_blocks,
archive_month_name: Labels.month_name(Map.get(normalized_archive_context, :month), language),
labels: Labels.for_language(language)
} }
end end
@@ -145,7 +148,7 @@ defmodule BDS.Rendering.ListArchive do
Map.get( Map.get(
assigns, assigns,
"not_found_message", "not_found_message",
I18n.translate(language, "render.notFound.message") BDS.Gettext.lgettext(language, "render", "The requested preview page could not be found.")
) )
), ),
not_found_back_label: not_found_back_label:
@@ -155,9 +158,10 @@ defmodule BDS.Rendering.ListArchive do
Map.get( Map.get(
assigns, assigns,
"not_found_back_label", "not_found_back_label",
I18n.translate(language, "render.notFound.back") BDS.Gettext.lgettext(language, "render", "Back to preview home")
)
) )
),
labels: Labels.for_language(language)
} }
end end

View File

@@ -2,6 +2,7 @@ defmodule BDS.Rendering.PostRendering do
@moduledoc false @moduledoc false
alias BDS.Rendering.Filters alias BDS.Rendering.Filters
alias BDS.Rendering.Labels
alias BDS.Rendering.LinksAndLanguages alias BDS.Rendering.LinksAndLanguages
alias BDS.Rendering.Metadata, as: RenderMetadata alias BDS.Rendering.Metadata, as: RenderMetadata
alias BDS.Rendering.TemplateSelection alias BDS.Rendering.TemplateSelection
@@ -95,7 +96,8 @@ defmodule BDS.Rendering.PostRendering do
canonical_post_path_by_slug: canonical_post_paths, canonical_post_path_by_slug: canonical_post_paths,
canonical_media_path_by_source_path: canonical_media_paths, canonical_media_path_by_source_path: canonical_media_paths,
post_data_json_by_id: post_data_json(post_assigns, post_record), post_data_json_by_id: post_data_json(post_assigns, post_record),
post: build_post_context(post_assigns, post_record, incoming_links, outgoing_links) post: build_post_context(post_assigns, post_record, incoming_links, outgoing_links),
labels: Labels.for_language(language)
} }
end end

View File

@@ -1,10 +1,15 @@
defmodule BDS.UI.Registry do defmodule BDS.UI.Registry do
@moduledoc false @moduledoc false
@sidebar_views [ use Gettext, backend: BDS.Gettext
def default_sidebar_view, do: :posts
def sidebar_views do
[
%{ %{
id: :posts, id: :posts,
label: "Posts", label: dgettext("ui", "Posts"),
activity_group: :top, activity_group: :top,
editor_route: :post, editor_route: :post,
entity_tab: true, entity_tab: true,
@@ -12,7 +17,7 @@ defmodule BDS.UI.Registry do
}, },
%{ %{
id: :pages, id: :pages,
label: "Pages", label: dgettext("ui", "Pages"),
activity_group: :top, activity_group: :top,
editor_route: :post, editor_route: :post,
entity_tab: true, entity_tab: true,
@@ -20,7 +25,7 @@ defmodule BDS.UI.Registry do
}, },
%{ %{
id: :media, id: :media,
label: "Media", label: dgettext("ui", "Media"),
activity_group: :top, activity_group: :top,
editor_route: :media, editor_route: :media,
entity_tab: true, entity_tab: true,
@@ -28,7 +33,7 @@ defmodule BDS.UI.Registry do
}, },
%{ %{
id: :scripts, id: :scripts,
label: "Scripts", label: dgettext("ui", "Scripts"),
activity_group: :top, activity_group: :top,
editor_route: :scripts, editor_route: :scripts,
entity_tab: true, entity_tab: true,
@@ -36,7 +41,7 @@ defmodule BDS.UI.Registry do
}, },
%{ %{
id: :templates, id: :templates,
label: "Templates", label: dgettext("ui", "Templates"),
activity_group: :top, activity_group: :top,
editor_route: :templates, editor_route: :templates,
entity_tab: true, entity_tab: true,
@@ -44,7 +49,7 @@ defmodule BDS.UI.Registry do
}, },
%{ %{
id: :tags, id: :tags,
label: "Tags", label: dgettext("ui", "Tags"),
activity_group: :top, activity_group: :top,
editor_route: :tags, editor_route: :tags,
singleton: true, singleton: true,
@@ -52,7 +57,7 @@ defmodule BDS.UI.Registry do
}, },
%{ %{
id: :chat, id: :chat,
label: "AI Assistant", label: dgettext("ui", "AI Assistant"),
activity_group: :top, activity_group: :top,
editor_route: :chat, editor_route: :chat,
entity_tab: true, entity_tab: true,
@@ -60,7 +65,7 @@ defmodule BDS.UI.Registry do
}, },
%{ %{
id: :import, id: :import,
label: "Import", label: dgettext("ui", "Import"),
activity_group: :top, activity_group: :top,
editor_route: :import, editor_route: :import,
entity_tab: true, entity_tab: true,
@@ -68,7 +73,7 @@ defmodule BDS.UI.Registry do
}, },
%{ %{
id: :git, id: :git,
label: "Source Control", label: dgettext("ui", "Source Control"),
activity_group: :bottom, activity_group: :bottom,
editor_route: :git_diff, editor_route: :git_diff,
entity_tab: true, entity_tab: true,
@@ -76,39 +81,38 @@ defmodule BDS.UI.Registry do
}, },
%{ %{
id: :settings, id: :settings,
label: "Settings", label: dgettext("ui", "Settings"),
activity_group: :bottom, activity_group: :bottom,
editor_route: :settings, editor_route: :settings,
singleton: true, singleton: true,
demo_kind: :singleton demo_kind: :singleton
} }
] ]
end
@editor_routes [ def editor_routes do
%{id: :dashboard, singleton: true, entity_tab: false, title: "Dashboard"}, [
%{id: :post, singleton: false, entity_tab: true, title: "Post"}, %{id: :dashboard, singleton: true, entity_tab: false, title: dgettext("ui", "Dashboard")},
%{id: :media, singleton: false, entity_tab: true, title: "Media"}, %{id: :post, singleton: false, entity_tab: true, title: dgettext("ui", "Post")},
%{id: :settings, singleton: true, entity_tab: false, title: "Settings"}, %{id: :media, singleton: false, entity_tab: true, title: dgettext("ui", "Media")},
%{id: :style, singleton: true, entity_tab: false, title: "Style"}, %{id: :settings, singleton: true, entity_tab: false, title: dgettext("ui", "Settings")},
%{id: :tags, singleton: true, entity_tab: false, title: "Tags"}, %{id: :style, singleton: true, entity_tab: false, title: dgettext("ui", "Style")},
%{id: :chat, singleton: false, entity_tab: true, title: "Chat"}, %{id: :tags, singleton: true, entity_tab: false, title: dgettext("ui", "Tags")},
%{id: :import, singleton: false, entity_tab: true, title: "Import"}, %{id: :chat, singleton: false, entity_tab: true, title: dgettext("ui", "Chat")},
%{id: :menu_editor, singleton: true, entity_tab: false, title: "Menu"}, %{id: :import, singleton: false, entity_tab: true, title: dgettext("ui", "Import")},
%{id: :metadata_diff, singleton: true, entity_tab: false, title: "Metadata Diff"}, %{id: :menu_editor, singleton: true, entity_tab: false, title: dgettext("ui", "Menu")},
%{id: :git_diff, singleton: false, entity_tab: true, title: "Git Diff"}, %{id: :metadata_diff, singleton: true, entity_tab: false, title: dgettext("ui", "Metadata Diff")},
%{id: :documentation, singleton: true, entity_tab: false, title: "Documentation"}, %{id: :git_diff, singleton: false, entity_tab: true, title: dgettext("ui", "Git Diff")},
%{id: :api_documentation, singleton: true, entity_tab: false, title: "API"}, %{id: :documentation, singleton: true, entity_tab: false, title: dgettext("ui", "Documentation")},
%{id: :site_validation, singleton: true, entity_tab: false, title: "Site Validation"}, %{id: :api_documentation, singleton: true, entity_tab: false, title: dgettext("ui", "API")},
%{id: :translation_validation, singleton: true, entity_tab: false, title: "Translations"}, %{id: :site_validation, singleton: true, entity_tab: false, title: dgettext("ui", "Site Validation")},
%{id: :scripts, singleton: false, entity_tab: true, title: "Script"}, %{id: :translation_validation, singleton: true, entity_tab: false, title: dgettext("ui", "Translations")},
%{id: :templates, singleton: false, entity_tab: true, title: "Template"}, %{id: :scripts, singleton: false, entity_tab: true, title: dgettext("ui", "Script")},
%{id: :find_duplicates, singleton: true, entity_tab: false, title: "Find Duplicates"} %{id: :templates, singleton: false, entity_tab: true, title: dgettext("ui", "Template")},
%{id: :find_duplicates, singleton: true, entity_tab: false, title: dgettext("ui", "Find Duplicates")}
] ]
end
def default_sidebar_view, do: :posts def sidebar_view(id) when is_atom(id), do: Enum.find(sidebar_views(), &(&1.id == id))
def sidebar_views, do: @sidebar_views def editor_route(id) when is_atom(id), do: Enum.find(editor_routes(), &(&1.id == id))
def editor_routes, do: @editor_routes
def sidebar_view(id) when is_atom(id), do: Enum.find(@sidebar_views, &(&1.id == id))
def editor_route(id) when is_atom(id), do: Enum.find(@editor_routes, &(&1.id == id))
end end

View File

@@ -2,6 +2,7 @@ defmodule BDS.UI.Sidebar do
@moduledoc false @moduledoc false
import Ecto.Query import Ecto.Query
use Gettext, backend: BDS.Gettext
alias BDS.AI.ChatConversation alias BDS.AI.ChatConversation
alias BDS.ImportDefinitions alias BDS.ImportDefinitions
@@ -29,8 +30,8 @@ defmodule BDS.UI.Sidebar do
"chat" => view(project_id, "chat"), "chat" => view(project_id, "chat"),
"import" => "import" =>
entity_list_view( entity_list_view(
"Import", dgettext("ui", "Import"),
"Import definitions", dgettext("ui", "Import definitions"),
"import", "import",
list_import_definitions(project_id) list_import_definitions(project_id)
), ),
@@ -57,21 +58,36 @@ defmodule BDS.UI.Sidebar do
media_view(project_id, params) media_view(project_id, params)
"scripts" -> "scripts" ->
entity_list_view("Scripts", "Automation helpers", "scripts", list_scripts(project_id)) entity_list_view(
dgettext("ui", "Scripts"),
dgettext("ui", "Automation helpers"),
"scripts",
list_scripts(project_id)
)
"templates" -> "templates" ->
entity_list_view("Templates", "Site rendering", "templates", list_templates(project_id)) entity_list_view(
dgettext("ui", "Templates"),
dgettext("ui", "Site rendering"),
"templates",
list_templates(project_id)
)
"tags" -> "tags" ->
tags_nav_view(list_tags(project_id)) tags_nav_view(list_tags(project_id))
"chat" -> "chat" ->
entity_list_view("Chat", "AI conversations", "chat", list_conversations()) entity_list_view(
dgettext("ui", "Chat"),
dgettext("ui", "AI conversations"),
"chat",
list_conversations()
)
"import" -> "import" ->
entity_list_view( entity_list_view(
"Import", dgettext("ui", "Import"),
"Import definitions", dgettext("ui", "Import definitions"),
"import", "import",
list_import_definitions(project_id) list_import_definitions(project_id)
) )
@@ -92,11 +108,35 @@ defmodule BDS.UI.Sidebar do
"posts" => empty_view("posts"), "posts" => empty_view("posts"),
"pages" => empty_view("pages"), "pages" => empty_view("pages"),
"media" => empty_view("media"), "media" => empty_view("media"),
"scripts" => entity_list_view("Scripts", "Automation helpers", "scripts", []), "scripts" =>
"templates" => entity_list_view("Templates", "Site rendering", "templates", []), entity_list_view(
dgettext("ui", "Scripts"),
dgettext("ui", "Automation helpers"),
"scripts",
[]
),
"templates" =>
entity_list_view(
dgettext("ui", "Templates"),
dgettext("ui", "Site rendering"),
"templates",
[]
),
"tags" => tags_nav_view([]), "tags" => tags_nav_view([]),
"chat" => entity_list_view("Chat", "AI conversations", "chat", []), "chat" =>
"import" => entity_list_view("Import", "Import definitions", "import", []), entity_list_view(
dgettext("ui", "Chat"),
dgettext("ui", "AI conversations"),
"chat",
[]
),
"import" =>
entity_list_view(
dgettext("ui", "Import"),
dgettext("ui", "Import definitions"),
"import",
[]
),
"git" => git_view(), "git" => git_view(),
"settings" => settings_nav_view() "settings" => settings_nav_view()
} }
@@ -105,19 +145,56 @@ defmodule BDS.UI.Sidebar do
defp empty_view("posts"), do: posts_view_data([], [], %{}, false, empty_filter_params(), %{}) defp empty_view("posts"), do: posts_view_data([], [], %{}, false, empty_filter_params(), %{})
defp empty_view("pages"), do: posts_view_data([], [], %{}, true, empty_filter_params(), %{}) defp empty_view("pages"), do: posts_view_data([], [], %{}, true, empty_filter_params(), %{})
defp empty_view("media"), do: media_view_data([], [], empty_filter_params(), %{}) defp empty_view("media"), do: media_view_data([], [], empty_filter_params(), %{})
defp empty_view("scripts"), do: entity_list_view("Scripts", "Automation helpers", "scripts", [])
defp empty_view("scripts"),
do:
entity_list_view(
dgettext("ui", "Scripts"),
dgettext("ui", "Automation helpers"),
"scripts",
[]
)
defp empty_view("templates"), defp empty_view("templates"),
do: entity_list_view("Templates", "Site rendering", "templates", []) do:
entity_list_view(
dgettext("ui", "Templates"),
dgettext("ui", "Site rendering"),
"templates",
[]
)
defp empty_view("tags"), do: tags_nav_view([]) defp empty_view("tags"), do: tags_nav_view([])
defp empty_view("chat"), do: entity_list_view("Chat", "AI conversations", "chat", [])
defp empty_view("import"), do: entity_list_view("Import", "Import definitions", "import", []) defp empty_view("chat"),
do:
entity_list_view(
dgettext("ui", "Chat"),
dgettext("ui", "AI conversations"),
"chat",
[]
)
defp empty_view("import"),
do:
entity_list_view(
dgettext("ui", "Import"),
dgettext("ui", "Import definitions"),
"import",
[]
)
defp empty_view("git"), do: git_view() defp empty_view("git"), do: git_view()
defp empty_view("settings"), do: settings_nav_view() defp empty_view("settings"), do: settings_nav_view()
defp empty_view(_other), defp empty_view(_other),
do: %{title: "", subtitle: "", layout: "entity_list", items: [], empty_message: "No items"} do: %{
title: "",
subtitle: "",
layout: "entity_list",
items: [],
empty_message: dgettext("ui", "No items")
}
defp posts_view(project_id, params, pages?) do defp posts_view(project_id, params, pages?) do
posts = list_posts(project_id) posts = list_posts(project_id)
@@ -144,25 +221,32 @@ defmodule BDS.UI.Sidebar do
available_categories = available_categories(base_posts, pages?) available_categories = available_categories(base_posts, pages?)
%{ %{
title: if(pages?, do: "Pages", else: "Posts"), title: if(pages?, do: dgettext("ui", "Pages"), else: dgettext("ui", "Posts")),
subtitle: subtitle:
if(pages?, do: "Standalone pages", else: "Drafts, published entries, and archive history"), if(pages?,
do: dgettext("ui", "Standalone pages"),
else: dgettext("ui", "Drafts, published entries, and archive history")
),
layout: "post_list", layout: "post_list",
empty_message: if(pages?, do: "sidebar.noPagesYet", else: "sidebar.noPostsYet"), empty_message:
if(pages?, do: dgettext("ui", "No pages yet"), else: dgettext("ui", "No posts yet")),
filters: %{ filters: %{
enabled: true, enabled: true,
search_placeholder: search_placeholder:
if(pages?, do: "sidebar.searchPagesPlaceholder", else: "sidebar.searchPostsPlaceholder"), if(pages?,
toggle_filters_label: "sidebar.toggleFilters", do: dgettext("ui", "Search pages..."),
archive_label: "render.archive", else: dgettext("ui", "Search posts...")
tags_label: "sidebar.tags", ),
categories_label: "sidebar.categories", toggle_filters_label: dgettext("ui", "Toggle Filters"),
clear_tags_label: "sidebar.clearTags", archive_label: dgettext("render", "Archive"),
clear_categories_label: "sidebar.clearCategories", tags_label: dgettext("ui", "Tags"),
clear_filters_label: "sidebar.clearFilters", categories_label: dgettext("ui", "Categories"),
results_label: "sidebar.results", clear_tags_label: dgettext("ui", "Clear tags"),
results_for_label: "sidebar.resultsFor", clear_categories_label: dgettext("ui", "Clear categories"),
no_results_label: "sidebar.noMatchingPosts", clear_filters_label: dgettext("ui", "Clear filters"),
results_label: dgettext("ui", "results"),
results_for_label: dgettext("ui", "results for"),
no_results_label: dgettext("ui", "No matching posts"),
year_month_counts: year_month_counts(base_posts, &post_filter_timestamp/1), year_month_counts: year_month_counts(base_posts, &post_filter_timestamp/1),
available_tags: available_tags, available_tags: available_tags,
available_tag_colors: Map.take(tag_colors, available_tags), available_tag_colors: Map.take(tag_colors, available_tags),
@@ -182,16 +266,22 @@ defmodule BDS.UI.Sidebar do
} }
}, },
sections: [ sections: [
build_post_section("Drafts", :draft, grouped_posts.draft, translation_counts, false),
build_post_section( build_post_section(
"Published", dgettext("ui", "Drafts"),
:draft,
grouped_posts.draft,
translation_counts,
false
),
build_post_section(
dgettext("ui", "Published"),
:published, :published,
grouped_posts.published, grouped_posts.published,
translation_counts, translation_counts,
true true
), ),
build_post_section( build_post_section(
"Archived", dgettext("ui", "Archived"),
:archived, :archived,
grouped_posts.archived, grouped_posts.archived,
translation_counts, translation_counts,
@@ -215,21 +305,21 @@ defmodule BDS.UI.Sidebar do
available_tags = available_tags(base_media, & &1.tags) available_tags = available_tags(base_media, & &1.tags)
%{ %{
title: "Media", title: dgettext("ui", "Media"),
subtitle: "Images and files", subtitle: dgettext("ui", "Images and files"),
layout: "media_grid", layout: "media_grid",
empty_message: "sidebar.noMediaFiles", empty_message: dgettext("ui", "No media files"),
filters: %{ filters: %{
enabled: true, enabled: true,
search_placeholder: "sidebar.searchMediaPlaceholder", search_placeholder: dgettext("ui", "Search media..."),
toggle_filters_label: "sidebar.toggleFilters", toggle_filters_label: dgettext("ui", "Toggle Filters"),
archive_label: "render.archive", archive_label: dgettext("render", "Archive"),
tags_label: "sidebar.tags", tags_label: dgettext("ui", "Tags"),
clear_tags_label: "sidebar.clearTags", clear_tags_label: dgettext("ui", "Clear tags"),
clear_filters_label: "sidebar.clearFilters", clear_filters_label: dgettext("ui", "Clear filters"),
results_label: "sidebar.results", results_label: dgettext("ui", "results"),
results_for_label: "sidebar.resultsFor", results_for_label: dgettext("ui", "results for"),
no_results_label: "sidebar.noMediaFiles", no_results_label: dgettext("ui", "No media files"),
year_month_counts: year_month_counts(base_media, &Map.get(&1, :updated_at)), year_month_counts: year_month_counts(base_media, &Map.get(&1, :updated_at)),
available_tags: available_tags, available_tags: available_tags,
available_tag_colors: Map.take(tag_colors, available_tags), available_tag_colors: Map.take(tag_colors, available_tags),
@@ -266,13 +356,13 @@ defmodule BDS.UI.Sidebar do
defp tags_nav_view(tags) do defp tags_nav_view(tags) do
%{ %{
title: "Tags", title: dgettext("ui", "Tags"),
subtitle: "Tag management", subtitle: dgettext("ui", "Tag management"),
layout: "nav_list", layout: "nav_list",
items: [ items: [
%{id: "tags-cloud", title: "Tag Cloud", icon: "☁️", route: "tags"}, %{id: "tags-cloud", title: dgettext("ui", "Tag Cloud"), icon: "☁️", route: "tags"},
%{id: "tags-manage", title: "Create / Edit", icon: "✏️", route: "tags"}, %{id: "tags-manage", title: dgettext("ui", "Create / Edit"), icon: "✏️", route: "tags"},
%{id: "tags-merge", title: "Merge Tags", icon: "🔀", route: "tags"} %{id: "tags-merge", title: dgettext("ui", "Merge Tags"), icon: "🔀", route: "tags"}
], ],
summary_badge: length(tags) summary_badge: length(tags)
} }
@@ -280,34 +370,44 @@ defmodule BDS.UI.Sidebar do
defp settings_nav_view do defp settings_nav_view do
%{ %{
title: "Settings", title: dgettext("ui", "Settings"),
subtitle: "Project and publishing", subtitle: dgettext("ui", "Project and publishing"),
layout: "nav_list", layout: "nav_list",
items: [ items: [
%{id: "settings-project", title: "Project", icon: "📁", route: "settings"}, %{id: "settings-project", title: dgettext("ui", "Project"), icon: "📁", route: "settings"},
%{id: "settings-editor", title: "Editor", icon: "📝", route: "settings"}, %{id: "settings-editor", title: dgettext("ui", "Editor"), icon: "📝", route: "settings"},
%{id: "settings-content", title: "Content", icon: "📋", route: "settings"}, %{id: "settings-content", title: dgettext("ui", "Content"), icon: "📋", route: "settings"},
%{id: "settings-ai", title: "AI", icon: "🤖", route: "settings"}, %{id: "settings-ai", title: dgettext("ui", "AI"), icon: "🤖", route: "settings"},
%{id: "settings-technology", title: "Technology", icon: "⚙️", route: "settings"}, %{
%{id: "settings-publishing", title: "Publishing", icon: "🚀", route: "settings"}, id: "settings-technology",
%{id: "settings-data", title: "Data", icon: "🗄️", route: "settings"}, title: dgettext("ui", "Technology"),
%{id: "settings-mcp", title: "MCP", icon: "🔌", route: "settings"}, icon: "⚙️",
%{id: "settings-style", title: "Style", icon: "🎨", route: "style"} route: "settings"
},
%{
id: "settings-publishing",
title: dgettext("ui", "Publishing"),
icon: "🚀",
route: "settings"
},
%{id: "settings-data", title: dgettext("ui", "Data"), icon: "🗄️", route: "settings"},
%{id: "settings-mcp", title: dgettext("ui", "MCP"), icon: "🔌", route: "settings"},
%{id: "settings-style", title: dgettext("ui", "Style"), icon: "🎨", route: "style"}
] ]
} }
end end
defp git_view do defp git_view do
%{ %{
title: "Git", title: dgettext("ui", "Git"),
subtitle: "Working tree and history", subtitle: dgettext("ui", "Working tree and history"),
layout: "entity_list", layout: "entity_list",
empty_message: "No items", empty_message: dgettext("ui", "No items"),
items: [ items: [
%{ %{
id: "git-working-tree", id: "git-working-tree",
title: "Working tree", title: dgettext("ui", "Working tree"),
meta: "Working tree and history", meta: dgettext("ui", "Working tree and history"),
route: "git_diff", route: "git_diff",
updated_at: nil updated_at: nil
} }
@@ -320,7 +420,7 @@ defmodule BDS.UI.Sidebar do
title: title, title: title,
subtitle: subtitle, subtitle: subtitle,
layout: "entity_list", layout: "entity_list",
empty_message: "No items", empty_message: dgettext("ui", "No items"),
items: items:
Enum.map(items, fn item -> Enum.map(items, fn item ->
%{ %{
@@ -350,7 +450,10 @@ defmodule BDS.UI.Sidebar do
status: Atom.to_string(post.status), status: Atom.to_string(post.status),
language_count: 1 + Map.get(translation_counts, post.id, 0), language_count: 1 + Map.get(translation_counts, post.id, 0),
meta_timestamp: meta_timestamp:
if(published_meta?, do: post.published_at || post.updated_at, else: post.updated_at), if(published_meta?,
do: post.published_at || post.updated_at,
else: post.updated_at
),
route: "post", route: "post",
search_blob: post_search_blob(post) search_blob: post_search_blob(post)
} }
@@ -651,7 +754,7 @@ defmodule BDS.UI.Sidebar do
cond do cond do
present?(post.title) -> post.title present?(post.title) -> post.title
present?(post.slug) -> post.slug present?(post.slug) -> post.slug
true -> "Untitled" true -> dgettext("ui", "Untitled")
end end
end end

View File

@@ -1,6 +1,8 @@
defmodule BDS.UI.Workbench do defmodule BDS.UI.Workbench do
@moduledoc false @moduledoc false
use Gettext, backend: BDS.Gettext
alias BDS.UI.Registry alias BDS.UI.Registry
@singleton_tabs MapSet.new([ @singleton_tabs MapSet.new([
@@ -175,6 +177,9 @@ defmodule BDS.UI.Workbench do
end end
def status_bar(state, opts) do def status_bar(state, opts) do
post_count = Keyword.get(opts, :post_count, 0)
media_count = Keyword.get(opts, :media_count, 0)
%{ %{
left: %{ left: %{
running_task_message: Keyword.get(opts, :running_task_message), running_task_message: Keyword.get(opts, :running_task_message),
@@ -182,10 +187,10 @@ defmodule BDS.UI.Workbench do
}, },
right: %{ right: %{
post_status: post_status(state, Keyword.get(opts, :active_post_status)), post_status: post_status(state, Keyword.get(opts, :active_post_status)),
post_count: "#{Keyword.get(opts, :post_count, 0)} posts", post_count: "#{post_count} #{dngettext("ui", "post", "posts", post_count)}",
post_count_value: Keyword.get(opts, :post_count, 0), post_count_value: post_count,
media_count: "#{Keyword.get(opts, :media_count, 0)} media", media_count: "#{media_count} #{dngettext("ui", "media", "media", media_count)}",
media_count_value: Keyword.get(opts, :media_count, 0), media_count_value: media_count,
token_usage: token_usage(state, Keyword.get(opts, :token_usage)), token_usage: token_usage(state, Keyword.get(opts, :token_usage)),
theme_badge: Keyword.get(opts, :theme_badge, "default"), theme_badge: Keyword.get(opts, :theme_badge, "default"),
offline_mode: Keyword.get(opts, :offline_mode, false), offline_mode: Keyword.get(opts, :offline_mode, false),

View File

@@ -35,6 +35,7 @@ defmodule BDS.MixProject do
{:desktop, "~> 1.5"}, {:desktop, "~> 1.5"},
{:image, "~> 0.65"}, {:image, "~> 0.65"},
{:stemex, "~> 0.2.1"}, {:stemex, "~> 0.2.1"},
{:gettext, "~> 0.24"},
{:lazy_html, ">= 0.1.0", only: :test}, {:lazy_html, ">= 0.1.0", only: :test},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}
] ]

View File

@@ -21,7 +21,7 @@
"expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"}, "expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"},
"exqlite": {:hex, :exqlite, "0.36.0", "07b4f95d61cb82b8d52946d0639497fa7d32117e09b2c8d25e24a38723c295cb", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "cbeca3ce781f9ff07cfa9a87486f3ebd512a143ad6a14ed5c9fca21fe0bf3ae7"}, "exqlite": {:hex, :exqlite, "0.36.0", "07b4f95d61cb82b8d52946d0639497fa7d32117e09b2c8d25e24a38723c295cb", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "cbeca3ce781f9ff07cfa9a87486f3ebd512a143ad6a14ed5c9fca21fe0bf3ae7"},
"fine": {:hex, :fine, "0.1.6", "4bf7151493443c454aac9f2fa2f34f5fefd0346a83fb5586a016c4a135c63247", [:mix], [], "hexpm", "5638eb4495488e885ebec167fa57973e5c35e1a50c344eb7666c90ec1c4e3b12"}, "fine": {:hex, :fine, "0.1.6", "4bf7151493443c454aac9f2fa2f34f5fefd0346a83fb5586a016c4a135c63247", [:mix], [], "hexpm", "5638eb4495488e885ebec167fa57973e5c35e1a50c344eb7666c90ec1c4e3b12"},
"gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"}, "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.4", "271455b4d300d5d53a5d92b5bd1c00ad14c5abf1c9ff87be069af5736496515c", [:mix], [{:mochiweb, "~> 2.15 or ~> 3.1", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "12e1754204e7db5df1750df0a5dba1bbdf89260800019ab081f2b046596be56b"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.4", "271455b4d300d5d53a5d92b5bd1c00ad14c5abf1c9ff87be069af5736496515c", [:mix], [{:mochiweb, "~> 2.15 or ~> 3.1", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "12e1754204e7db5df1750df0a5dba1bbdf89260800019ab081f2b046596be56b"},

View File

@@ -14,10 +14,8 @@ version: 1
<section class="not-found" data-template="not-found"> <section class="not-found" data-template="not-found">
<article> <article>
<h1>404</h1> <h1>404</h1>
{% assign default_not_found_message = 'render.notFound.message' | i18n: language %} <p>{{ not_found_message }}</p>
{% assign default_not_found_back = 'render.notFound.back' | i18n: language %} <p><a href="/" role="button">{{ not_found_back_label }}</a></p>
<p>{{ not_found_message | default: default_not_found_message }}</p>
<p><a href="/" role="button">{{ not_found_back_label | default: default_not_found_back }}</a></p>
</article> </article>
</section> </section>
</main> </main>

View File

@@ -1,5 +1,5 @@
{% if blog_languages.size > 1 %} {% if blog_languages.size > 1 %}
<nav class="language-switcher" aria-label="{{ 'render.languageSwitcher.ariaLabel' | i18n: language }}"> <nav class="language-switcher" aria-label="{{ labels.language_switcher_label }}">
{% for lang in blog_languages %} {% for lang in blog_languages %}
{% if lang.is_current %} {% if lang.is_current %}
<span class="language-switcher-badge language-switcher-badge-current" aria-current="true" title="{{ lang.code }}">{{ lang.flag }}</span> <span class="language-switcher-badge language-switcher-badge-current" aria-current="true" title="{{ lang.code }}">{{ lang.flag }}</span>
@@ -7,15 +7,15 @@
<a class="language-switcher-badge" href="{{ lang.href_prefix | default: '/' }}" data-lang-prefix="{{ lang.href_prefix }}" title="{{ lang.code }}">{{ lang.flag }}</a> <a class="language-switcher-badge" href="{{ lang.href_prefix | default: '/' }}" data-lang-prefix="{{ lang.href_prefix }}" title="{{ lang.code }}">{{ lang.flag }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<div class="blog-search-widget" aria-label="{{ 'render.search.ariaLabel' | i18n: language }}"> <div class="blog-search-widget" aria-label="{{ labels.site_search_label }}">
<button type="button" class="blog-search-toggle" data-blog-search-toggle aria-label="{{ 'render.search.ariaLabel' | i18n: language }}"> <button type="button" class="blog-search-toggle" data-blog-search-toggle aria-label="{{ labels.site_search_label }}">
<svg aria-hidden="true" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false"> <svg aria-hidden="true" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false">
<circle cx="11" cy="11" r="8"></circle> <circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line> <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg> </svg>
</button> </button>
<div class="blog-search-panel" data-blog-search-panel hidden> <div class="blog-search-panel" data-blog-search-panel hidden>
<div id="blog-search" data-blog-search-root data-search-placeholder="{{ 'render.search.placeholder' | i18n: language }}"></div> <div id="blog-search" data-blog-search-root data-search-placeholder="{{ labels.search_placeholder }}"></div>
</div> </div>
</div> </div>
</nav> </nav>
@@ -27,15 +27,15 @@
}()); }());
</script> </script>
{% else %} {% else %}
<div class="blog-search-standalone" aria-label="{{ 'render.search.ariaLabel' | i18n: language }}"> <div class="blog-search-standalone" aria-label="{{ labels.site_search_label }}">
<button type="button" class="blog-search-toggle" data-blog-search-toggle aria-label="{{ 'render.search.ariaLabel' | i18n: language }}"> <button type="button" class="blog-search-toggle" data-blog-search-toggle aria-label="{{ labels.site_search_label }}">
<svg aria-hidden="true" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false"> <svg aria-hidden="true" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false">
<circle cx="11" cy="11" r="8"></circle> <circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line> <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg> </svg>
</button> </button>
<div class="blog-search-panel" data-blog-search-panel hidden> <div class="blog-search-panel" data-blog-search-panel hidden>
<div id="blog-search" data-blog-search-root data-search-placeholder="{{ 'render.search.placeholder' | i18n: language }}"></div> <div id="blog-search" data-blog-search-root data-search-placeholder="{{ labels.search_placeholder }}"></div>
</div> </div>
</div> </div>
{% endif %} {% endif %}

View File

@@ -22,8 +22,8 @@
data-blog-calendar-toggle data-blog-calendar-toggle
{% if calendar_initial_year %}data-blog-calendar-year="{{ calendar_initial_year }}"{% endif %} {% if calendar_initial_year %}data-blog-calendar-year="{{ calendar_initial_year }}"{% endif %}
{% if calendar_initial_month %}data-blog-calendar-month="{{ calendar_initial_month }}"{% endif %} {% if calendar_initial_month %}data-blog-calendar-month="{{ calendar_initial_month }}"{% endif %}
aria-label="{{ 'render.calendar.open' | i18n: language }}" aria-label="{{ labels.calendar_open_label }}"
title="{{ 'render.calendar.open' | i18n: language }}" title="{{ labels.calendar_open_label }}"
> >
<svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" focusable="false"> <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" focusable="false">
<rect x="3" y="5" width="18" height="16" rx="2" ry="2"></rect> <rect x="3" y="5" width="18" height="16" rx="2" ry="2"></rect>
@@ -37,25 +37,25 @@
id="blog-calendar" id="blog-calendar"
class="blog-calendar-panel" class="blog-calendar-panel"
data-blog-calendar-panel data-blog-calendar-panel
data-i18n-loading="{{ 'render.calendar.loading' | i18n: language }}" data-i18n-loading="{{ labels.calendar_loading_label }}"
data-i18n-error="{{ 'render.calendar.error' | i18n: language }}" data-i18n-error="{{ labels.calendar_error_label }}"
hidden hidden
> >
<header class="blog-calendar-header"> <header class="blog-calendar-header">
<strong>{{ 'render.calendar.title' | i18n: language }}</strong> <strong>{{ labels.calendar_title_label }}</strong>
<button <button
type="button" type="button"
class="blog-calendar-close" class="blog-calendar-close"
data-blog-calendar-close data-blog-calendar-close
aria-label="{{ 'render.calendar.close' | i18n: language }}" aria-label="{{ labels.calendar_close_label }}"
title="{{ 'render.calendar.close' | i18n: language }}" title="{{ labels.calendar_close_label }}"
> >
× ×
</button> </button>
</header> </header>
<div class="blog-calendar-content"> <div class="blog-calendar-content">
<div data-blog-calendar-root></div> <div data-blog-calendar-root></div>
<p class="blog-calendar-status" data-blog-calendar-status>{{ 'render.calendar.loading' | i18n: language }}</p> <p class="blog-calendar-status" data-blog-calendar-status>{{ labels.calendar_loading_label }}</p>
</div> </div>
</section> </section>
</li> </li>

View File

@@ -1,7 +1,7 @@
<nav class="blog-menu"> <nav class="blog-menu">
{% if menu_items and menu_items.size > 0 %} {% if menu_items and menu_items.size > 0 %}
{% render 'partials/menu-items', items: menu_items, include_calendar: true, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month %} {% render 'partials/menu-items', items: menu_items, include_calendar: true, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month, labels: labels %}
{% else %} {% else %}
{% render 'partials/menu-items', items: menu_items, include_calendar: true, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month %} {% render 'partials/menu-items', items: menu_items, include_calendar: true, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month, labels: labels %}
{% endif %} {% endif %}
</nav> </nav>

View File

@@ -11,32 +11,30 @@ version: 1
{% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href, language_prefix: language_prefix %} {% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href, language_prefix: language_prefix %}
<body> <body>
<main> <main>
{% render 'partials/language-switcher', blog_languages: blog_languages, language: language %} {% render 'partials/language-switcher', blog_languages: blog_languages, language: language, labels: labels %}
{% if archive_context %} {% if archive_context %}
{% if show_archive_range_heading and min_date and max_date %} {% if show_archive_range_heading and min_date and max_date %}
{% if archive_context.kind == 'tag' or archive_context.kind == 'category' %} {% if archive_context.kind == 'tag' or archive_context.kind == 'category' %}
<h1 class="archive-heading">{{ archive_context.name }} - {{ min_date.day }}.{{ min_date.month }}.{{ min_date.year }} - {{ max_date.day }}.{{ max_date.month }}.{{ max_date.year }}</h1> <h1 class="archive-heading">{{ archive_context.name }} - {{ min_date.day }}.{{ min_date.month }}.{{ min_date.year }} - {{ max_date.day }}.{{ max_date.month }}.{{ max_date.year }}</h1>
{% else %} {% else %}
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ min_date.day }}.{{ min_date.month }}.{{ min_date.year }} - {{ max_date.day }}.{{ max_date.month }}.{{ max_date.year }}</h1> <h1 class="archive-heading">{{ labels.archive_label }} {{ min_date.day }}.{{ min_date.month }}.{{ min_date.year }} - {{ max_date.day }}.{{ max_date.month }}.{{ max_date.year }}</h1>
{% endif %} {% endif %}
{% else %} {% else %}
{% if archive_context.kind == 'tag' or archive_context.kind == 'category' %} {% if archive_context.kind == 'tag' or archive_context.kind == 'category' %}
<h1 class="archive-heading">{{ archive_context.name }}</h1> <h1 class="archive-heading">{{ archive_context.name }}</h1>
{% elsif archive_context.kind == 'month' and archive_context.month and archive_context.year %} {% elsif archive_context.kind == 'month' and archive_context.month and archive_context.year %}
{% assign month_key = 'render.month.' | append: archive_context.month %} <h1 class="archive-heading">{{ labels.archive_label }} {{ archive_month_name }} {{ archive_context.year }}</h1>
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ month_key | i18n: language }} {{ archive_context.year }}</h1>
{% elsif archive_context.kind == 'year' and archive_context.year %} {% elsif archive_context.kind == 'year' and archive_context.year %}
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ archive_context.year }}</h1> <h1 class="archive-heading">{{ labels.archive_label }} {{ archive_context.year }}</h1>
{% elsif archive_context.kind == 'day' and archive_context.day and archive_context.month and archive_context.year %} {% elsif archive_context.kind == 'day' and archive_context.day and archive_context.month and archive_context.year %}
{% assign day_month_key = 'render.month.' | append: archive_context.month %} <h1 class="archive-heading">{{ labels.archive_label }} {{ archive_context.day }}. {{ archive_month_name }} {{ archive_context.year }}</h1>
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ archive_context.day }}. {{ day_month_key | i18n: language }} {{ archive_context.year }}</h1>
{% else %} {% else %}
<h1 class="archive-heading">{{ page_title }}</h1> <h1 class="archive-heading">{{ page_title }}</h1>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% render 'partials/menu', menu_items: menu_items, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month %} {% render 'partials/menu', menu_items: menu_items, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month, labels: labels %}
<section class="post-list" data-template="post-list" data-list-page="{{ is_list_page }}" data-first-page="{{ is_first_page }}" data-last-page="{{ is_last_page }}"> <section class="post-list" data-template="post-list" data-list-page="{{ is_list_page }}" data-first-page="{{ is_first_page }}" data-last-page="{{ is_last_page }}">
{% for day_block in day_blocks %} {% for day_block in day_blocks %}
@@ -80,15 +78,15 @@ version: 1
</section> </section>
{% if has_prev_page or has_next_page %} {% if has_prev_page or has_next_page %}
<nav class="preview-pagination" aria-label="{{ 'render.pagination.label' | i18n: language }}"> <nav class="preview-pagination" aria-label="{{ labels.pagination_label }}">
{% if has_prev_page %} {% if has_prev_page %}
<a href="{{ prev_page_href }}" class="preview-pagination-link" aria-label="{{ 'render.pagination.newer' | i18n: language }}">{{ 'render.pagination.newer' | i18n: language }}</a> <a href="{{ prev_page_href }}" class="preview-pagination-link" aria-label="{{ labels.newer_label }}">{{ labels.newer_label }}</a>
{% else %} {% else %}
<span class="spacer"></span> <span class="spacer"></span>
{% endif %} {% endif %}
{% if has_next_page %} {% if has_next_page %}
<a href="{{ next_page_href }}" class="preview-pagination-link" aria-label="{{ 'render.pagination.older' | i18n: language }}">{{ 'render.pagination.older' | i18n: language }}</a> <a href="{{ next_page_href }}" class="preview-pagination-link" aria-label="{{ labels.older_label }}">{{ labels.older_label }}</a>
{% else %} {% else %}
<span class="spacer"></span> <span class="spacer"></span>
{% endif %} {% endif %}

View File

@@ -11,11 +11,11 @@ version: 1
{% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href, alternate_links: alternate_links, language_prefix: language_prefix %} {% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href, alternate_links: alternate_links, language_prefix: language_prefix %}
<body> <body>
<main> <main>
{% render 'partials/language-switcher', blog_languages: blog_languages, language: language %} {% render 'partials/language-switcher', blog_languages: blog_languages, language: language, labels: labels %}
<h1>{{ post.title }}</h1> <h1>{{ post.title }}</h1>
{% render 'partials/menu', menu_items: menu_items, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month %} {% render 'partials/menu', menu_items: menu_items, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month, labels: labels %}
{% if post_categories.size > 0 or post_tags.size > 0 %} {% if post_categories.size > 0 or post_tags.size > 0 %}
<div class="single-post-taxonomy" aria-label="{{ 'render.taxonomy.ariaLabel' | i18n: language }}"> <div class="single-post-taxonomy" aria-label="{{ labels.taxonomy_label }}">
{% for category in post_categories %} {% for category in post_categories %}
<a class="single-post-taxonomy-bubble single-post-taxonomy-bubble-category" href="/category/{{ category | slugify | url_encode }}/">{{ category | escape }}</a> <a class="single-post-taxonomy-bubble single-post-taxonomy-bubble-category" href="/category/{{ category | slugify | url_encode }}/">{{ category | escape }}</a>
{% endfor %} {% endfor %}
@@ -29,8 +29,8 @@ version: 1
<div class="post">{{ post.content }}</div> <div class="post">{{ post.content }}</div>
</article> </article>
{% if backlinks.size > 0 %} {% if backlinks.size > 0 %}
<div class="single-post-backlinks" aria-label="{{ 'render.backlinks.ariaLabel' | i18n: language }}"> <div class="single-post-backlinks" aria-label="{{ labels.backlinks_label }}">
<span class="single-post-backlinks-label">{{ 'render.backlinks.label' | i18n: language }}</span> <span class="single-post-backlinks-label">{{ labels.linked_from_label }}</span>
{% for backlink in backlinks %} {% for backlink in backlinks %}
<a class="single-post-taxonomy-bubble single-post-backlink-bubble" href="{{ backlink.path }}">{{ backlink.display_slug }}</a> <a class="single-post-taxonomy-bubble single-post-backlink-bubble" href="{{ backlink.path }}">{{ backlink.display_slug }}</a>
{% endfor %} {% endfor %}

View File

@@ -0,0 +1,12 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -0,0 +1,156 @@
#: lib/bds/rendering/labels.ex:17
#: lib/bds/ui/sidebar.ex:241
#: lib/bds/ui/sidebar.ex:316
#, elixir-autogen, elixir-format
msgid "Archive"
msgstr "Archiv"
#: lib/bds/rendering/labels.ex:52
#, elixir-autogen, elixir-format
msgid "April"
msgstr "Apr."
#: lib/bds/rendering/labels.ex:24
#, elixir-autogen, elixir-format, fuzzy
msgid "Archive calendar"
msgstr "Archiv"
#: lib/bds/rendering/labels.ex:68
#, elixir-autogen, elixir-format
msgid "August"
msgstr "Aug."
#: lib/bds/rendering/labels.ex:15
#, elixir-autogen, elixir-format
msgid "Backlinks"
msgstr "Rückverweise"
#: lib/bds/rendering/labels.ex:23
#, elixir-autogen, elixir-format
msgid "Calendar data could not be loaded."
msgstr "Kalenderdaten konnten nicht geladen werden."
#: lib/bds/rendering/labels.ex:25
#, elixir-autogen, elixir-format
msgid "Close calendar"
msgstr "Kalender schließen"
#: lib/bds/rendering/labels.ex:84
#, elixir-autogen, elixir-format
msgid "December"
msgstr "Dezember"
#: lib/bds/rendering/labels.ex:44
#, elixir-autogen, elixir-format
msgid "February"
msgstr "Februar"
#: lib/bds/rendering/labels.ex:40
#, elixir-autogen, elixir-format
msgid "January"
msgstr "Januar"
#: lib/bds/rendering/labels.ex:64
#, elixir-autogen, elixir-format
msgid "July"
msgstr "Juli"
#: lib/bds/rendering/labels.ex:60
#, elixir-autogen, elixir-format
msgid "June"
msgstr "Juni"
#: lib/bds/rendering/labels.ex:26
#, elixir-autogen, elixir-format
msgid "Language"
msgstr "Sprache"
#: lib/bds/rendering/labels.ex:16
#, elixir-autogen, elixir-format
msgid "Linked from"
msgstr "Verlinkt von"
#: lib/bds/rendering/labels.ex:22
#, elixir-autogen, elixir-format
msgid "Loading calendar…"
msgstr "Kalender wird geladen …"
#: lib/bds/rendering/labels.ex:48
#, elixir-autogen, elixir-format
msgid "March"
msgstr "März"
#: lib/bds/rendering/labels.ex:56
#, elixir-autogen, elixir-format
msgid "May"
msgstr "Mai"
#: lib/bds/rendering/labels.ex:80
#, elixir-autogen, elixir-format
msgid "November"
msgstr "Nov."
#: lib/bds/rendering/labels.ex:76
#, elixir-autogen, elixir-format
msgid "October"
msgstr "Oktober"
#: lib/bds/rendering/labels.ex:21
#, elixir-autogen, elixir-format
msgid "Open calendar"
msgstr "Kalender öffnen"
#: lib/bds/rendering/labels.ex:18
#, elixir-autogen, elixir-format
msgid "Pagination"
msgstr "Seitennummerierung"
#: lib/bds/rendering/labels.ex:28
#, elixir-autogen, elixir-format
msgid "Search..."
msgstr "Suchen..."
#: lib/bds/rendering/labels.ex:72
#, elixir-autogen, elixir-format
msgid "September"
msgstr "Sept."
#: lib/bds/rendering/labels.ex:27
#, elixir-autogen, elixir-format
msgid "Site search"
msgstr "Seitensuche"
#: lib/bds/rendering/labels.ex:14
#, elixir-autogen, elixir-format
msgid "Taxonomy"
msgstr "Taxonomie"
#: lib/bds/rendering/labels.ex:19
#, elixir-autogen, elixir-format
msgid "newer"
msgstr "neuer"
#: lib/bds/rendering/labels.ex:20
#, elixir-autogen, elixir-format
msgid "older"
msgstr "älter"
#: lib/bds/rendering/labels.ex:30
#, elixir-autogen, elixir-format
msgid "Back to preview home"
msgstr "Zurück zur Vorschau-Startseite"
#: lib/bds/rendering/labels.ex:29
#, elixir-autogen, elixir-format
msgid "The requested preview page could not be found."
msgstr "Die angeforderte Vorschauseite konnte nicht gefunden werden."
#: lib/bds/rendering/labels.ex:32
#, elixir-autogen, elixir-format
msgid "Vimeo video"
msgstr "Vimeo-Video"
#: lib/bds/rendering/labels.ex:31
#, elixir-autogen, elixir-format
msgid "YouTube video"
msgstr "YouTube-Video"

File diff suppressed because it is too large Load Diff

12
priv/gettext/default.pot Normal file
View File

@@ -0,0 +1,12 @@
## This file is a PO Template file.
##
## "msgid"s here are often extracted from source code.
## Add new messages manually only if they're dynamic
## messages that can't be statically extracted.
##
## Run "mix gettext.extract" to bring this file up to
## date. Leave "msgstr"s empty as changing them here has no
## effect: edit them in PO (.po) files instead.
#
msgid ""
msgstr ""

View File

@@ -0,0 +1,12 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -0,0 +1,156 @@
#: lib/bds/rendering/labels.ex:17
#: lib/bds/ui/sidebar.ex:241
#: lib/bds/ui/sidebar.ex:316
#, elixir-autogen, elixir-format
msgid "Archive"
msgstr ""
#: lib/bds/rendering/labels.ex:52
#, elixir-autogen, elixir-format
msgid "April"
msgstr ""
#: lib/bds/rendering/labels.ex:24
#, elixir-autogen, elixir-format, fuzzy
msgid "Archive calendar"
msgstr ""
#: lib/bds/rendering/labels.ex:68
#, elixir-autogen, elixir-format
msgid "August"
msgstr ""
#: lib/bds/rendering/labels.ex:15
#, elixir-autogen, elixir-format
msgid "Backlinks"
msgstr ""
#: lib/bds/rendering/labels.ex:23
#, elixir-autogen, elixir-format
msgid "Calendar data could not be loaded."
msgstr ""
#: lib/bds/rendering/labels.ex:25
#, elixir-autogen, elixir-format
msgid "Close calendar"
msgstr ""
#: lib/bds/rendering/labels.ex:84
#, elixir-autogen, elixir-format
msgid "December"
msgstr ""
#: lib/bds/rendering/labels.ex:44
#, elixir-autogen, elixir-format
msgid "February"
msgstr ""
#: lib/bds/rendering/labels.ex:40
#, elixir-autogen, elixir-format
msgid "January"
msgstr ""
#: lib/bds/rendering/labels.ex:64
#, elixir-autogen, elixir-format
msgid "July"
msgstr ""
#: lib/bds/rendering/labels.ex:60
#, elixir-autogen, elixir-format
msgid "June"
msgstr ""
#: lib/bds/rendering/labels.ex:26
#, elixir-autogen, elixir-format
msgid "Language"
msgstr ""
#: lib/bds/rendering/labels.ex:16
#, elixir-autogen, elixir-format
msgid "Linked from"
msgstr ""
#: lib/bds/rendering/labels.ex:22
#, elixir-autogen, elixir-format
msgid "Loading calendar…"
msgstr ""
#: lib/bds/rendering/labels.ex:48
#, elixir-autogen, elixir-format
msgid "March"
msgstr ""
#: lib/bds/rendering/labels.ex:56
#, elixir-autogen, elixir-format
msgid "May"
msgstr ""
#: lib/bds/rendering/labels.ex:80
#, elixir-autogen, elixir-format
msgid "November"
msgstr ""
#: lib/bds/rendering/labels.ex:76
#, elixir-autogen, elixir-format
msgid "October"
msgstr ""
#: lib/bds/rendering/labels.ex:21
#, elixir-autogen, elixir-format
msgid "Open calendar"
msgstr ""
#: lib/bds/rendering/labels.ex:18
#, elixir-autogen, elixir-format
msgid "Pagination"
msgstr ""
#: lib/bds/rendering/labels.ex:28
#, elixir-autogen, elixir-format
msgid "Search..."
msgstr ""
#: lib/bds/rendering/labels.ex:72
#, elixir-autogen, elixir-format
msgid "September"
msgstr ""
#: lib/bds/rendering/labels.ex:27
#, elixir-autogen, elixir-format
msgid "Site search"
msgstr ""
#: lib/bds/rendering/labels.ex:14
#, elixir-autogen, elixir-format
msgid "Taxonomy"
msgstr ""
#: lib/bds/rendering/labels.ex:19
#, elixir-autogen, elixir-format
msgid "newer"
msgstr ""
#: lib/bds/rendering/labels.ex:20
#, elixir-autogen, elixir-format
msgid "older"
msgstr ""
#: lib/bds/rendering/labels.ex:30
#, elixir-autogen, elixir-format
msgid "Back to preview home"
msgstr ""
#: lib/bds/rendering/labels.ex:29
#, elixir-autogen, elixir-format
msgid "The requested preview page could not be found."
msgstr ""
#: lib/bds/rendering/labels.ex:32
#, elixir-autogen, elixir-format
msgid "Vimeo video"
msgstr ""
#: lib/bds/rendering/labels.ex:31
#, elixir-autogen, elixir-format
msgid "YouTube video"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -0,0 +1,156 @@
#: lib/bds/rendering/labels.ex:17
#: lib/bds/ui/sidebar.ex:241
#: lib/bds/ui/sidebar.ex:316
#, elixir-autogen, elixir-format
msgid "Archive"
msgstr "Archivo"
#: lib/bds/rendering/labels.ex:52
#, elixir-autogen, elixir-format
msgid "April"
msgstr "abril"
#: lib/bds/rendering/labels.ex:24
#, elixir-autogen, elixir-format, fuzzy
msgid "Archive calendar"
msgstr "Archivo"
#: lib/bds/rendering/labels.ex:68
#, elixir-autogen, elixir-format
msgid "August"
msgstr "agosto"
#: lib/bds/rendering/labels.ex:15
#, elixir-autogen, elixir-format
msgid "Backlinks"
msgstr "Retroenlaces"
#: lib/bds/rendering/labels.ex:23
#, elixir-autogen, elixir-format
msgid "Calendar data could not be loaded."
msgstr "No se pudieron cargar los datos del calendario."
#: lib/bds/rendering/labels.ex:25
#, elixir-autogen, elixir-format
msgid "Close calendar"
msgstr "Cerrar calendario"
#: lib/bds/rendering/labels.ex:84
#, elixir-autogen, elixir-format
msgid "December"
msgstr "diciembre"
#: lib/bds/rendering/labels.ex:44
#, elixir-autogen, elixir-format
msgid "February"
msgstr "febrero"
#: lib/bds/rendering/labels.ex:40
#, elixir-autogen, elixir-format
msgid "January"
msgstr "enero"
#: lib/bds/rendering/labels.ex:64
#, elixir-autogen, elixir-format
msgid "July"
msgstr "julio"
#: lib/bds/rendering/labels.ex:60
#, elixir-autogen, elixir-format
msgid "June"
msgstr "junio"
#: lib/bds/rendering/labels.ex:26
#, elixir-autogen, elixir-format
msgid "Language"
msgstr "Idioma"
#: lib/bds/rendering/labels.ex:16
#, elixir-autogen, elixir-format
msgid "Linked from"
msgstr "Enlazado desde"
#: lib/bds/rendering/labels.ex:22
#, elixir-autogen, elixir-format
msgid "Loading calendar…"
msgstr "Cargando calendario…"
#: lib/bds/rendering/labels.ex:48
#, elixir-autogen, elixir-format
msgid "March"
msgstr "marzo"
#: lib/bds/rendering/labels.ex:56
#, elixir-autogen, elixir-format
msgid "May"
msgstr "mayo"
#: lib/bds/rendering/labels.ex:80
#, elixir-autogen, elixir-format
msgid "November"
msgstr "noviembre"
#: lib/bds/rendering/labels.ex:76
#, elixir-autogen, elixir-format
msgid "October"
msgstr "octubre"
#: lib/bds/rendering/labels.ex:21
#, elixir-autogen, elixir-format
msgid "Open calendar"
msgstr "Abrir calendario"
#: lib/bds/rendering/labels.ex:18
#, elixir-autogen, elixir-format
msgid "Pagination"
msgstr "Paginación"
#: lib/bds/rendering/labels.ex:28
#, elixir-autogen, elixir-format
msgid "Search..."
msgstr "Buscar..."
#: lib/bds/rendering/labels.ex:72
#, elixir-autogen, elixir-format
msgid "September"
msgstr "septiembre"
#: lib/bds/rendering/labels.ex:27
#, elixir-autogen, elixir-format
msgid "Site search"
msgstr "Buscar en el sitio"
#: lib/bds/rendering/labels.ex:14
#, elixir-autogen, elixir-format
msgid "Taxonomy"
msgstr "Taxonomía"
#: lib/bds/rendering/labels.ex:19
#, elixir-autogen, elixir-format
msgid "newer"
msgstr "más reciente"
#: lib/bds/rendering/labels.ex:20
#, elixir-autogen, elixir-format
msgid "older"
msgstr "más antiguo"
#: lib/bds/rendering/labels.ex:30
#, elixir-autogen, elixir-format
msgid "Back to preview home"
msgstr "Volver al inicio de vista previa"
#: lib/bds/rendering/labels.ex:29
#, elixir-autogen, elixir-format
msgid "The requested preview page could not be found."
msgstr "No se pudo encontrar la página de vista previa solicitada."
#: lib/bds/rendering/labels.ex:32
#, elixir-autogen, elixir-format
msgid "Vimeo video"
msgstr "Vídeo de Vimeo"
#: lib/bds/rendering/labels.ex:31
#, elixir-autogen, elixir-format
msgid "YouTube video"
msgstr "Vídeo de YouTube"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n>1);\n"

View File

@@ -0,0 +1,156 @@
#: lib/bds/rendering/labels.ex:17
#: lib/bds/ui/sidebar.ex:241
#: lib/bds/ui/sidebar.ex:316
#, elixir-autogen, elixir-format
msgid "Archive"
msgstr "Archives"
#: lib/bds/rendering/labels.ex:52
#, elixir-autogen, elixir-format
msgid "April"
msgstr "avril"
#: lib/bds/rendering/labels.ex:24
#, elixir-autogen, elixir-format, fuzzy
msgid "Archive calendar"
msgstr "Archives"
#: lib/bds/rendering/labels.ex:68
#, elixir-autogen, elixir-format
msgid "August"
msgstr "août"
#: lib/bds/rendering/labels.ex:15
#, elixir-autogen, elixir-format
msgid "Backlinks"
msgstr "Rétroliens"
#: lib/bds/rendering/labels.ex:23
#, elixir-autogen, elixir-format
msgid "Calendar data could not be loaded."
msgstr "Impossible de charger les données du calendrier."
#: lib/bds/rendering/labels.ex:25
#, elixir-autogen, elixir-format
msgid "Close calendar"
msgstr "Fermer le calendrier"
#: lib/bds/rendering/labels.ex:84
#, elixir-autogen, elixir-format
msgid "December"
msgstr "décembre"
#: lib/bds/rendering/labels.ex:44
#, elixir-autogen, elixir-format
msgid "February"
msgstr "février"
#: lib/bds/rendering/labels.ex:40
#, elixir-autogen, elixir-format
msgid "January"
msgstr "janvier"
#: lib/bds/rendering/labels.ex:64
#, elixir-autogen, elixir-format
msgid "July"
msgstr "juillet"
#: lib/bds/rendering/labels.ex:60
#, elixir-autogen, elixir-format
msgid "June"
msgstr "juin"
#: lib/bds/rendering/labels.ex:26
#, elixir-autogen, elixir-format
msgid "Language"
msgstr "Langue"
#: lib/bds/rendering/labels.ex:16
#, elixir-autogen, elixir-format
msgid "Linked from"
msgstr "Lié depuis"
#: lib/bds/rendering/labels.ex:22
#, elixir-autogen, elixir-format
msgid "Loading calendar…"
msgstr "Chargement du calendrier…"
#: lib/bds/rendering/labels.ex:48
#, elixir-autogen, elixir-format
msgid "March"
msgstr "mars"
#: lib/bds/rendering/labels.ex:56
#, elixir-autogen, elixir-format
msgid "May"
msgstr "mai"
#: lib/bds/rendering/labels.ex:80
#, elixir-autogen, elixir-format
msgid "November"
msgstr "novembre"
#: lib/bds/rendering/labels.ex:76
#, elixir-autogen, elixir-format
msgid "October"
msgstr "octobre"
#: lib/bds/rendering/labels.ex:21
#, elixir-autogen, elixir-format
msgid "Open calendar"
msgstr "Ouvrir le calendrier"
#: lib/bds/rendering/labels.ex:18
#, elixir-autogen, elixir-format
msgid "Pagination"
msgstr "Navigation paginée"
#: lib/bds/rendering/labels.ex:28
#, elixir-autogen, elixir-format
msgid "Search..."
msgstr "Rechercher..."
#: lib/bds/rendering/labels.ex:72
#, elixir-autogen, elixir-format
msgid "September"
msgstr "septembre"
#: lib/bds/rendering/labels.ex:27
#, elixir-autogen, elixir-format
msgid "Site search"
msgstr "Recherche du site"
#: lib/bds/rendering/labels.ex:14
#, elixir-autogen, elixir-format
msgid "Taxonomy"
msgstr "Taxonomie"
#: lib/bds/rendering/labels.ex:19
#, elixir-autogen, elixir-format
msgid "newer"
msgstr "plus récent"
#: lib/bds/rendering/labels.ex:20
#, elixir-autogen, elixir-format
msgid "older"
msgstr "plus ancien"
#: lib/bds/rendering/labels.ex:30
#, elixir-autogen, elixir-format
msgid "Back to preview home"
msgstr "Retour à laccueil de laperçu"
#: lib/bds/rendering/labels.ex:29
#, elixir-autogen, elixir-format
msgid "The requested preview page could not be found."
msgstr "La page daperçu demandée est introuvable."
#: lib/bds/rendering/labels.ex:32
#, elixir-autogen, elixir-format
msgid "Vimeo video"
msgstr "Vidéo Vimeo"
#: lib/bds/rendering/labels.ex:31
#, elixir-autogen, elixir-format
msgid "YouTube video"
msgstr "Vidéo YouTube"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
## "msgid"s in this file come from POT (.pot) files.
###
### Do not add, change, or remove "msgid"s manually here as
### they're tied to the ones in the corresponding POT file
### (with the same domain).
###
### Use "mix gettext.extract --merge" or "mix gettext.merge"
### to merge POT files into PO files.
msgid ""
msgstr ""
"Language: it\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

View File

@@ -0,0 +1,156 @@
#: lib/bds/rendering/labels.ex:17
#: lib/bds/ui/sidebar.ex:241
#: lib/bds/ui/sidebar.ex:316
#, elixir-autogen, elixir-format
msgid "Archive"
msgstr "Archivio"
#: lib/bds/rendering/labels.ex:52
#, elixir-autogen, elixir-format
msgid "April"
msgstr "aprile"
#: lib/bds/rendering/labels.ex:24
#, elixir-autogen, elixir-format, fuzzy
msgid "Archive calendar"
msgstr "Archivio"
#: lib/bds/rendering/labels.ex:68
#, elixir-autogen, elixir-format
msgid "August"
msgstr "agosto"
#: lib/bds/rendering/labels.ex:15
#, elixir-autogen, elixir-format
msgid "Backlinks"
msgstr "Retrocollegamenti"
#: lib/bds/rendering/labels.ex:23
#, elixir-autogen, elixir-format
msgid "Calendar data could not be loaded."
msgstr "Impossibile caricare i dati del calendario."
#: lib/bds/rendering/labels.ex:25
#, elixir-autogen, elixir-format
msgid "Close calendar"
msgstr "Chiudi calendario"
#: lib/bds/rendering/labels.ex:84
#, elixir-autogen, elixir-format
msgid "December"
msgstr "dicembre"
#: lib/bds/rendering/labels.ex:44
#, elixir-autogen, elixir-format
msgid "February"
msgstr "febbraio"
#: lib/bds/rendering/labels.ex:40
#, elixir-autogen, elixir-format
msgid "January"
msgstr "gennaio"
#: lib/bds/rendering/labels.ex:64
#, elixir-autogen, elixir-format
msgid "July"
msgstr "luglio"
#: lib/bds/rendering/labels.ex:60
#, elixir-autogen, elixir-format
msgid "June"
msgstr "giugno"
#: lib/bds/rendering/labels.ex:26
#, elixir-autogen, elixir-format
msgid "Language"
msgstr "Lingua"
#: lib/bds/rendering/labels.ex:16
#, elixir-autogen, elixir-format
msgid "Linked from"
msgstr "Collegato da"
#: lib/bds/rendering/labels.ex:22
#, elixir-autogen, elixir-format
msgid "Loading calendar…"
msgstr "Caricamento calendario…"
#: lib/bds/rendering/labels.ex:48
#, elixir-autogen, elixir-format
msgid "March"
msgstr "marzo"
#: lib/bds/rendering/labels.ex:56
#, elixir-autogen, elixir-format
msgid "May"
msgstr "maggio"
#: lib/bds/rendering/labels.ex:80
#, elixir-autogen, elixir-format
msgid "November"
msgstr "novembre"
#: lib/bds/rendering/labels.ex:76
#, elixir-autogen, elixir-format
msgid "October"
msgstr "ottobre"
#: lib/bds/rendering/labels.ex:21
#, elixir-autogen, elixir-format
msgid "Open calendar"
msgstr "Apri calendario"
#: lib/bds/rendering/labels.ex:18
#, elixir-autogen, elixir-format
msgid "Pagination"
msgstr "Paginazione"
#: lib/bds/rendering/labels.ex:28
#, elixir-autogen, elixir-format
msgid "Search..."
msgstr "Cerca..."
#: lib/bds/rendering/labels.ex:72
#, elixir-autogen, elixir-format
msgid "September"
msgstr "settembre"
#: lib/bds/rendering/labels.ex:27
#, elixir-autogen, elixir-format
msgid "Site search"
msgstr "Ricerca nel sito"
#: lib/bds/rendering/labels.ex:14
#, elixir-autogen, elixir-format
msgid "Taxonomy"
msgstr "Tassonomia"
#: lib/bds/rendering/labels.ex:19
#, elixir-autogen, elixir-format
msgid "newer"
msgstr "più recente"
#: lib/bds/rendering/labels.ex:20
#, elixir-autogen, elixir-format
msgid "older"
msgstr "più vecchio"
#: lib/bds/rendering/labels.ex:30
#, elixir-autogen, elixir-format
msgid "Back to preview home"
msgstr "Torna alla home di anteprima"
#: lib/bds/rendering/labels.ex:29
#, elixir-autogen, elixir-format
msgid "The requested preview page could not be found."
msgstr "La pagina di anteprima richiesta non è stata trovata."
#: lib/bds/rendering/labels.ex:32
#, elixir-autogen, elixir-format
msgid "Vimeo video"
msgstr "Video Vimeo"
#: lib/bds/rendering/labels.ex:31
#, elixir-autogen, elixir-format
msgid "YouTube video"
msgstr "Video YouTube"

File diff suppressed because it is too large Load Diff

169
priv/gettext/render.pot Normal file
View File

@@ -0,0 +1,169 @@
## This file is a PO Template file.
##
## "msgid"s here are often extracted from source code.
## Add new messages manually only if they're dynamic
## messages that can't be statically extracted.
##
## Run "mix gettext.extract" to bring this file up to
## date. Leave "msgstr"s empty as changing them here has no
## effect: edit them in PO (.po) files instead.
#
msgid ""
msgstr ""
#: lib/bds/rendering/labels.ex:17
#: lib/bds/ui/sidebar.ex:241
#: lib/bds/ui/sidebar.ex:316
#, elixir-autogen, elixir-format
msgid "Archive"
msgstr ""
#: lib/bds/rendering/labels.ex:52
#, elixir-autogen, elixir-format
msgid "April"
msgstr ""
#: lib/bds/rendering/labels.ex:24
#, elixir-autogen, elixir-format
msgid "Archive calendar"
msgstr ""
#: lib/bds/rendering/labels.ex:68
#, elixir-autogen, elixir-format
msgid "August"
msgstr ""
#: lib/bds/rendering/labels.ex:15
#, elixir-autogen, elixir-format
msgid "Backlinks"
msgstr ""
#: lib/bds/rendering/labels.ex:23
#, elixir-autogen, elixir-format
msgid "Calendar data could not be loaded."
msgstr ""
#: lib/bds/rendering/labels.ex:25
#, elixir-autogen, elixir-format
msgid "Close calendar"
msgstr ""
#: lib/bds/rendering/labels.ex:84
#, elixir-autogen, elixir-format
msgid "December"
msgstr ""
#: lib/bds/rendering/labels.ex:44
#, elixir-autogen, elixir-format
msgid "February"
msgstr ""
#: lib/bds/rendering/labels.ex:40
#, elixir-autogen, elixir-format
msgid "January"
msgstr ""
#: lib/bds/rendering/labels.ex:64
#, elixir-autogen, elixir-format
msgid "July"
msgstr ""
#: lib/bds/rendering/labels.ex:60
#, elixir-autogen, elixir-format
msgid "June"
msgstr ""
#: lib/bds/rendering/labels.ex:26
#, elixir-autogen, elixir-format
msgid "Language"
msgstr ""
#: lib/bds/rendering/labels.ex:16
#, elixir-autogen, elixir-format
msgid "Linked from"
msgstr ""
#: lib/bds/rendering/labels.ex:22
#, elixir-autogen, elixir-format
msgid "Loading calendar…"
msgstr ""
#: lib/bds/rendering/labels.ex:48
#, elixir-autogen, elixir-format
msgid "March"
msgstr ""
#: lib/bds/rendering/labels.ex:56
#, elixir-autogen, elixir-format
msgid "May"
msgstr ""
#: lib/bds/rendering/labels.ex:80
#, elixir-autogen, elixir-format
msgid "November"
msgstr ""
#: lib/bds/rendering/labels.ex:76
#, elixir-autogen, elixir-format
msgid "October"
msgstr ""
#: lib/bds/rendering/labels.ex:21
#, elixir-autogen, elixir-format
msgid "Open calendar"
msgstr ""
#: lib/bds/rendering/labels.ex:18
#, elixir-autogen, elixir-format
msgid "Pagination"
msgstr ""
#: lib/bds/rendering/labels.ex:28
#, elixir-autogen, elixir-format
msgid "Search..."
msgstr ""
#: lib/bds/rendering/labels.ex:72
#, elixir-autogen, elixir-format
msgid "September"
msgstr ""
#: lib/bds/rendering/labels.ex:27
#, elixir-autogen, elixir-format
msgid "Site search"
msgstr ""
#: lib/bds/rendering/labels.ex:14
#, elixir-autogen, elixir-format
msgid "Taxonomy"
msgstr ""
#: lib/bds/rendering/labels.ex:19
#, elixir-autogen, elixir-format
msgid "newer"
msgstr ""
#: lib/bds/rendering/labels.ex:20
#, elixir-autogen, elixir-format
msgid "older"
msgstr ""
#: lib/bds/rendering/labels.ex:30
#, elixir-autogen, elixir-format
msgid "Back to preview home"
msgstr ""
#: lib/bds/rendering/labels.ex:29
#, elixir-autogen, elixir-format
msgid "The requested preview page could not be found."
msgstr ""
#: lib/bds/rendering/labels.ex:32
#, elixir-autogen, elixir-format
msgid "Vimeo video"
msgstr ""
#: lib/bds/rendering/labels.ex:31
#, elixir-autogen, elixir-format
msgid "YouTube video"
msgstr ""

3184
priv/gettext/ui.pot Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,8 @@
<section class="not-found" data-template="not-found"> <section class="not-found" data-template="not-found">
<article> <article>
<h1>404</h1> <h1>404</h1>
{% assign default_not_found_message = 'render.notFound.message' | i18n: language %} <p>{{ not_found_message }}</p>
{% assign default_not_found_back = 'render.notFound.back' | i18n: language %} <p><a href="/" role="button">{{ not_found_back_label }}</a></p>
<p>{{ not_found_message | default: default_not_found_message }}</p>
<p><a href="/" role="button">{{ not_found_back_label | default: default_not_found_back }}</a></p>
</article> </article>
</section> </section>
</main> </main>

View File

@@ -1,5 +1,5 @@
{% if blog_languages.size > 1 %} {% if blog_languages.size > 1 %}
<nav class="language-switcher" aria-label="{{ 'render.languageSwitcher.ariaLabel' | i18n: language }}"> <nav class="language-switcher" aria-label="{{ labels.language_switcher_label }}">
{% for lang in blog_languages %} {% for lang in blog_languages %}
{% if lang.is_current %} {% if lang.is_current %}
<span class="language-switcher-badge language-switcher-badge-current" aria-current="true" title="{{ lang.code }}">{{ lang.flag }}</span> <span class="language-switcher-badge language-switcher-badge-current" aria-current="true" title="{{ lang.code }}">{{ lang.flag }}</span>
@@ -7,15 +7,15 @@
<a class="language-switcher-badge" href="{{ lang.href_prefix | default: '/' }}" data-lang-prefix="{{ lang.href_prefix }}" title="{{ lang.code }}">{{ lang.flag }}</a> <a class="language-switcher-badge" href="{{ lang.href_prefix | default: '/' }}" data-lang-prefix="{{ lang.href_prefix }}" title="{{ lang.code }}">{{ lang.flag }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<div class="blog-search-widget" aria-label="{{ 'render.search.ariaLabel' | i18n: language }}"> <div class="blog-search-widget" aria-label="{{ labels.site_search_label }}">
<button type="button" class="blog-search-toggle" data-blog-search-toggle aria-label="{{ 'render.search.ariaLabel' | i18n: language }}"> <button type="button" class="blog-search-toggle" data-blog-search-toggle aria-label="{{ labels.site_search_label }}">
<svg aria-hidden="true" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false"> <svg aria-hidden="true" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false">
<circle cx="11" cy="11" r="8"></circle> <circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line> <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg> </svg>
</button> </button>
<div class="blog-search-panel" data-blog-search-panel hidden> <div class="blog-search-panel" data-blog-search-panel hidden>
<div id="blog-search" data-blog-search-root data-search-placeholder="{{ 'render.search.placeholder' | i18n: language }}"></div> <div id="blog-search" data-blog-search-root data-search-placeholder="{{ labels.search_placeholder }}"></div>
</div> </div>
</div> </div>
</nav> </nav>
@@ -27,15 +27,15 @@
}()); }());
</script> </script>
{% else %} {% else %}
<div class="blog-search-standalone" aria-label="{{ 'render.search.ariaLabel' | i18n: language }}"> <div class="blog-search-standalone" aria-label="{{ labels.site_search_label }}">
<button type="button" class="blog-search-toggle" data-blog-search-toggle aria-label="{{ 'render.search.ariaLabel' | i18n: language }}"> <button type="button" class="blog-search-toggle" data-blog-search-toggle aria-label="{{ labels.site_search_label }}">
<svg aria-hidden="true" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false"> <svg aria-hidden="true" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" focusable="false">
<circle cx="11" cy="11" r="8"></circle> <circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line> <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg> </svg>
</button> </button>
<div class="blog-search-panel" data-blog-search-panel hidden> <div class="blog-search-panel" data-blog-search-panel hidden>
<div id="blog-search" data-blog-search-root data-search-placeholder="{{ 'render.search.placeholder' | i18n: language }}"></div> <div id="blog-search" data-blog-search-root data-search-placeholder="{{ labels.search_placeholder }}"></div>
</div> </div>
</div> </div>
{% endif %} {% endif %}

View File

@@ -22,8 +22,8 @@
data-blog-calendar-toggle data-blog-calendar-toggle
{% if calendar_initial_year %}data-blog-calendar-year="{{ calendar_initial_year }}"{% endif %} {% if calendar_initial_year %}data-blog-calendar-year="{{ calendar_initial_year }}"{% endif %}
{% if calendar_initial_month %}data-blog-calendar-month="{{ calendar_initial_month }}"{% endif %} {% if calendar_initial_month %}data-blog-calendar-month="{{ calendar_initial_month }}"{% endif %}
aria-label="{{ 'render.calendar.open' | i18n: language }}" aria-label="{{ labels.calendar_open_label }}"
title="{{ 'render.calendar.open' | i18n: language }}" title="{{ labels.calendar_open_label }}"
> >
<svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" focusable="false"> <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" focusable="false">
<rect x="3" y="5" width="18" height="16" rx="2" ry="2"></rect> <rect x="3" y="5" width="18" height="16" rx="2" ry="2"></rect>
@@ -37,25 +37,25 @@
id="blog-calendar" id="blog-calendar"
class="blog-calendar-panel" class="blog-calendar-panel"
data-blog-calendar-panel data-blog-calendar-panel
data-i18n-loading="{{ 'render.calendar.loading' | i18n: language }}" data-i18n-loading="{{ labels.calendar_loading_label }}"
data-i18n-error="{{ 'render.calendar.error' | i18n: language }}" data-i18n-error="{{ labels.calendar_error_label }}"
hidden hidden
> >
<header class="blog-calendar-header"> <header class="blog-calendar-header">
<strong>{{ 'render.calendar.title' | i18n: language }}</strong> <strong>{{ labels.calendar_title_label }}</strong>
<button <button
type="button" type="button"
class="blog-calendar-close" class="blog-calendar-close"
data-blog-calendar-close data-blog-calendar-close
aria-label="{{ 'render.calendar.close' | i18n: language }}" aria-label="{{ labels.calendar_close_label }}"
title="{{ 'render.calendar.close' | i18n: language }}" title="{{ labels.calendar_close_label }}"
> >
× ×
</button> </button>
</header> </header>
<div class="blog-calendar-content"> <div class="blog-calendar-content">
<div data-blog-calendar-root></div> <div data-blog-calendar-root></div>
<p class="blog-calendar-status" data-blog-calendar-status>{{ 'render.calendar.loading' | i18n: language }}</p> <p class="blog-calendar-status" data-blog-calendar-status>{{ labels.calendar_loading_label }}</p>
</div> </div>
</section> </section>
</li> </li>

View File

@@ -1,7 +1,7 @@
<nav class="blog-menu"> <nav class="blog-menu">
{% if menu_items and menu_items.size > 0 %} {% if menu_items and menu_items.size > 0 %}
{% render 'partials/menu-items', items: menu_items, include_calendar: true, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month %} {% render 'partials/menu-items', items: menu_items, include_calendar: true, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month, labels: labels %}
{% else %} {% else %}
{% render 'partials/menu-items', items: menu_items, include_calendar: true, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month %} {% render 'partials/menu-items', items: menu_items, include_calendar: true, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month, labels: labels %}
{% endif %} {% endif %}
</nav> </nav>

View File

@@ -3,32 +3,30 @@
{% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href, language_prefix: language_prefix %} {% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href, language_prefix: language_prefix %}
<body> <body>
<main> <main>
{% render 'partials/language-switcher', blog_languages: blog_languages, language: language %} {% render 'partials/language-switcher', blog_languages: blog_languages, language: language, labels: labels %}
{% if archive_context %} {% if archive_context %}
{% if show_archive_range_heading and min_date and max_date %} {% if show_archive_range_heading and min_date and max_date %}
{% if archive_context.kind == 'tag' or archive_context.kind == 'category' %} {% if archive_context.kind == 'tag' or archive_context.kind == 'category' %}
<h1 class="archive-heading">{{ archive_context.name }} - {{ min_date.day }}.{{ min_date.month }}.{{ min_date.year }} - {{ max_date.day }}.{{ max_date.month }}.{{ max_date.year }}</h1> <h1 class="archive-heading">{{ archive_context.name }} - {{ min_date.day }}.{{ min_date.month }}.{{ min_date.year }} - {{ max_date.day }}.{{ max_date.month }}.{{ max_date.year }}</h1>
{% else %} {% else %}
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ min_date.day }}.{{ min_date.month }}.{{ min_date.year }} - {{ max_date.day }}.{{ max_date.month }}.{{ max_date.year }}</h1> <h1 class="archive-heading">{{ labels.archive_label }} {{ min_date.day }}.{{ min_date.month }}.{{ min_date.year }} - {{ max_date.day }}.{{ max_date.month }}.{{ max_date.year }}</h1>
{% endif %} {% endif %}
{% else %} {% else %}
{% if archive_context.kind == 'tag' or archive_context.kind == 'category' %} {% if archive_context.kind == 'tag' or archive_context.kind == 'category' %}
<h1 class="archive-heading">{{ archive_context.name }}</h1> <h1 class="archive-heading">{{ archive_context.name }}</h1>
{% elsif archive_context.kind == 'month' and archive_context.month and archive_context.year %} {% elsif archive_context.kind == 'month' and archive_context.month and archive_context.year %}
{% assign month_key = 'render.month.' | append: archive_context.month %} <h1 class="archive-heading">{{ labels.archive_label }} {{ archive_month_name }} {{ archive_context.year }}</h1>
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ month_key | i18n: language }} {{ archive_context.year }}</h1>
{% elsif archive_context.kind == 'year' and archive_context.year %} {% elsif archive_context.kind == 'year' and archive_context.year %}
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ archive_context.year }}</h1> <h1 class="archive-heading">{{ labels.archive_label }} {{ archive_context.year }}</h1>
{% elsif archive_context.kind == 'day' and archive_context.day and archive_context.month and archive_context.year %} {% elsif archive_context.kind == 'day' and archive_context.day and archive_context.month and archive_context.year %}
{% assign day_month_key = 'render.month.' | append: archive_context.month %} <h1 class="archive-heading">{{ labels.archive_label }} {{ archive_context.day }}. {{ archive_month_name }} {{ archive_context.year }}</h1>
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ archive_context.day }}. {{ day_month_key | i18n: language }} {{ archive_context.year }}</h1>
{% else %} {% else %}
<h1 class="archive-heading">{{ page_title }}</h1> <h1 class="archive-heading">{{ page_title }}</h1>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% render 'partials/menu', menu_items: menu_items, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month %} {% render 'partials/menu', menu_items: menu_items, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month, labels: labels %}
<section class="post-list" data-template="post-list" data-list-page="{{ is_list_page }}" data-first-page="{{ is_first_page }}" data-last-page="{{ is_last_page }}"> <section class="post-list" data-template="post-list" data-list-page="{{ is_list_page }}" data-first-page="{{ is_first_page }}" data-last-page="{{ is_last_page }}">
{% for day_block in day_blocks %} {% for day_block in day_blocks %}
@@ -72,15 +70,15 @@
</section> </section>
{% if has_prev_page or has_next_page %} {% if has_prev_page or has_next_page %}
<nav class="preview-pagination" aria-label="{{ 'render.pagination.label' | i18n: language }}"> <nav class="preview-pagination" aria-label="{{ labels.pagination_label }}">
{% if has_prev_page %} {% if has_prev_page %}
<a href="{{ prev_page_href }}" class="preview-pagination-link" aria-label="{{ 'render.pagination.newer' | i18n: language }}">{{ 'render.pagination.newer' | i18n: language }}</a> <a href="{{ prev_page_href }}" class="preview-pagination-link" aria-label="{{ labels.newer_label }}">{{ labels.newer_label }}</a>
{% else %} {% else %}
<span class="spacer"></span> <span class="spacer"></span>
{% endif %} {% endif %}
{% if has_next_page %} {% if has_next_page %}
<a href="{{ next_page_href }}" class="preview-pagination-link" aria-label="{{ 'render.pagination.older' | i18n: language }}">{{ 'render.pagination.older' | i18n: language }}</a> <a href="{{ next_page_href }}" class="preview-pagination-link" aria-label="{{ labels.older_label }}">{{ labels.older_label }}</a>
{% else %} {% else %}
<span class="spacer"></span> <span class="spacer"></span>
{% endif %} {% endif %}

View File

@@ -3,11 +3,11 @@
{% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href, alternate_links: alternate_links, language_prefix: language_prefix %} {% render 'partials/head', page_title: page_title, pico_stylesheet_href: pico_stylesheet_href, alternate_links: alternate_links, language_prefix: language_prefix %}
<body> <body>
<main> <main>
{% render 'partials/language-switcher', blog_languages: blog_languages, language: language %} {% render 'partials/language-switcher', blog_languages: blog_languages, language: language, labels: labels %}
<h1>{{ post.title }}</h1> <h1>{{ post.title }}</h1>
{% render 'partials/menu', menu_items: menu_items, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month %} {% render 'partials/menu', menu_items: menu_items, language: language, calendar_initial_year: calendar_initial_year, calendar_initial_month: calendar_initial_month, labels: labels %}
{% if post_categories.size > 0 or post_tags.size > 0 %} {% if post_categories.size > 0 or post_tags.size > 0 %}
<div class="single-post-taxonomy" aria-label="{{ 'render.taxonomy.ariaLabel' | i18n: language }}"> <div class="single-post-taxonomy" aria-label="{{ labels.taxonomy_label }}">
{% for category in post_categories %} {% for category in post_categories %}
<a class="single-post-taxonomy-bubble single-post-taxonomy-bubble-category" href="/category/{{ category | slugify | url_encode }}/">{{ category | escape }}</a> <a class="single-post-taxonomy-bubble single-post-taxonomy-bubble-category" href="/category/{{ category | slugify | url_encode }}/">{{ category | escape }}</a>
{% endfor %} {% endfor %}
@@ -21,8 +21,8 @@
<div class="post">{{ post.content }}</div> <div class="post">{{ post.content }}</div>
</article> </article>
{% if backlinks.size > 0 %} {% if backlinks.size > 0 %}
<div class="single-post-backlinks" aria-label="{{ 'render.backlinks.ariaLabel' | i18n: language }}"> <div class="single-post-backlinks" aria-label="{{ labels.backlinks_label }}">
<span class="single-post-backlinks-label">{{ 'render.backlinks.label' | i18n: language }}</span> <span class="single-post-backlinks-label">{{ labels.linked_from_label }}</span>
{% for backlink in backlinks %} {% for backlink in backlinks %}
<a class="single-post-taxonomy-bubble single-post-backlink-bubble" href="{{ backlink.path }}">{{ backlink.display_slug }}</a> <a class="single-post-taxonomy-bubble single-post-backlink-bubble" href="{{ backlink.path }}">{{ backlink.display_slug }}</a>
{% endfor %} {% endfor %}

View File

@@ -25,60 +25,34 @@ defmodule BDS.I18nTest do
assert BDS.I18n.format_locale("pt-BR") == "en-US" assert BDS.I18n.format_locale("pt-BR") == "en-US"
end end
test "render translations are loaded from locale json files with unsupported-locale fallback" do test "gettext render translations resolve for supported locales" do
locale_dir = Application.app_dir(:bds, "priv/i18n/locales") assert BDS.Gettext.lgettext("de", "render", "Back to preview home") ==
assert File.exists?(Path.join(locale_dir, "en.json"))
assert File.exists?(Path.join(locale_dir, "de.json"))
assert File.exists?(Path.join(locale_dir, "fr.json"))
assert File.exists?(Path.join(locale_dir, "it.json"))
assert File.exists?(Path.join(locale_dir, "es.json"))
assert BDS.I18n.get_render_translations("de") ==
Jason.decode!(File.read!(Path.join(locale_dir, "de.json")))
assert BDS.I18n.translate("de", "render.notFound.back") ==
"Zurück zur Vorschau-Startseite" "Zurück zur Vorschau-Startseite"
assert BDS.I18n.translate("pt-BR", "render.notFound.back") == "Back to preview home" assert BDS.Gettext.lgettext("pt-BR", "render", "Back to preview home") ==
"Back to preview home"
end end
test "supported locales do not silently fall back to english per key" do test "gettext ui translations resolve for supported locales" do
assert BDS.I18n.translate("de", "missing.key") == "missing.key" BDS.Gettext.put_locale("de")
assert Gettext.dgettext(BDS.Gettext, "ui", "Posts") == "Beiträge"
BDS.Gettext.put_locale("en")
end end
test "supported locale files expose the same translation keys" do test "gettext falls back to the msgid for missing translations" do
catalogs = locale_catalogs() assert BDS.Gettext.lgettext("de", "ui", "totally.missing.key.12345") ==
english_keys = catalogs["en"] |> Map.keys() |> Enum.sort() "totally.missing.key.12345"
for {locale, catalog} <- catalogs, locale != "en" do
assert Map.keys(catalog) |> Enum.sort() == english_keys
end
end end
test "supported non-english locales translate representative keys differently from english" do test "supported non-english locales translate representative keys differently from english" do
catalogs = locale_catalogs() representative = [
{"ui", "Posts"},
representative_keys = [ {"render", "Back to preview home"},
"activity.posts", {"render", "Archive"}
"common.settings",
"render.calendar.title",
"render.notFound.back",
"render.search.ariaLabel"
] ]
for locale <- ["de", "fr", "it", "es"], key <- representative_keys do for locale <- ["de", "fr", "it", "es"], {domain, msgid} <- representative do
assert Map.fetch!(catalogs, locale)[key] != Map.fetch!(catalogs, "en")[key] assert BDS.Gettext.lgettext(locale, domain, msgid) != msgid
end end
end end
defp locale_catalogs do
locale_dir = Application.app_dir(:bds, "priv/i18n/locales")
Path.wildcard(Path.join(locale_dir, "*.json"))
|> Enum.sort()
|> Enum.into(%{}, fn path ->
{Path.basename(path, ".json"), Jason.decode!(File.read!(path))}
end)
end
end end

View File

@@ -263,7 +263,7 @@ defmodule BDS.UI.ShellTest do
assert css =~ "opacity: 1;" assert css =~ "opacity: 1;"
assert Regex.match?( assert Regex.match?(
~r/class="secondary quick-actions-btn".*?<span class="quick-actions-btn-icon">⚡<\/span>\s*<span class="quick-actions-btn-label"><%= translated\("Quick Actions"\) %><\/span>/s, ~r/class="secondary quick-actions-btn".*?<span class="quick-actions-btn-icon">⚡<\/span>\s*<span class="quick-actions-btn-label"><%= dgettext\("ui", "Quick Actions"\) %><\/span>/s,
template template
) )
@@ -271,7 +271,7 @@ defmodule BDS.UI.ShellTest do
assert template =~ ~s(class="quick-action-icon">🤖</span>) assert template =~ ~s(class="quick-action-icon">🤖</span>)
assert Regex.match?( assert Regex.match?(
~r/class="quick-action-text">\s*<strong><%= translated\("AI Suggestions"\) %><\/strong>.*?<\/span>\s*<span class="quick-action-icon">🤖<\/span>/s, ~r/class="quick-action-text">\s*<strong><%= dgettext\("ui", "AI Suggestions"\) %><\/strong>.*?<\/span>\s*<span class="quick-action-icon">🤖<\/span>/s,
template template
) )
@@ -383,7 +383,7 @@ defmodule BDS.UI.ShellTest do
assert post_editor_ex =~ "defp build_data(socket)" assert post_editor_ex =~ "defp build_data(socket)"
assert Regex.match?( assert Regex.match?(
~r/class="secondary quick-actions-btn".*?<span class="quick-actions-btn-icon">⚡<\/span>\s*<span class="quick-actions-btn-label"><%= translated\("Quick Actions"\) %><\/span>/s, ~r/class="secondary quick-actions-btn".*?<span class="quick-actions-btn-icon">⚡<\/span>\s*<span class="quick-actions-btn-label"><%= dgettext\("ui", "Quick Actions"\) %><\/span>/s,
post_template post_template
) )