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

View File

@@ -1,6 +1,8 @@
defmodule BDS.Desktop.ShellData do
@moduledoc false
use Gettext, backend: BDS.Gettext
alias BDS.Git
alias BDS.I18n
alias BDS.Projects
@@ -12,12 +14,24 @@ defmodule BDS.Desktop.ShellData do
Application.get_env(:bds, :desktop)[:title] || "Blogging Desktop Server"
end
def ui_language do
I18n.current_ui_locale()
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 translations(locale \\ nil) do
I18n.get_ui_translations(effective_ui_language(locale))
def ui_language do
I18n.current_ui_locale()
end
def supported_ui_languages do
@@ -26,14 +40,6 @@ defmodule BDS.Desktop.ShellData do
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
Projects.shell_snapshot()
rescue
@@ -77,20 +83,20 @@ defmodule BDS.Desktop.ShellData 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",
text: "Metadata flush, diffing, and rebuild hooks still need editor wiring."
label: dgettext("ui", "Filesystem Sync"),
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
def editor_meta(task_status) do
[
%{label: "Status", value: task_status.running_task_message || "Idle"},
%{label: "Mode", value: "Offline"},
%{label: "Main Language", value: ui_language()}
%{label: dgettext("ui", "Status"), value: task_status.running_task_message || dgettext("ui", "Idle")},
%{label: dgettext("ui", "Mode"), value: dgettext("ui", "Offline")},
%{label: dgettext("ui", "Main Language"), value: ui_language()}
]
end
@@ -140,70 +146,18 @@ defmodule BDS.Desktop.ShellData do
|> Enum.uniq()
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
case to_string(status) do
"draft" -> translate("dashboard.status.draft")
"published" -> translate("dashboard.status.published")
"archived" -> translate("dashboard.status.archived")
"draft" -> dgettext("ui", "Draft")
"published" -> dgettext("ui", "Published")
"archived" -> dgettext("ui", "Archived")
other -> other |> String.replace("_", " ") |> String.capitalize()
end
end
def dashboard_post_count_label(count) do
normalized_count = count || 0
key =
if normalized_count == 1, do: "dashboard.postCount.one", else: "dashboard.postCount.other"
translate(key, %{count: normalized_count})
dngettext("ui", "%{count} post", "%{count} posts", normalized_count, count: normalized_count)
end
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
case to_string(route) do
"git_log" ->
"Git Log"
dgettext("ui", "Git Log")
"post_links" ->
"Post Links"
dgettext("ui", "Post Links")
other ->
other
@@ -288,11 +242,16 @@ defmodule BDS.Desktop.ShellData do
end
end
defp effective_ui_language(nil) do
BDS.Desktop.UILocale.current() || ui_language()
defp git_remote_state_provider do
Application.get_env(:bds, :git_remote_state_provider, &Git.remote_state/2)
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]

View File

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

View File

@@ -6,9 +6,9 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
import Phoenix.HTML, only: [raw: 1]
alias BDS.{AI, BoundedAtoms, MapUtils, Persistence}
alias BDS.Desktop.ShellData
alias BDS.Desktop.ShellLive.ChatEditor.{MessageBuild, ModelSelection, ToolTracking}
alias BDS.Desktop.ShellLive.TabHelpers
use Gettext, backend: BDS.Gettext
embed_templates("chat_editor_html/*")
@@ -72,7 +72,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
{:noreply, assign(socket, :model_selector_open?, false) |> build_data()}
{: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()}
end
end
@@ -196,8 +196,8 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
socket.assigns.offline_mode ->
notify_parent(
{:chat_editor_output, translated("Chat"),
translated("Automatic AI actions stay gated by airplane mode."), "info"}
{:chat_editor_output, dgettext("ui", "Chat"),
dgettext("ui", "Automatic AI actions stay gated by airplane mode."), "info"}
)
build_data(socket)
@@ -272,7 +272,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
assign(socket, :request, nil) |> build_data()
{: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()
end
end
@@ -483,8 +483,8 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
# ── HEEx-callable helpers ─────────────────────────────────────────────────
@spec message_role_label(atom()) :: String.t()
def message_role_label(:user), do: translated("chat.role.you")
def message_role_label(_role), do: translated("chat.role.assistant")
def message_role_label(:user), do: dgettext("ui", "You")
def message_role_label(_role), do: dgettext("ui", "Assistant")
defdelegate tool_call_name(tool_call), to: ToolTracking
defdelegate tool_call_arguments(tool_call), to: ToolTracking
@@ -547,10 +547,10 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
<% end %>
</summary>
<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>
<%= 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>
<% end %>
</div>
@@ -571,7 +571,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
<summary class="chat-inline-surface-header">
<span class="chat-inline-surface-icon"><%= surface_icon(@surface.type) %></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>
<div class="chat-inline-surface-body">
<%= case @surface.type do %>
@@ -848,7 +848,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
defp present?(value), do: not is_nil(value)
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)
@@ -860,7 +860,4 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
:error -> 0
end
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -3,8 +3,8 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.MessageBuild do
alias BDS.AI
alias BDS.AI.ChatConversation
alias BDS.Desktop.ShellData
alias BDS.Desktop.ShellLive.ChatEditor.{ModelSelection, ToolSurfaces, ToolTracking}
use Gettext, backend: BDS.Gettext
@spec build(term()) :: term()
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,
title: conversation.title || translated("chat.newChat"),
title: conversation.title || dgettext("ui", "New Chat"),
model: conversation.model,
effective_model: effective_model,
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(_request), do: nil
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

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

View File

@@ -1,7 +1,7 @@
defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
@moduledoc false
alias BDS.Desktop.ShellData
use Gettext, backend: BDS.Gettext
@render_tool_names MapSet.new([
"render_card",
@@ -85,7 +85,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
|> List.wrap()
|> 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)),
segments: List.wrap(map_value(entry, "segments", []))
}
@@ -173,7 +173,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.ToolSurfaces do
fields: fields,
submit_label:
map_value(arguments, "submitLabel") ||
map_value(arguments, "submit_label", translated("chat.stop")),
map_value(arguments, "submit_label", dgettext("ui", "Stop")),
submit_action:
map_value(arguments, "submitAction") ||
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
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"),
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), do: false
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -3,7 +3,7 @@
<div class="chat-panel-title">
<span class="chat-panel-title-main">
<%= if @chat_editor.needs_api_key? do %>
<%= translated("chat.setupTitle") %>
<%= dgettext("ui", "AI Chat Setup") %>
<% else %>
<%= @chat_editor.title %>
<% end %>
@@ -18,7 +18,7 @@
phx-target={@myself}
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>
</button>
@@ -59,24 +59,24 @@
<%= 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-icon">🔑</div>
<h2><%= translated("chat.apiKeyRequiredTitle") %></h2>
<p><%= translated("chat.apiKeyRequiredDescription") %></p>
<h2><%= dgettext("ui", "API Key Required") %></h2>
<p><%= dgettext("ui", "Configure an API key in Settings to enable AI chat.") %></p>
<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>
<% else %>
<%= if Enum.empty?(@chat_editor.messages) and not @chat_editor.is_streaming do %>
<div class="chat-welcome">
<div class="chat-welcome-icon">🤖</div>
<h2><%= translated("chat.welcomeTitle") %></h2>
<p><%= translated("chat.welcomeDescription") %></p>
<h2><%= dgettext("ui", "Welcome to the AI Assistant") %></h2>
<p><%= dgettext("ui", "I can help you manage your blog with rich visualizations. Try asking me to:") %></p>
<ul>
<li><%= translated("chat.welcomeTipSearch") %></li>
<li><%= translated("chat.welcomeTipChart") %></li>
<li><%= translated("chat.welcomeTipTable") %></li>
<li><%= translated("chat.welcomeTipMetadata") %></li>
<li><%= translated("chat.welcomeTipTabs") %></li>
<li><%= dgettext("ui", "Search for posts about a specific topic") %></li>
<li><%= dgettext("ui", "Show a chart of posts published per month") %></li>
<li><%= dgettext("ui", "Compare my recent posts in a table") %></li>
<li><%= dgettext("ui", "Update metadata for posts or media") %></li>
<li><%= dgettext("ui", "Show post statistics by year in tabs with charts") %></li>
</ul>
</div>
<% else %>
@@ -149,11 +149,11 @@
<%= unless @chat_editor.needs_api_key? do %>
<div class="chat-input-container" data-testid="chat-input-container">
<%= 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 %>
<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>
</form>

View File

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

View File

@@ -30,6 +30,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
translate_execution_phase: 1
]
use Gettext, backend: BDS.Gettext
import TaxonomyEditing,
only: [
existing_taxonomy_terms: 1,
@@ -162,7 +163,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
definition_id = socket.assigns.definition_id
socket =
case FolderPicker.choose_directory(translated("importAnalysis.uploadsFolder")) do
case FolderPicker.choose_directory(dgettext("ui", "Uploads Folder")) do
{:ok, uploads_folder_path} ->
{:ok, _definition} =
ImportDefinitions.update_definition(definition_id, %{
@@ -175,7 +176,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
build_data(socket)
{:error, %{message: message}} ->
notify_output(translated("activity.import"), message, "error")
notify_output(dgettext("ui", "Import"), message, "error")
build_data(socket)
end
@@ -187,7 +188,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
project_id = socket.assigns.project_id
socket =
case FilePicker.choose_file(translated("importAnalysis.wxrFile")) do
case FilePicker.choose_file(dgettext("ui", "WXR File")) do
{:ok, wxr_file_path} ->
{:ok, definition} =
ImportDefinitions.update_definition(definition_id, %{
@@ -214,7 +215,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
socket
|> assign(:analysis_state, %{
loading: true,
step: translated("importAnalysis.analyzingWxr"),
step: dgettext("ui", "Analyzing WXR file..."),
detail: Path.basename(wxr_file_path),
file_path: wxr_file_path,
ref: task.ref
@@ -226,7 +227,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
build_data(socket)
{:error, %{message: message}} ->
notify_output(translated("activity.import"), message, "error")
notify_output(dgettext("ui", "Import"), message, "error")
build_data(socket)
end
@@ -430,12 +431,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
%{} = report <- ImportDefinitions.decode_analysis_result(definition) do
if socket.assigns.offline_mode? do
notify_output(
translated("activity.import"),
ShellData.translate(
"Automatic AI actions stay gated by airplane mode.",
%{},
socket.assigns[:page_language] || ShellData.ui_language()
),
dgettext("ui", "Import"),
BDS.Gettext.lgettext(socket.assigns[:page_language] || ShellData.ui_language(), "ui", "Automatic AI actions stay gated by airplane mode."),
"info"
)
@@ -467,15 +464,15 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
mapped_count = TaxonomyEditing.auto_mapped_count(report, updated_report)
notify_output(
translated("activity.import"),
translated("importAnalysis.mappedCount", %{count: mapped_count}),
dgettext("ui", "Import"),
dgettext("ui", "%{count} mapped", count: mapped_count),
"info"
)
build_data(socket)
{:error, reason} ->
notify_output(translated("activity.import"), inspect(reason), "error")
notify_output(dgettext("ui", "Import"), inspect(reason), "error")
build_data(socket)
end
end
@@ -552,7 +549,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
socket
|> 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] ->
message = if is_binary(reason), do: reason, else: inspect(reason)
@@ -567,7 +564,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
ref: nil
})
)
|> notify_output(translated("activity.import"), message, "error")
|> notify_output(dgettext("ui", "Import"), message, "error")
true ->
socket
@@ -630,11 +627,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
end
defp maybe_update_tab_meta(socket, name) do
title = name || translated("importAnalysis.untitledImport")
title = name || dgettext("ui", "Untitled Import")
notify_parent(
{: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
@@ -653,7 +650,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
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(model_id, available_models) do
@@ -685,14 +682,14 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
socket
{:error, reason} ->
notify_output(socket, translated("activity.import"), inspect(reason), "error")
notify_output(socket, dgettext("ui", "Import"), inspect(reason), "error")
end
{:error, %{message: message}} ->
notify_output(socket, translated("activity.import"), message, "error")
notify_output(socket, dgettext("ui", "Import"), message, "error")
{:error, reason} ->
notify_output(socket, translated("activity.import"), inspect(reason), "error")
notify_output(socket, dgettext("ui", "Import"), inspect(reason), "error")
end
end
@@ -713,8 +710,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
ref: nil
})
|> notify_output(
translated("activity.import"),
translated("importAnalysis.importComplete", %{count: previous_state.count}),
dgettext("ui", "Import"),
dgettext("ui", "Import completed successfully!", count: previous_state.count),
"info"
)
@@ -727,7 +724,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
error: message,
ref: nil
})
|> notify_output(translated("activity.import"), message, "error")
|> notify_output(dgettext("ui", "Import"), message, "error")
{:error, reason} ->
message = inspect(reason)
@@ -740,7 +737,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
error: message,
ref: nil
})
|> notify_output(translated("activity.import"), message, "error")
|> notify_output(dgettext("ui", "Import"), message, "error")
end
# Allow DB connections to settle before rebuilding
@@ -812,27 +809,27 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
class="import-definition-name"
type="text"
name="import_definition[name]"
value={@import_editor.definition_name || translated("importAnalysis.untitledImport")}
placeholder={translated("importAnalysis.namePlaceholder")}
value={@import_editor.definition_name || dgettext("ui", "Untitled Import")}
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>
<div class="import-file-selectors">
<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")]}>
<%= @import_editor.uploads_folder_path || translated("importAnalysis.noFolderSelected") %>
<%= @import_editor.uploads_folder_path || dgettext("ui", "No folder selected") %>
</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 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")]}>
<%= @import_editor.wxr_file_path || translated("importAnalysis.selectFileToAnalyze") %>
<%= @import_editor.wxr_file_path || dgettext("ui", "Select a file to analyze") %>
</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>
@@ -840,7 +837,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<div class="import-loading">
<div class="import-spinner"></div>
<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 %>
<div class="import-progress-detail"><%= @analysis_state.detail %></div>
<% end %>
@@ -851,37 +848,37 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= if not is_nil(@report) and not @import_editor.is_loading do %>
<div class="import-site-info">
<div class="import-site-info-item">
<span class="info-label"><%= translated("importAnalysis.site") %></span>
<span class="info-value"><%= get_in(@report, [:site_info, :title]) || translated("importAnalysis.untitled") %></span>
<span class="info-label"><%= dgettext("ui", "Site") %></span>
<span class="info-value"><%= get_in(@report, [:site_info, :title]) || dgettext("ui", "Untitled") %></span>
</div>
<div class="import-site-info-item">
<span class="info-label"><%= translated("importAnalysis.url") %></span>
<span class="info-value"><%= get_in(@report, [:site_info, :url]) || translated("importAnalysis.notAvailable") %></span>
<span class="info-label"><%= dgettext("ui", "URL") %></span>
<span class="info-value"><%= get_in(@report, [:site_info, :url]) || dgettext("ui", "N/A") %></span>
</div>
<div class="import-site-info-item">
<span class="info-label"><%= translated("importAnalysis.language") %></span>
<span class="info-value"><%= get_in(@report, [:site_info, :language]) || translated("importAnalysis.notAvailable") %></span>
<span class="info-label"><%= dgettext("ui", "Language") %></span>
<span class="info-value"><%= get_in(@report, [:site_info, :language]) || dgettext("ui", "N/A") %></span>
</div>
<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>
</div>
</div>
<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 %>
<.other_stat_card label={translated("importAnalysis.other")} stats={@report.other_stats} />
<.other_stat_card label={dgettext("ui", "Other")} stats={@report.other_stats} />
<% end %>
<.stat_card label={translated("importAnalysis.pages")} stats={@report.page_stats} />
<.media_stat_card label={translated("importAnalysis.media")} stats={@report.media_stats} />
<.taxonomy_stat_card label={translated("importAnalysis.categories")} stats={@report.category_stats} />
<.taxonomy_stat_card label={translated("importAnalysis.tags")} stats={@report.tag_stats} />
<.stat_card label={dgettext("ui", "pages")} stats={@report.page_stats} />
<.media_stat_card label={dgettext("ui", "media")} stats={@report.media_stats} />
<.taxonomy_stat_card label={dgettext("ui", "Categories")} stats={@report.category_stats} />
<.taxonomy_stat_card label={dgettext("ui", "Tags")} stats={@report.tag_stats} />
</div>
<%= if Enum.any?(Map.get(@report, :date_distribution, [])) do %>
<div class="import-date-distribution">
<h3><%= translated("importAnalysis.dateDistribution") %></h3>
<h3><%= dgettext("ui", "Date Distribution") %></h3>
<div class="distribution-bars">
<%= for row <- @report.date_distribution do %>
<div class="distribution-row">
@@ -900,13 +897,13 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= if @execution_state.is_executing do %>
<div class="import-execution-progress">
<div class="import-execution-header">
<h3><%= translated("importAnalysis.importing") %></h3>
<h3><%= dgettext("ui", "Importing...") %></h3>
</div>
<div class="import-progress-bar">
<div class="import-progress-fill" style={"width: #{execution_progress_width(@execution_state)}%;"}></div>
</div>
<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 %>
<span class="import-detail"><%= @execution_state.detail %></span>
<% end %>
@@ -921,18 +918,18 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= if not @execution_state.is_executing and not @execution_state.completed do %>
<div class="import-execute-section">
<div class="import-execute-summary">
<%= translated("importAnalysis.readyToImport") %>
<%= if @counts.tags > 0 do %><span class="import-count-tag"><%= @counts.tags %> <%= translated("importAnalysis.tagsCategories") %></span><% end %>
<%= if @counts.posts > 0 do %><span class="import-count-tag"><%= @counts.posts %> <%= translated("importAnalysis.posts") %></span><% end %>
<%= if @counts.media > 0 do %><span class="import-count-tag"><%= @counts.media %> <%= translated("importAnalysis.media") %></span><% end %>
<%= if @counts.pages > 0 do %><span class="import-count-tag"><%= @counts.pages %> <%= translated("importAnalysis.pages") %></span><% end %>
<%= dgettext("ui", "Ready to import:") %>
<%= 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 %> <%= dgettext("ui", "posts") %></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 %> <%= dgettext("ui", "pages") %></span><% end %>
</div>
<button class="import-execute-btn" type="button" phx-click="execute_import_editor" phx-target={@myself} disabled={@counts.total == 0}>
<%= if @counts.total == 0 do %>
<%= translated("importAnalysis.nothingToImport") %>
<%= dgettext("ui", "Nothing to Import") %>
<% else %>
<%= translated("importAnalysis.importItems", %{count: @counts.total}) %>
<%= dgettext("ui", "Import %{count} Items", count: @counts.total) %>
<% end %>
</button>
</div>
@@ -940,56 +937,56 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= if @execution_state.completed do %>
<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>
<% end %>
<%= if present?(@execution_state.error) do %>
<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>
<% end %>
<%= 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 %>
<%= 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 %>
<%= 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 %>
<%= 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 %>
<%= 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 %>
<%= 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 %>
<%= if Enum.any?(Map.get(@report.items, :categories, [])) or Enum.any?(Map.get(@report.items, :tags, [])) do %>
<section class="import-detail-section">
<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>
</button>
<%= if @sections.taxonomy do %>
<div class="taxonomy-analyze-row">
<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 %>
<div class="taxonomy-model-dropdown">
<%= 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}>
<%= 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>
<% end %>
</div>
@@ -1000,12 +997,12 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= @import_editor.selected_model_label %>
</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 class="import-taxonomy-groups">
<.taxonomy_group
title={translated("importAnalysis.categories")}
title={dgettext("ui", "Categories")}
items={Map.get(@report.items, :categories, [])}
suggestions={Map.get(@import_editor.taxonomy_terms, :categories, [])}
edit={@import_editor.taxonomy_edit}
@@ -1013,7 +1010,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
myself={@myself}
/>
<.taxonomy_group
title={translated("importAnalysis.tags")}
title={dgettext("ui", "Tags")}
items={Map.get(@report.items, :tags, [])}
suggestions={Map.get(@import_editor.taxonomy_terms, :tags, [])}
edit={@import_editor.taxonomy_edit}
@@ -1029,14 +1026,14 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<%= if Enum.any?(Map.get(macros, :discovered, [])) do %>
<section class="import-detail-section">
<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>
</button>
<%= if @sections.macros do %>
<div class="macros-summary">
<span class="macros-mapped"><%= translated("importAnalysis.mappedCount", %{count: macros.mapped_count || 0}) %></span>
<span class="macros-unmapped"><%= translated("importAnalysis.unmappedCount", %{count: macros.unmapped_count || 0}) %></span>
<span class="macros-mapped"><%= dgettext("ui", "%{count} mapped", count: macros.mapped_count || 0) %></span>
<span class="macros-unmapped"><%= dgettext("ui", "%{count} unmapped", count: macros.unmapped_count || 0) %></span>
</div>
<div class="macros-list">
<%= for macro <- macros.discovered do %>
@@ -1044,9 +1041,9 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<div class="macro-header">
<span class="macro-name"><%= macro.name %></span>
<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 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>
<%= if Enum.any?(Map.get(macro, :usages, [])) do %>
<div class="macro-usages">
@@ -1058,17 +1055,17 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<span class="macro-usage-param"><%= k %>=<%= v %></span>
<% end %>
<% else %>
<%= translated("importAnalysis.noParameters") %>
<%= dgettext("ui", "(no parameters)") %>
<% end %>
</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>
<% end %>
</div>
<% end %>
<%= if Enum.any?(Map.get(macro, :post_slugs, [])) do %>
<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>
<% end %>
</div>
@@ -1084,7 +1081,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path>
</svg>
<p><%= translated("importAnalysis.emptyState") %></p>
<p><%= dgettext("ui", "Select a WordPress export file to begin analysis.") %></p>
</div>
<% end %>
<% end %>
@@ -1111,10 +1108,10 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<table class="import-detail-table conflicts-table">
<thead>
<tr>
<th><%= translated("importAnalysis.slug") %></th>
<th><%= translated("importAnalysis.newEntryWxr") %></th>
<th><%= translated("importAnalysis.existingEntry") %></th>
<th><%= translated("importAnalysis.resolution") %></th>
<th><%= dgettext("ui", "Slug") %></th>
<th><%= dgettext("ui", "New Entry (WXR)") %></th>
<th><%= dgettext("ui", "Existing Entry") %></th>
<th><%= dgettext("ui", "Resolution") %></th>
</tr>
</thead>
<tbody>
@@ -1122,15 +1119,15 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<tr>
<td class="slug-cell"><%= Map.get(item, :slug) %></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>
<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_name" value={Map.get(item, :slug)} />
<select class="resolution-select" name="resolution">
<option value="ignore" selected={conflict_resolution_selected?(item, "ignore")}><%= translated("importAnalysis.ignore") %></option>
<option value="overwrite" selected={conflict_resolution_selected?(item, "overwrite")}><%= translated("importAnalysis.overwrite") %></option>
<option value="import" selected={Map.get(item, :resolution) == "import"}><%= translated("importAnalysis.importNewSlug") %></option>
<option value="ignore" selected={conflict_resolution_selected?(item, "ignore")}><%= dgettext("ui", "Ignore") %></option>
<option value="overwrite" selected={conflict_resolution_selected?(item, "overwrite")}><%= dgettext("ui", "Overwrite") %></option>
<option value="import" selected={Map.get(item, :resolution) == "import"}><%= dgettext("ui", "Import (new slug)") %></option>
</select>
</form>
</td>
@@ -1163,15 +1160,15 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<table class="import-detail-table">
<thead>
<tr>
<th><%= translated("importAnalysis.status") %></th>
<th><%= dgettext("ui", "Status") %></th>
<%= if @show_type do %>
<th><%= translated("importAnalysis.type") %></th>
<th><%= dgettext("ui", "Type") %></th>
<% end %>
<th><%= translated("importAnalysis.title") %></th>
<th><%= translated("importAnalysis.slug") %></th>
<th><%= translated("importAnalysis.categories") %></th>
<th><%= translated("importAnalysis.wpStatus") %></th>
<th><%= translated("importAnalysis.existingMatch") %></th>
<th><%= dgettext("ui", "Title") %></th>
<th><%= dgettext("ui", "Slug") %></th>
<th><%= dgettext("ui", "Categories") %></th>
<th><%= dgettext("ui", "WP Status") %></th>
<th><%= dgettext("ui", "Existing Match") %></th>
</tr>
</thead>
<tbody>
@@ -1184,8 +1181,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<td><%= Map.get(item, :title) %></td>
<td class="slug-cell"><%= Map.get(item, :slug) %></td>
<td class="categories-cell"><%= joined_or_none(Map.get(item, :categories)) %></td>
<td><%= Map.get(item, :wp_status) || translated("importAnalysis.none") %></td>
<td class="existing-match"><%= Map.get(item, :existing_title) || translated("importAnalysis.none") %></td>
<td><%= Map.get(item, :wp_status) || dgettext("ui", "--") %></td>
<td class="existing-match"><%= Map.get(item, :existing_title) || dgettext("ui", "--") %></td>
</tr>
<% end %>
</tbody>
@@ -1214,11 +1211,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<table class="import-detail-table">
<thead>
<tr>
<th><%= translated("importAnalysis.status") %></th>
<th><%= translated("importAnalysis.filename") %></th>
<th><%= translated("importAnalysis.type") %></th>
<th><%= translated("importAnalysis.path") %></th>
<th><%= translated("importAnalysis.existingMatch") %></th>
<th><%= dgettext("ui", "Status") %></th>
<th><%= dgettext("ui", "Filename") %></th>
<th><%= dgettext("ui", "Type") %></th>
<th><%= dgettext("ui", "Path") %></th>
<th><%= dgettext("ui", "Existing Match") %></th>
</tr>
</thead>
<tbody>
@@ -1226,9 +1223,9 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<tr>
<td><span class={status_badge_class(item.status)}><%= item.status %></span></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="existing-match"><%= Map.get(item, :existing_title) || translated("importAnalysis.none") %></td>
<td class="existing-match"><%= Map.get(item, :existing_title) || dgettext("ui", "--") %></td>
</tr>
<% end %>
</tbody>
@@ -1248,10 +1245,10 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<h3><%= @label %></h3>
<div class="import-stat-number"><%= total_stats(@stats) %></div>
<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.update_count > 0 do %><span class="import-stat-tag stat-update"><%= @stats.update_count %> <%= translated("importAnalysis.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.duplicate_count > 0 do %><span class="import-stat-tag stat-duplicate"><%= @stats.duplicate_count %> <%= translated("importAnalysis.duplicate") %></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 %> <%= dgettext("ui", "update") %></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 %> <%= dgettext("ui", "duplicate") %></span><% end %>
</div>
</div>
"""
@@ -1285,11 +1282,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<h3><%= @label %></h3>
<div class="import-stat-number"><%= total_media_stats(@stats) %></div>
<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.update_count > 0 do %><span class="import-stat-tag stat-update"><%= @stats.update_count %> <%= translated("importAnalysis.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.duplicate_count > 0 do %><span class="import-stat-tag stat-duplicate"><%= @stats.duplicate_count %> <%= translated("importAnalysis.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.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 %> <%= dgettext("ui", "update") %></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 %> <%= dgettext("ui", "duplicate") %></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>
"""
@@ -1305,9 +1302,9 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
<h3><%= @label %></h3>
<div class="import-stat-number"><%= @stats.existing_count + @stats.mapped_count + @stats.new_count %></div>
<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.mapped_count > 0 do %><span class="import-stat-tag stat-mapped"><%= @stats.mapped_count %> <%= translated("importAnalysis.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.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 %> <%= dgettext("ui", "mapped") %></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>
"""
@@ -1342,14 +1339,14 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
type="text"
name="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}"}
autocomplete="off"
/>
<button class="taxonomy-edit-btn" type="submit" title={translated("importAnalysis.mapToPlaceholder")}>✓</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" 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={dgettext("ui", "Cancel")}>×</button>
<%= 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 %>
</form>
<% else %>
@@ -1380,7 +1377,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
phx-value-name={item.name}
phx-value-mapped_to={Map.get(item, :mapped_to) || ""}
><%= 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 %>
</div>
<% end %>
@@ -1416,7 +1413,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
end
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]
@@ -1445,7 +1442,4 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
defp maybe_put(map, _key, nil), do: map
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

View File

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

View File

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

View File

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

View File

@@ -77,8 +77,8 @@
data-testid="toggle-sidebar"
type="button"
phx-click="toggle_sidebar"
aria-label={translated("Toggle sidebar")}
title={translated("Toggle sidebar")}
aria-label={dgettext("ui", "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-pane"></span>
@@ -89,8 +89,8 @@
data-testid="toggle-panel"
type="button"
phx-click="toggle_panel"
aria-label={translated("Toggle panel")}
title={translated("Toggle panel")}
aria-label={dgettext("ui", "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-pane"></span>
@@ -101,8 +101,8 @@
data-testid="toggle-assistant"
type="button"
phx-click="toggle_assistant_sidebar"
aria-label={translated("Toggle assistant")}
title={translated("Toggle assistant")}
aria-label={dgettext("ui", "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-pane"></span>
@@ -178,8 +178,8 @@
data-testid="sidebar-filter-toggle"
type="button"
phx-click="toggle_sidebar_filters"
aria-label={translated(Map.get(@sidebar_data.filters, :toggle_filters_label))}
title={translated(Map.get(@sidebar_data.filters, :toggle_filters_label))}
aria-label={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">
<path d="M6 12v-1h4v1H6zM4 8v-1h8v1H4zm-2-4v-1h12v1H2z"/>
@@ -194,8 +194,8 @@
type="button"
phx-click="create_sidebar_item"
phx-value-kind={create_action.kind}
aria-label={translated(create_action.label)}
title={translated(create_action.label)}
aria-label={create_action.label}
title={create_action.label}
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M14 7v1H8v6H7V8H1V7h6V1h1v6h6z"/>
@@ -215,7 +215,7 @@
<main class="app-content" data-region="content">
<div class="tab-bar" data-region="tab-bar">
<%= if Enum.empty?(@workbench.tabs) do %>
<div class="tab-bar-empty"><%= translated("Dashboard") %></div>
<div class="tab-bar-empty"><%= dgettext("ui", "Dashboard") %></div>
<% else %>
<div class="tab-bar-tabs">
<%= for tab <- @workbench.tabs do %>
@@ -253,8 +253,8 @@
phx-click="close_tab"
phx-value-type={tab.type}
phx-value-id={tab.id}
aria-label={translated("Close tab")}
title={translated("Close tab")}
aria-label={dgettext("ui", "Close tab")}
title={dgettext("ui", "Close tab")}
>
×
</button>
@@ -269,41 +269,41 @@
<%= if is_nil(@current_tab) do %>
<div class="editor-empty">
<div class="dashboard-content">
<h1 data-testid="editor-title"><%= translated("dashboard.title") %></h1>
<p class="text-muted"><%= translated("dashboard.subtitle") %></p>
<h1 data-testid="editor-title"><%= dgettext("ui", "Dashboard") %></h1>
<p class="text-muted"><%= dgettext("ui", "Overview of your blog database") %></p>
<div class="dashboard-stats">
<div class="stat-card">
<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">
<span class="stat-tag stat-published"><%= translated("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-published"><%= dgettext("ui", "dashboard.stats.published", count: @dashboard.post_stats.published_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 %>
<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 %>
</div>
</div>
<div class="stat-card">
<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">
<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>
</div>
</div>
<div class="stat-card">
<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">
<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>
<%= if Enum.any?(@dashboard_timeline_entries) do %>
<div class="dashboard-section">
<h4><%= translated("dashboard.section.postsOverTime") %></h4>
<h4><%= dgettext("ui", "Posts Over Time") %></h4>
<div class="timeline-chart">
<%= for entry <- @dashboard_timeline_entries do %>
<div class="timeline-bar-container">
@@ -322,7 +322,7 @@
<%= if Enum.any?(@dashboard_tag_cloud_items) do %>
<div class="dashboard-section">
<h4><%= translated("dashboard.section.tags") %></h4>
<h4><%= dgettext("ui", "Tags") %></h4>
<div class="tag-cloud">
<%= 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>
@@ -333,7 +333,7 @@
<%= if Enum.any?(@dashboard_category_counts) do %>
<div class="dashboard-section">
<h4><%= translated("dashboard.section.categories") %></h4>
<h4><%= dgettext("ui", "Categories") %></h4>
<div class="tag-cloud">
<%= for category <- @dashboard_category_counts do %>
<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 %>
<div class="dashboard-section">
<h4><%= translated("dashboard.section.recentlyUpdated") %></h4>
<h4><%= dgettext("ui", "Recently Updated") %></h4>
<div class="recent-posts-list">
<%= for post <- @dashboard_recent_posts do %>
<button
@@ -373,8 +373,8 @@
<div class="dashboard-inspector-meta" hidden>
<%= for item <- @editor_meta do %>
<section class="editor-meta-row">
<strong data-testid="editor-meta-label"><%= translated(item.label) %></strong>
<span><%= translated(item.value) %></span>
<strong data-testid="editor-meta-label"><%= item.label %></strong>
<span><%= item.value %></span>
</section>
<% end %>
</div>
@@ -442,8 +442,8 @@
<aside class="editor-meta">
<%= for item <- @editor_meta do %>
<section class="editor-meta-row">
<strong data-testid="editor-meta-label"><%= translated(item.label) %></strong>
<span><%= translated(item.value) %></span>
<strong data-testid="editor-meta-label"><%= item.label %></strong>
<span><%= item.value %></span>
</section>
<% end %>
</aside>
@@ -471,8 +471,8 @@
data-testid="panel-close"
type="button"
phx-click="toggle_panel"
aria-label={translated("Close panel")}
title={translated("Close panel")}
aria-label={dgettext("ui", "Close panel")}
title={dgettext("ui", "Close panel")}
>
×
</button>
@@ -493,24 +493,24 @@
<div class="assistant-content">
<header class="assistant-sidebar-header">
<div class="assistant-sidebar-heading">
<strong><%= translated("AI Assistant") %></strong>
<span class="assistant-sidebar-description"><%= translated("AI conversations") %></span>
<strong><%= dgettext("ui", "AI Assistant") %></strong>
<span class="assistant-sidebar-description"><%= dgettext("ui", "AI conversations") %></span>
</div>
<span class={[
"assistant-sidebar-status",
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>
</header>
<section class="assistant-sidebar-context" data-testid="assistant-context">
<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>
</div>
<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>
</div>
<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"
name="assistant[prompt]"
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>
<button
@@ -536,7 +536,7 @@
type="submit"
disabled={String.trim(@assistant_prompt || "") == ""}
>
<%= translated("Start chat") %>
<%= dgettext("ui", "Start chat") %>
</button>
</form>
@@ -544,8 +544,8 @@
<div class="assistant-sidebar-welcome">
<%= for card <- @assistant_cards do %>
<section class="assistant-card">
<strong><%= translated(card.label) %></strong>
<span><%= translated(card.text) %></span>
<strong><%= card.label %></strong>
<span><%= card.text %></span>
</section>
<% end %>
</div>
@@ -576,8 +576,8 @@
data-testid="toggle-sidebar"
type="button"
phx-click="toggle_sidebar"
aria-label={translated("Toggle sidebar")}
title={translated("Toggle sidebar")}
aria-label={dgettext("ui", "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-pane"></span>
@@ -588,8 +588,8 @@
data-testid="toggle-panel"
type="button"
phx-click="toggle_panel"
aria-label={translated("Toggle panel")}
title={translated("Toggle panel")}
aria-label={dgettext("ui", "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-pane"></span>
@@ -600,8 +600,8 @@
data-testid="toggle-assistant"
type="button"
phx-click="toggle_assistant_sidebar"
aria-label={translated("Toggle assistant")}
title={translated("Toggle assistant")}
aria-label={dgettext("ui", "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-pane"></span>
@@ -614,7 +614,7 @@
class="project-selector-trigger"
data-testid="project-selector-trigger"
type="button"
title={translated("Switch project")}
title={dgettext("ui", "Switch project")}
phx-click="toggle_project_menu"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" class="project-icon">
@@ -629,7 +629,7 @@
<%= if @project_menu_open do %>
<div class="project-dropdown" data-testid="project-dropdown" phx-click-away="close_project_menu">
<div class="project-dropdown-header">
<span><%= translated("Projects") %></span>
<span><%= dgettext("ui", "Projects") %></span>
</div>
<div class="project-list">
<%= for project <- @projects.projects do %>
@@ -650,10 +650,10 @@
</div>
<div class="project-dropdown-footer">
<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 class="create-project-btn" type="button" phx-click="create_project">
<span><%= translated("New Project") %></span>
<span><%= dgettext("ui", "New Project") %></span>
</button>
</div>
</div>
@@ -663,7 +663,7 @@
<%= if @status.left.running_task_message do %>
<span class="task-spinner"></span>
<% 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 %>
<span class="status-bar-count">+<%= @status.left.running_task_overflow %></span>
<% end %>
@@ -673,9 +673,9 @@
<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 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">
<span><%= translated("UI") %></span>
<span><%= dgettext("ui", "UI") %></span>
<select class="status-bar-language-select" name="ui_language" data-testid="status-language-select">
<%= for language <- @supported_ui_languages do %>
<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
alias BDS.Desktop.{FilePicker, ShellData}
alias BDS.Desktop.{FilePicker}
alias BDS.{AI, I18n, Media}
alias BDS.Media.Media, as: MediaRecord
alias BDS.Media.Translation
alias BDS.Posts.Post
alias BDS.Repo
use Gettext, backend: BDS.Gettext
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
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} ->
case Media.replace_media_file(media.id, source_path) do
{:ok, %MediaRecord{} = updated_media} ->
@@ -130,7 +131,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, build_data(socket)}
{: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)}
end
@@ -138,7 +139,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, socket}
{:error, %{message: message}} ->
notify_output(socket, translated("Replace File"), message, "error")
notify_output(socket, dgettext("ui", "Replace File"), message, "error")
{:noreply, build_data(socket)}
end
end
@@ -147,8 +148,8 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
if socket.assigns.offline_mode do
notify_output(
socket,
translated("Detect Language"),
translated("Automatic AI actions stay gated by airplane mode."),
dgettext("ui", "Detect Language"),
dgettext("ui", "Automatic AI actions stay gated by airplane mode."),
"info"
)
@@ -186,19 +187,19 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, socket}
{: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)}
end
{: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)}
_other ->
notify_output(
socket,
translated("Detect Language"),
translated("Language detection failed."),
dgettext("ui", "Detect Language"),
dgettext("ui", "Language detection failed."),
"error"
)
@@ -240,7 +241,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, socket}
{: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)}
end
end
@@ -253,7 +254,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, build_data(socket)}
{: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)}
end
end
@@ -312,7 +313,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, socket}
{: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)}
end
@@ -336,8 +337,8 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
if socket.assigns.offline_mode do
notify_output(
socket,
translated("Translate"),
translated("Automatic AI actions stay gated by airplane mode."),
dgettext("ui", "Translate"),
dgettext("ui", "Automatic AI actions stay gated by airplane mode."),
"info"
)
@@ -350,12 +351,12 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, build_data(socket)}
{: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)}
end
{: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)}
end
end
@@ -374,7 +375,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
{:noreply, socket}
{: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)}
end
end
@@ -469,11 +470,11 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
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_output(socket, translated("Media"), translated("Media saved"))
notify_output(socket, dgettext("ui", "Media"), dgettext("ui", "Media saved"))
socket
{:error, reason} ->
notify_output(socket, translated("Media"), inspect(reason), "error")
notify_output(socket, dgettext("ui", "Media"), inspect(reason), "error")
|> build_data()
end
end
@@ -516,8 +517,8 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
if socket.assigns.offline_mode do
notify_output(
socket,
translated("Translate"),
translated("Automatic AI actions stay gated by airplane mode."),
dgettext("ui", "Translate"),
dgettext("ui", "Automatic AI actions stay gated by airplane mode."),
"info"
)
@@ -537,12 +538,12 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|> build_data()
{:error, reason} ->
notify_output(socket, translated("Translate"), inspect(reason), "error")
notify_output(socket, dgettext("ui", "Translate"), inspect(reason), "error")
|> build_data()
end
{:error, reason} ->
notify_output(socket, translated("Translate"), inspect(reason), "error")
notify_output(socket, dgettext("ui", "Translate"), inspect(reason), "error")
|> build_data()
end
end
@@ -683,14 +684,11 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
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()
def media_editor_save_state_label(:dirty), do: translated("Unsaved")
def media_editor_save_state_label(:saved), do: translated("Saved")
def media_editor_save_state_label(_state), do: translated("Idle")
def media_editor_save_state_label(:dirty), do: dgettext("ui", "Unsaved")
def media_editor_save_state_label(:saved), do: dgettext("ui", "Saved")
def media_editor_save_state_label(_state), do: dgettext("ui", "Idle")
@spec language_label(term()) :: term()
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>
<%= 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 %>
</div>
</div>
@@ -26,7 +26,7 @@
phx-target={@myself}
>
<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>
<%= if @media_editor.quick_actions_open? do %>
@@ -40,8 +40,8 @@
phx-value-kind="ai_suggestions"
>
<span class="quick-action-text">
<strong><%= translated("AI Suggestions") %></strong>
<small><%= translated("Review title, alt text, and caption suggestions") %></small>
<strong><%= dgettext("ui", "AI Suggestions") %></strong>
<small><%= dgettext("ui", "Review title, alt text, and caption suggestions") %></small>
</span>
<span class="quick-action-icon">🤖</span>
</button>
@@ -57,8 +57,8 @@
disabled={not @media_editor.can_detect_language?}
>
<span class="quick-action-text">
<strong><%= translated("Detect Language") %></strong>
<small><%= translated("Persist the detected language for this media item") %></small>
<strong><%= dgettext("ui", "Detect Language") %></strong>
<small><%= dgettext("ui", "Persist the detected language for this media item") %></small>
</span>
<span class="quick-action-icon">🔍</span>
</button>
@@ -74,8 +74,8 @@
disabled={not @media_editor.can_translate?}
>
<span class="quick-action-text">
<strong><%= translated("Translate") %></strong>
<small><%= translated("Select a target language for this media item") %></small>
<strong><%= dgettext("ui", "Translate") %></strong>
<small><%= dgettext("ui", "Select a target language for this media item") %></small>
</span>
<span class="quick-action-icon">🌍</span>
</button>
@@ -84,10 +84,10 @@
</div>
<button class="secondary" type="button" phx-click="replace_media_editor_file" phx-target={@myself}>
<%= translated("Replace File") %>
<%= dgettext("ui", "Replace File") %>
</button>
<button data-testid="media-save-button" type="button" phx-click="save_media_editor" phx-target={@myself}>
<%= translated("Save") %>
<%= dgettext("ui", "Save") %>
</button>
<button
class="secondary danger"
@@ -96,7 +96,7 @@
phx-click="open_overlay"
phx-value-kind="confirm_delete"
>
<%= translated("Delete") %>
<%= dgettext("ui", "Delete") %>
</button>
</div>
</div>
@@ -120,58 +120,58 @@
<div class="media-details">
<form class="media-editor-details-form" data-testid="media-editor-form" phx-change="change_media_editor" phx-target={@myself}>
<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 />
</div>
<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 />
</div>
<div class="editor-field-row">
<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 />
</div>
<%= if @media_editor.dimensions do %>
<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 />
</div>
<% end %>
</div>
<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"]} />
</div>
<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"]} />
</div>
<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>
</div>
<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"]} />
</div>
<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"]} />
</div>
<div class="editor-field">
<label><%= translated("Language") %></label>
<label><%= dgettext("ui", "Language") %></label>
<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 %>
<option value={language} selected={language == @media_editor.form["language"]}><%= language_label(language) %></option>
<% end %>
@@ -181,10 +181,10 @@
<%= if @media_editor.form["language"] not in [nil, ""] do %>
<div class="editor-field media-translations-section">
<label><%= translated("Translations") %></label>
<label><%= dgettext("ui", "Translations") %></label>
<%= 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 %>
<div class="linked-posts-list">
<%= for translation <- @media_editor.translations do %>
@@ -199,7 +199,7 @@
<%= translation.flag %> <%= language_label(translation.language) %><%= if translation.title, do: " — #{translation.title}" %>
</button>
<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 class="unlink-btn" type="button" phx-click="delete_media_translation" phx-target={@myself} phx-value-language={translation.language}>×</button>
</div>
@@ -211,9 +211,9 @@
<div class="editor-field linked-posts-section">
<label>
<%= translated("Linked Posts") %>
<%= dgettext("ui", "Linked Posts") %>
<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>
</label>
@@ -224,14 +224,14 @@
type="text"
name="media_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-target={@myself}
/>
</div>
<%= 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 %>
<div class="post-picker-list">
<%= for result <- @media_editor.post_picker_results do %>
@@ -240,7 +240,7 @@
</button>
<% end %>
<%= 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 %>
</div>
<% end %>
@@ -248,7 +248,7 @@
<% end %>
<%= 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 %>
<div class="linked-posts-list">
<%= for linked_post <- @media_editor.linked_posts do %>
@@ -277,27 +277,27 @@
<div class="translation-modal-backdrop">
<div class="translation-modal">
<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>
</div>
<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"]} />
<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"]} />
</div>
<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"]} />
</div>
<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>
</div>
</form>
<div class="translation-modal-footer">
<button class="secondary" type="button" phx-click="close_media_translation_editor" phx-target={@myself}><%= translated("Cancel") %></button>
<button type="button" phx-click="save_media_translation" phx-target={@myself}><%= translated("Save") %></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}><%= dgettext("ui", "Save") %></button>
</div>
</div>
</div>

View File

@@ -3,7 +3,8 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
use Phoenix.LiveComponent
alias BDS.Desktop.ShellData
use Gettext, backend: BDS.Gettext
alias BDS.Desktop.ShellLive.MenuEditor.{
DraftManagement,
@@ -215,7 +216,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
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()
end
@@ -236,8 +237,8 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
tab_meta =
Map.put(socket.assigns.tab_meta, {:menu_editor, tab_id}, %{
title: translated("menuEditor.tabTitle"),
subtitle: translated("menuEditor.description")
title: dgettext("ui", "Blog Menu"),
subtitle: dgettext("ui", "Manage the central blog navigation outline and save it to meta/menu.opml.")
})
socket
@@ -284,7 +285,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
phx-target={@myself}
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)}>
<.kind_icon kind={item.kind} />
</span>
@@ -317,16 +318,16 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
<div class="menu-editor-inline-actions">
<%= if @menu_editor.draft.type == :page do %>
<button class="menu-editor-inline-action" data-testid="menu-editor-create-submenu" type="submit">
<%= translated("menuEditor.addSubmenu") %>
<%= dgettext("ui", "Add Submenu") %>
</button>
<% else %>
<button class="menu-editor-inline-action" type="submit">
<%= translated("menuEditor.addCategoryArchive") %>
<%= dgettext("ui", "Add Category Archive") %>
</button>
<% end %>
<button class="menu-editor-inline-action" type="button" phx-click="cancel_menu_editor_entry" phx-target={@myself}>
<%= translated("Cancel") %>
<%= dgettext("ui", "Cancel") %>
</button>
</div>
</div>
@@ -334,7 +335,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
<%= if @menu_editor.draft.type == :page do %>
<div class="menu-editor-picker-list">
<%= 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 %>
<%= for post <- @menu_editor.filtered_pages do %>
<button
@@ -353,7 +354,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
<% else %>
<div class="menu-editor-picker-list">
<%= 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 %>
<%= for category <- @menu_editor.filtered_categories do %>
<button
@@ -406,9 +407,6 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
"""
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()
def row_label(item, category_titles) do
@@ -420,24 +418,24 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
end
@spec kind_label(term()) :: term()
def kind_label(:home), do: translated("menuEditor.type.home")
def kind_label(:page), do: translated("menuEditor.type.page")
def kind_label(:category_archive), do: translated("menuEditor.type.categoryArchive")
def kind_label(:submenu), do: translated("menuEditor.type.submenu")
def kind_label(:home), do: dgettext("ui", "Home")
def kind_label(:page), do: dgettext("ui", "Page")
def kind_label(:category_archive), do: dgettext("ui", "Category Archive")
def kind_label(:submenu), do: dgettext("ui", "Submenu")
defdelegate draft_item?(menu_editor, item_id), to: TreePredicates
@spec editing_title(term()) :: term()
def editing_title(%{draft: %{type: :category}}), do: translated("menuEditor.addCategoryArchive")
def editing_title(_menu_editor), do: translated("menuEditor.pagePicker.title")
def editing_title(%{draft: %{type: :category}}), do: dgettext("ui", "Add Category Archive")
def editing_title(_menu_editor), do: dgettext("ui", "Select Page")
@spec editing_hint(term()) :: term()
def editing_hint(%{draft: %{type: :category}}), do: translated("menuEditor.categoryPicker.hint")
def editing_hint(_menu_editor), do: translated("menuEditor.createHint")
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: dgettext("ui", "Select a page below or press Enter to create a submenu")
@spec editing_placeholder(term()) :: term()
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

View File

@@ -1,10 +1,10 @@
defmodule BDS.Desktop.ShellLive.MenuEditor.DraftManagement do
@moduledoc false
alias BDS.Desktop.ShellData
alias BDS.Metadata
alias BDS.Desktop.ShellLive.MenuEditor.PageCategory
alias BDS.Desktop.ShellLive.MenuEditor.TreeOps
use Gettext, backend: BDS.Gettext
@spec current_draft(term()) :: term()
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_id: Ecto.UUID.generate(),
kind: :page,
label: translated("menuEditor.newPage"),
label: dgettext("ui", "New Page"),
slug: nil,
children: [],
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
label =
if(String.trim(query) == "",
do: translated("menuEditor.newSubmenu"),
do: dgettext("ui", "New Submenu"),
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))
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

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

View File

@@ -9,41 +9,41 @@
<div class="menu-editor-main">
<div class="menu-editor-tree-wrap">
<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>
</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>
</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")}>
<span aria-hidden="true"><%= translated("menuEditor.addCategoryArchiveShort") %></span>
<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"><%= dgettext("ui", "menuEditor.addCategoryArchiveShort") %></span>
</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>
</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>
</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>
</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>
</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>
</button>
</div>
<%= 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 %>
<div id="menu-editor-tree-shell" class="menu-editor-tree-shell" phx-hook="MenuEditorTree">
<ul class="menu-editor-tree-level">

View File

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

View File

@@ -5,12 +5,12 @@
<p><%= @misc_editor.subtitle %></p>
</div>
<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 %>
<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 %>
<%= 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 %>
</div>
</div>
@@ -25,9 +25,9 @@
<%= case @misc_editor.kind do %>
<% :site_validation -> %>
<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><%= 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><%= 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", "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><%= 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><%= 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>
<% :metadata_diff -> %>
@@ -81,7 +81,7 @@
phx-value-direction="db_to_file"
phx-value-field={field.field_name}
>
<%= translated("DB to File") %>
<%= dgettext("ui", "DB to File") %>
</button>
<button
@@ -95,7 +95,7 @@
phx-value-direction="file_to_db"
phx-value-field={field.field_name}
>
<%= translated("File to DB") %>
<%= dgettext("ui", "File to DB") %>
</button>
</div>
<% end %>
@@ -130,7 +130,7 @@
<span><%= diff.db_value %></span>
</div>
<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>
</div>
</div>
@@ -145,7 +145,7 @@
<%= if @misc_editor.active_field == nil and @misc_editor.orphan_files != [] do %>
<section class="orphan-files-section" data-testid="metadata-diff-orphans">
<div class="orphan-files-header">
<h3><%= translated("Orphan Files") %></h3>
<h3><%= dgettext("ui", "Orphan Files") %></h3>
<div class="orphan-files-actions">
<span class="misc-summary-pill"><%= length(@misc_editor.orphan_files) %></span>
<button
@@ -155,7 +155,7 @@
phx-click="import_metadata_diff_orphans"
phx-target={@myself}
>
<%= translated("Import") %>
<%= dgettext("ui", "Import") %>
</button>
</div>
</div>
@@ -165,13 +165,13 @@
<header class="diff-item-header">
<div>
<strong><%= orphan.slug %></strong>
<div class="diff-item-meta"><%= translated("Orphan Files") %></div>
<div class="diff-item-meta"><%= dgettext("ui", "Orphan Files") %></div>
</div>
</header>
<div class="diff-item-fields">
<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-value file-value orphan-path"><span><%= orphan.file_path %></span></div>
</div>
@@ -192,30 +192,30 @@
</section>
<section class="translation-validation-section">
<h3><%= translated("translationValidation.databaseTitle") %></h3>
<h3><%= dgettext("ui", "translationValidation.databaseTitle") %></h3>
<%= 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 %>
<div class="translation-validation-list">
<%= for issue <- @misc_editor.invalid_database_rows do %>
<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>
<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>
<%= 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>
<% end %>
<%= 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>
<% end %>
<dt><%= translated("translationValidation.field.languages") %></dt>
<dt><%= dgettext("ui", "translationValidation.field.languages") %></dt>
<dd><%= translation_issue_languages(issue) %></dd>
<%= 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>
<% end %>
</dl>
@@ -226,26 +226,26 @@
</section>
<section class="translation-validation-section">
<h3><%= translated("translationValidation.filesystemTitle") %></h3>
<h3><%= dgettext("ui", "translationValidation.filesystemTitle") %></h3>
<%= 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 %>
<div class="translation-validation-list">
<%= for issue <- @misc_editor.invalid_filesystem_files do %>
<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>
<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>
<%= 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>
<% end %>
<dt><%= translated("translationValidation.field.languages") %></dt>
<dt><%= dgettext("ui", "translationValidation.field.languages") %></dt>
<dd><%= translation_issue_languages(issue) %></dd>
<%= 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>
<% end %>
</dl>
@@ -256,8 +256,8 @@
</section>
<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="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="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?}><%= dgettext("ui", "translationValidation.fix") %></button>
</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>
<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>
<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>
<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>
<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)}><%= dgettext("ui", "Dismiss") %></button>
</article>
<% end %>
</div>
@@ -281,7 +281,7 @@
<p class="git-diff-empty"><%= @misc_editor.empty_message %></p>
<% else %>
<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">
<%= for file_path <- @misc_editor.files do %>
<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
use Phoenix.Component
use Gettext, backend: BDS.Gettext
import Ecto.Query
alias BDS.Desktop.ShellData
alias BDS.{I18n, Media, Metadata, Posts, Repo}
alias BDS.Media.Media, as: MediaRecord
alias BDS.Media.Translation, as: MediaTranslation
@@ -38,10 +38,10 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
language_names: language_names(),
language_flags: language_flags(),
existing_translations: existing_translations(current_tab),
ai_title: ShellData.translate("AI Suggestions", %{}, page_language),
insert_link_title: ShellData.translate("Insert Link", %{}, page_language),
insert_media_title: ShellData.translate("Insert Media", %{}, page_language),
language_picker_title: ShellData.translate("Translate", %{}, page_language),
ai_title: BDS.Gettext.lgettext(page_language, "ui", "AI Suggestions"),
insert_link_title: BDS.Gettext.lgettext(page_language, "ui", "Insert Link"),
insert_media_title: BDS.Gettext.lgettext(page_language, "ui", "Insert Media"),
language_picker_title: BDS.Gettext.lgettext(page_language, "ui", "Translate"),
gallery_title: tab_title,
ai_fields: ai_fields(current_tab, tab_title, tab_subtitle, 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 translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
def project_metadata(nil), do: %{main_language: "en", blog_languages: []}
@@ -217,7 +216,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
[
%{
key: "title",
label: ShellData.translate("Title", %{}, page_language),
label: BDS.Gettext.lgettext(page_language, "ui", "Title"),
current_value: post.title || title,
suggested_value: "",
locked: false,
@@ -225,7 +224,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
},
%{
key: "excerpt",
label: ShellData.translate("Excerpt", %{}, page_language),
label: BDS.Gettext.lgettext(page_language, "ui", "Excerpt"),
current_value: post.excerpt || subtitle,
suggested_value: "",
locked: false,
@@ -233,7 +232,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
},
%{
key: "slug",
label: ShellData.translate("Slug", %{}, page_language),
label: BDS.Gettext.lgettext(page_language, "ui", "Slug"),
current_value: post.slug || slugify(post.title || title),
suggested_value: "",
locked: post.status == :published,
@@ -254,7 +253,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
[
%{
key: "title",
label: ShellData.translate("Title", %{}, page_language),
label: BDS.Gettext.lgettext(page_language, "ui", "Title"),
current_value: media.title || title,
suggested_value: "",
locked: false,
@@ -262,7 +261,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
},
%{
key: "alt",
label: ShellData.translate("Alt Text", %{}, page_language),
label: BDS.Gettext.lgettext(page_language, "ui", "Alt Text"),
current_value: media.alt || "",
suggested_value: "",
locked: false,
@@ -270,7 +269,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
},
%{
key: "caption",
label: ShellData.translate("Caption", %{}, page_language),
label: BDS.Gettext.lgettext(page_language, "ui", "Caption"),
current_value: media.caption || "",
suggested_value: "",
locked: false,
@@ -306,7 +305,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|> 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_type: "media",
reference_list: reference_list
@@ -314,7 +313,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
rescue
_error ->
%{
title: ShellData.translate("Delete Media", %{}, page_language),
title: BDS.Gettext.lgettext(page_language, "ui", "Delete Media"),
entity_name: media_id,
entity_type: "media",
reference_list: []
@@ -327,7 +326,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
|> Kernel.||("tag")
%{
title: ShellData.translate("Delete Tag", %{}, page_language),
title: BDS.Gettext.lgettext(page_language, "ui", "Delete Tag"),
entity_name: tag_name,
entity_type: "tag",
reference_list: []
@@ -335,7 +334,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
rescue
_error ->
%{
title: ShellData.translate("Delete Tag", %{}, page_language),
title: BDS.Gettext.lgettext(page_language, "ui", "Delete Tag"),
entity_name: "tag",
entity_type: "tag",
reference_list: []
@@ -344,7 +343,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents 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_type: "item",
reference_list: []
@@ -366,16 +365,16 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do
%{
target: target,
count: max(length(tags), 1),
title: ShellData.translate("Merge Tags", %{}, page_language),
message: ShellData.translate("Cannot be undone.", %{}, page_language)
title: BDS.Gettext.lgettext(page_language, "ui", "Merge Tags"),
message: BDS.Gettext.lgettext(page_language, "ui", "Cannot be undone.")
}
rescue
_error ->
%{
target: "tag",
count: 1,
title: ShellData.translate("Merge Tags", %{}, page_language),
message: ShellData.translate("Cannot be undone.", %{}, page_language)
title: BDS.Gettext.lgettext(page_language, "ui", "Merge Tags"),
message: BDS.Gettext.lgettext(page_language, "ui", "Cannot be undone.")
}
end

View File

@@ -2,7 +2,7 @@
<%= case @shell_overlay.kind do %>
<% :ai_suggestions -> %>
<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-header">
<h2><%= @shell_overlay.title %></h2>
@@ -11,7 +11,7 @@
<div class="ai-suggestions-modal-body">
<%= if Map.get(@shell_overlay, :error) do %>
<div class="ai-suggestions-error">
<strong><%= translated("Error") %></strong>
<strong><%= dgettext("ui", "Error") %></strong>
<span><%= @shell_overlay.error %></span>
</div>
<% end %>
@@ -39,27 +39,27 @@
</div>
</div>
<div class="ai-suggestions-modal-footer">
<button class="button-cancel" type="button" phx-click="close_overlay"><%= translated("Cancel") %></button>
<button class="button-apply" type="button" phx-click="overlay_confirm"><%= translated("Apply Selected") %></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"><%= dgettext("ui", "Apply Selected") %></button>
</div>
</div>
</div>
<% :insert_link -> %>
<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-header">
<h2 class="insert-modal-title"><%= @shell_overlay.title %></h2>
<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 == :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 == :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"><%= dgettext("ui", "External") %></button>
</div>
</div>
<%= if @shell_overlay.active_tab == :internal do %>
<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>
<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 %>
@@ -69,20 +69,20 @@
</button>
<% end %>
<%= 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 %>
</div>
<% else %>
<form class="insert-modal-external" phx-change="overlay_update_form">
<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} />
</label>
<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} />
</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>
<% end %>
</div>
@@ -90,13 +90,13 @@
<% :insert_media -> %>
<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-header">
<h2 class="insert-modal-title"><%= @shell_overlay.title %></h2>
</div>
<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>
<div class="insert-modal-results insert-modal-media-grid">
<%= for result <- @shell_overlay.results do %>
@@ -115,14 +115,14 @@
<% :language_picker -> %>
<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-header">
<h2><%= @shell_overlay.title %></h2>
<button class="language-picker-modal-close" type="button" phx-click="close_overlay">×</button>
</div>
<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">
<%= for target <- @shell_overlay.available_targets do %>
<button class="language-picker-option" type="button" phx-click="overlay_select_language" phx-value-code={target.code}>
@@ -140,7 +140,7 @@
<% :confirm_delete -> %>
<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-header">
<h2><%= @shell_overlay.title %></h2>
@@ -151,7 +151,7 @@
<%= if @shell_overlay.reference_count > 0 do %>
<div class="confirm-delete-warning">
<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">
<%= for title <- @shell_overlay.reference_list do %>
<li><span class="reference-title"><%= title %></span></li>
@@ -162,15 +162,15 @@
<% end %>
</div>
<div class="confirm-delete-modal-footer">
<button class="button-cancel" type="button" phx-click="close_overlay"><%= translated("Cancel") %></button>
<button class="button-delete" type="button" phx-click="overlay_confirm"><%= translated("Delete") %></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"><%= dgettext("ui", "Delete") %></button>
</div>
</div>
</div>
<% :confirm_dialog -> %>
<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-header">
<h2><%= @shell_overlay.title %></h2>
@@ -180,18 +180,18 @@
<div class="confirm-delete-message"><%= @shell_overlay.message %></div>
</div>
<div class="confirm-delete-modal-footer">
<button class="button-cancel" type="button" phx-click="close_overlay"><%= translated("Cancel") %></button>
<button class="button-apply" type="button" phx-click="overlay_confirm"><%= translated("Confirm") %></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"><%= dgettext("ui", "Confirm") %></button>
</div>
</div>
</div>
<% :gallery -> %>
<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-header">
<h2><%= translated("Gallery") %></h2>
<h2><%= dgettext("ui", "Gallery") %></h2>
<button class="gallery-overlay-close" type="button" phx-click="close_overlay">×</button>
</div>
<div class="gallery-overlay-grid">
@@ -205,7 +205,7 @@
<%= if @shell_overlay.lightbox do %>
<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">
<button class="lightbox-close" type="button" phx-click="overlay_close_lightbox">×</button>
<%= 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]
alias BDS.{AI, Media, Metadata}
alias BDS.Desktop.{Overlay, ShellData, UILocale}
alias BDS.Desktop.{Overlay}
alias BDS.Desktop.ShellLive.{
MediaEditor,
@@ -16,6 +16,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
}
alias BDS.Desktop.ShellLive.OverlayComponents, as: ShellOverlayComponents
use Gettext, backend: BDS.Gettext
# ── Event handlers ─────────────────────────────────────────────────────────
@@ -67,8 +68,8 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
if socket.assigns.offline_mode do
callbacks.append_output.(
socket,
translated("AI Suggestions"),
translated("Automatic AI actions stay gated by airplane mode."),
dgettext("ui", "AI Suggestions"),
dgettext("ui", "Automatic AI actions stay gated by airplane mode."),
nil,
"info"
)
@@ -265,7 +266,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
socket
|> assign(:shell_overlay, nil)
|> callbacks.append_output.(
translated("Delete Media"),
dgettext("ui", "Delete Media"),
inspect(reason),
nil,
"error"
@@ -392,7 +393,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
) :: Phoenix.LiveView.Socket.t()
defp close_overlay_with_output(socket, append_output, title, details) do
socket
|> append_output.(title, translated("Command completed"), details, "info")
|> append_output.(title, dgettext("ui", "Command completed"), details, "info")
|> assign(:shell_overlay, nil)
end
@@ -444,7 +445,4 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
_error -> "en"
end
@spec translated(String.t(), map()) :: String.t()
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, UILocale.current())
end

View File

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

View File

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

View File

@@ -3,8 +3,8 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
alias BDS.Posts
alias BDS.Posts.Post
alias BDS.Desktop.ShellData
alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, PostMetadata}
use Gettext, backend: BDS.Gettext
@spec persist(term(), term(), term(), term(), term()) :: term()
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()
def discard_label(%Post{} = post) do
if has_published_version?(post),
do: translated("Discard Changes"),
else: translated("Discard Draft")
do: dgettext("ui", "Discard Changes"),
else: dgettext("ui", "Discard Draft")
end
@spec discard_title(term()) :: term()
def discard_title(%Post{} = post) do
if has_published_version?(post),
do: translated("Discard changes and restore the published version"),
else: translated("Delete this unpublished draft")
do: dgettext("ui", "Discard changes and restore the published version"),
else: dgettext("ui", "Delete this unpublished draft")
end
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.reject(&(&1 == ""))
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -4,9 +4,9 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
import Ecto.Query
alias BDS.{I18n, Media, Metadata, PostLinks, Posts, Preview, Repo, Templates}
alias BDS.Desktop.ShellData
alias BDS.Media.Media, as: MediaRecord
alias BDS.Posts.{Post, PostMedia}
use Gettext, backend: BDS.Gettext
@spec project_metadata(term()) :: term()
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()
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
@spec gallery_count(term()) :: term()
@@ -219,7 +219,4 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
trimmed -> trimmed
end
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

@@ -4,7 +4,7 @@
<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>
<%= 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 %>
</div>
</div>
@@ -25,7 +25,7 @@
phx-target={@myself}
>
<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>
<%= if @post_editor.quick_actions_open? do %>
@@ -40,8 +40,8 @@
>
<span class="quick-action-icon">🤖</span>
<span class="quick-action-text">
<strong><%= translated("AI Suggestions") %></strong>
<small><%= translated("Review title, excerpt, and content suggestions") %></small>
<strong><%= dgettext("ui", "AI Suggestions") %></strong>
<small><%= dgettext("ui", "Review title, excerpt, and content suggestions") %></small>
</span>
</button>
@@ -57,8 +57,8 @@
>
<span class="quick-action-icon">🌍</span>
<span class="quick-action-text">
<strong><%= translated("Translate") %></strong>
<small><%= translated("Select a target language for this post") %></small>
<strong><%= dgettext("ui", "Translate") %></strong>
<small><%= dgettext("ui", "Select a target language for this post") %></small>
</span>
</button>
</div>
@@ -67,7 +67,7 @@
<%= if @post_editor.can_publish? do %>
<button class="success" data-testid="post-publish-button" type="button" phx-click="publish_post_editor" phx-target={@myself}>
<%= translated("Publish") %>
<%= dgettext("ui", "Publish") %>
</button>
<% end %>
<%= if @post_editor.can_publish? do %>
@@ -77,7 +77,7 @@
<% end %>
<%= 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}>
<%= translated("Delete") %>
<%= dgettext("ui", "Delete") %>
</button>
<% end %>
</div>
@@ -87,10 +87,10 @@
<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}>
<span class="metadata-toggle-chevron"><%= if @post_editor.metadata_expanded, do: "▼", else: "▶" %></span>
<span><%= translated("Metadata") %></span>
<span><%= dgettext("ui", "Metadata") %></span>
</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 %>
<button
class={[
@@ -114,19 +114,19 @@
<div class={["editor-header-row", if(not @post_editor.metadata_expanded, do: "is-collapsed")]}>
<div class="editor-meta">
<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"]} />
</div>
<div class="editor-field">
<label><%= translated("Tags") %></label>
<label><%= dgettext("ui", "Tags") %></label>
<div class="tag-input-container">
<input type="hidden" name="post_editor[tags]" value={@post_editor.form["tags"]} />
<div class="tag-input-wrapper">
<%= for tag <- @post_editor.tag_chips do %>
<span class={["tag-chip", if(tag.color, do: "has-color")]} style={tag_chip_style(tag.color)}>
<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>
<% end %>
@@ -135,7 +135,7 @@
type="text"
name="post_editor[tag_query]"
value={@post_editor.tag_query}
placeholder={translated("Add tag")}
placeholder={dgettext("ui", "Add tag")}
autocomplete="off"
/>
</div>
@@ -154,7 +154,7 @@
<%= 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}>
<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>
<% end %>
</div>
@@ -163,12 +163,12 @@
</div>
<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"]} />
</div>
<div class="editor-field">
<label><%= translated("Language") %></label>
<label><%= dgettext("ui", "Language") %></label>
<div class="editor-language-row">
<select class="post-editor-input" name="post_editor[language]">
<%= for language <- @post_editor.languages do %>
@@ -184,7 +184,7 @@
phx-target={@myself}
disabled={not @post_editor.detect_language_enabled?}
>
<%= translated("Detect") %>
<%= dgettext("ui", "Detect") %>
</button>
</div>
</div>
@@ -193,25 +193,25 @@
<label class="editor-checkbox-label">
<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"]} />
<span><%= translated("Do Not Translate") %></span>
<span><%= dgettext("ui", "Do Not Translate") %></span>
</label>
</div>
<div class="editor-field-row">
<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} />
</div>
<div class="editor-field">
<label><%= translated("Categories") %></label>
<label><%= dgettext("ui", "Categories") %></label>
<div class="tag-input-container">
<input type="hidden" name="post_editor[categories]" value={@post_editor.form["categories"]} />
<div class="tag-input-wrapper">
<%= for category <- @post_editor.category_values do %>
<span class="tag-chip">
<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>
<% end %>
@@ -220,7 +220,7 @@
type="text"
name="post_editor[category_query]"
value={@post_editor.category_query}
placeholder={translated("Add category")}
placeholder={dgettext("ui", "Add category")}
autocomplete="off"
/>
</div>
@@ -236,7 +236,7 @@
<%= 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}>
<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>
<% end %>
</div>
@@ -247,9 +247,9 @@
<%= if @post_editor.show_template_selector? do %>
<div class="editor-field">
<label><%= translated("Template") %></label>
<label><%= dgettext("ui", "Template") %></label>
<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 %>
<option value={template.slug} selected={template.slug == @post_editor.form["template_slug"]}><%= template.title %></option>
<% end %>
@@ -258,10 +258,10 @@
<% end %>
<div class="post-editor-links-panel">
<strong><%= translated("Post Links") %></strong>
<strong><%= dgettext("ui", "Post Links") %></strong>
<div class="post-editor-links-columns">
<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 %>
<ul class="editor-list compact">
<%= for item <- @post_editor.post_links.backlinks do %>
@@ -269,11 +269,11 @@
<% end %>
</ul>
<% else %>
<span class="post-editor-empty"><%= translated("No items") %></span>
<span class="post-editor-empty"><%= dgettext("ui", "No items") %></span>
<% end %>
</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 %>
<ul class="editor-list compact">
<%= for item <- @post_editor.post_links.outlinks do %>
@@ -281,7 +281,7 @@
<% end %>
</ul>
<% else %>
<span class="post-editor-empty"><%= translated("No items") %></span>
<span class="post-editor-empty"><%= dgettext("ui", "No items") %></span>
<% end %>
</div>
</div>
@@ -290,7 +290,7 @@
<aside class="editor-media-panel post-editor-side-panel">
<div class="post-editor-side-panel-header">
<strong><%= translated("Linked Media") %></strong>
<strong><%= dgettext("ui", "Linked Media") %></strong>
</div>
<%= if Enum.any?(@post_editor.linked_media) do %>
@@ -298,24 +298,24 @@
<%= for item <- @post_editor.linked_media do %>
<li class="post-editor-media-item">
<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>
<% end %>
</ul>
<% else %>
<div class="post-editor-empty"><%= translated("No linked media") %></div>
<div class="post-editor-empty"><%= dgettext("ui", "No linked media") %></div>
<% end %>
</aside>
</div>
<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><%= translated("Excerpt") %></span>
<span><%= dgettext("ui", "Excerpt") %></span>
</button>
<div class={["editor-excerpt-panel", if(not @post_editor.excerpt_expanded, do: "is-collapsed")]}>
<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>
</div>
</div>
@@ -323,7 +323,7 @@
<div class="editor-body">
<div class="editor-toolbar">
<div class="editor-toolbar-left">
<label><%= translated("Content") %></label>
<label><%= dgettext("ui", "Content") %></label>
</div>
<div class="editor-toolbar-center">
@@ -351,7 +351,7 @@
phx-click="open_overlay"
phx-value-kind="insert_link"
>
<%= translated("Insert Link") %>
<%= dgettext("ui", "Insert Link") %>
</button>
<button
class="insert-media-button"
@@ -360,7 +360,7 @@
phx-click="open_overlay"
phx-value-kind="insert_media"
>
<%= translated("Insert Media") %>
<%= dgettext("ui", "Insert Media") %>
</button>
<% end %>
@@ -372,7 +372,7 @@
phx-click="open_overlay"
phx-value-kind="gallery"
>
<%= translated("Gallery") %> (<%= @post_editor.gallery_count %>)
<%= dgettext("ui", "Gallery") %> (<%= @post_editor.gallery_count %>)
</button>
<% end %>
</div>
@@ -383,7 +383,7 @@
<%= if @post_editor.preview_url do %>
<iframe class="editor-preview-frame" src={@post_editor.preview_url}></iframe>
<% else %>
<div class="post-editor-empty"><%= translated("Preview unavailable") %></div>
<div class="post-editor-empty"><%= dgettext("ui", "Preview unavailable") %></div>
<% end %>
</div>
<% else %>
@@ -406,10 +406,10 @@
</form>
<div class="editor-footer">
<span><strong><%= translated("Created") %>:</strong> <%= @post_editor.footer.created_at %></span>
<span><strong><%= translated("Updated") %>:</strong> <%= @post_editor.footer.updated_at %></span>
<span><strong><%= dgettext("ui", "Created") %>:</strong> <%= @post_editor.footer.created_at %></span>
<span><strong><%= dgettext("ui", "Updated") %>:</strong> <%= @post_editor.footer.updated_at %></span>
<%= 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 %>
</div>
</div>

View File

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

View File

@@ -7,30 +7,30 @@
"status-#{@script_editor.status}"
]} data-testid="script-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@script_editor.status) %></span>
<%= 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 %>
<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-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-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 danger" type="button" phx-click="delete_script_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Delete", %{}, 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}><%= dgettext("ui", "Run") %></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}><%= dgettext("ui", "Delete") %></button>
</div>
</div>
<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-meta">
<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><%= 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", "Title") %></label><input type="text" name="script_editor[title]" value={@script_editor.title} /></div>
<div class="editor-field"><label><%= dgettext("ui", "Slug") %></label><input type="text" name="script_editor[slug]" value={@script_editor.slug} /></div>
</div>
<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><%= 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 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"><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><%= 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} /> <%= dgettext("ui", "Enabled") %></label></div>
</div>
</div>
</div>
<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
id={"script-editor-monaco-shell-#{@script_editor.id}"}
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>
</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>
</div>

View File

@@ -2,10 +2,10 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
@moduledoc false
use Phoenix.LiveComponent
use Gettext, backend: BDS.Gettext
import Ecto.Query
alias BDS.Desktop.ShellData
alias BDS.Repo
alias BDS.Templates.Template
@@ -386,7 +386,4 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
defp section_matches?(query, keywords),
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

View File

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

View File

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

View File

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

View File

@@ -3,8 +3,8 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
use Phoenix.Component
alias BDS.Desktop.ShellData
alias BDS.MCP.AgentConfig
use Gettext, backend: BDS.Gettext
@mcp_agents [
%{id: :claude_code, label: "Claude Code", supported?: true},
@@ -47,15 +47,15 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
{:error, reason} ->
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)
end
_other ->
socket
|> append_output.(
translated("MCP"),
translated("This MCP agent is not supported in the rewrite yet"),
dgettext("ui", "MCP"),
dgettext("ui", "This MCP agent is not supported in the rewrite yet"),
nil,
"error"
)
@@ -70,28 +70,28 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
end
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,
reason: inspect(reason)
)
end
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,
reason: inspect(reason)
)
end
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,
reason: inspect(reason)
)
end
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
defp mcp_configured?(%{supported?: false}), do: false
@@ -124,7 +124,4 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.MCPConfig do
|> Map.get("mcpServers", %{})
|> Map.has_key?("bDS")
end
defp translated(text, bindings \\ %{}),
do: ShellData.translate(text, bindings, BDS.Desktop.UILocale.current())
end

View File

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

View File

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

View File

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

View File

@@ -8,51 +8,51 @@
>
<div class="settings-view">
<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}>
<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>
</div>
<div class="settings-content">
<%= if Enum.empty?(@settings_editor.active_sections) do %>
<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>
<% end %>
<%= if @settings_editor.project_visible? do %>
<div class="setting-section" id="settings-section-project">
<div class="setting-section-header">
<h3><%= translated("Project") %></h3>
<p class="setting-section-description"><%= translated("Blog identity, URLs, authoring defaults, and bookmarklet setup") %></p>
<h3><%= dgettext("ui", "Project") %></h3>
<p class="setting-section-description"><%= dgettext("ui", "Blog identity, URLs, authoring defaults, and bookmarklet setup") %></p>
</div>
<form class="setting-section-content" phx-change="change_settings_project" phx-target={@myself}>
<div class="setting-row">
<div class="setting-info">
<label class="setting-label"><%= translated("Project Name") %></label>
<label class="setting-label"><%= dgettext("ui", "Project Name") %></label>
</div>
<div class="setting-control"><input type="text" name="settings_project[name]" value={@settings_editor.project["name"]} /></div>
</div>
<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>
<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-input-group">
<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 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>
<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">
<select name="settings_project[main_language]">
<%= for language <- @settings_editor.supported_languages do %>
@@ -62,7 +62,7 @@
</div>
</div>
<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-input-group">
<%= for language <- @settings_editor.supported_languages do %>
@@ -75,15 +75,15 @@
</div>
</div>
<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>
<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>
<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">
<select name="settings_project[blogmark_category]">
<%= for category <- Enum.map(@settings_editor.categories, & &1.name) do %>
@@ -93,67 +93,67 @@
</div>
</div>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-info"><label class="setting-label"><%= dgettext("ui", "Blogmark Bookmarklet") %></label></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>
</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>
<% end %>
<%= if @settings_editor.editor_visible? do %>
<div class="setting-section" id="settings-section-editor">
<div class="setting-section-header">
<h3><%= translated("Editor") %></h3>
<p class="setting-section-description"><%= translated("Default editing mode and diff presentation") %></p>
<h3><%= dgettext("ui", "Editor") %></h3>
<p class="setting-section-description"><%= dgettext("ui", "Default editing mode and diff presentation") %></p>
</div>
<form class="setting-section-content" phx-change="change_settings_editor" phx-target={@myself}>
<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">
<select name="settings_editor[default_mode]">
<option value="wysiwyg" selected={@settings_editor.editor["default_mode"] == "wysiwyg"}><%= translated("WYSIWYG") %></option>
<option value="markdown" selected={@settings_editor.editor["default_mode"] == "markdown"}><%= translated("Markdown") %></option>
<option value="preview" selected={@settings_editor.editor["default_mode"] == "preview"}><%= translated("Preview") %></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"}><%= dgettext("ui", "Markdown") %></option>
<option value="preview" selected={@settings_editor.editor["default_mode"] == "preview"}><%= dgettext("ui", "Preview") %></option>
</select>
</div>
</div>
<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">
<select name="settings_editor[diff_view_style]">
<option value="inline" selected={@settings_editor.editor["diff_view_style"] == "inline"}><%= translated("Inline") %></option>
<option value="side-by-side" selected={@settings_editor.editor["diff_view_style"] == "side-by-side"}><%= translated("Side by Side") %></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"}><%= dgettext("ui", "Side by Side") %></option>
</select>
</div>
</div>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-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"]} /> <%= dgettext("ui", "Enable line wrapping in diffs") %></label></div>
</div>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-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"]} /> <%= dgettext("ui", "Collapse unchanged diff hunks") %></label></div>
</div>
</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>
<% end %>
<%= if @settings_editor.content_visible? do %>
<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">
<table class="categories-table">
<thead>
<tr>
<th><%= translated("Category") %></th>
<th><%= translated("Title") %></th>
<th><%= translated("Render in Lists") %></th>
<th><%= translated("Show Titles") %></th>
<th><%= translated("Post Template") %></th>
<th><%= translated("List Template") %></th>
<th><%= translated("Actions") %></th>
<th><%= dgettext("ui", "Category") %></th>
<th><%= dgettext("ui", "Title") %></th>
<th><%= dgettext("ui", "Render in Lists") %></th>
<th><%= dgettext("ui", "Show Titles") %></th>
<th><%= dgettext("ui", "Post Template") %></th>
<th><%= dgettext("ui", "List Template") %></th>
<th><%= dgettext("ui", "Actions") %></th>
</tr>
</thead>
<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>
<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 %>
<option value={template.slug} selected={template.slug == category.post_template_slug}><%= template.title %></option>
<% end %>
@@ -175,7 +175,7 @@
</td>
<td>
<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 %>
<option value={template.slug} selected={template.slug == category.list_template_slug}><%= template.title %></option>
<% end %>
@@ -186,8 +186,8 @@
<form id={"category-form-#{category.name}"} phx-submit="save_settings_category" phx-target={@myself}>
<input type="hidden" name="category_settings[category]" value={category.name} />
</form>
<button class="secondary" type="submit" form={"category-form-#{category.name}"}><%= translated("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="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?}><%= dgettext("ui", "Remove") %></button>
</div>
</td>
</tr>
@@ -195,103 +195,103 @@
</tbody>
</table>
<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-input-group">
<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 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>
<% end %>
<%= if @settings_editor.ai_visible? do %>
<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}>
<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-input-group">
<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 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>
<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>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-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"]} /> <%= dgettext("ui", "Enable tool calls for the online chat model") %></label></div>
</div>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-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"]} /> <%= dgettext("ui", "Disable reasoning output for the online chat model") %></label></div>
</div>
<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>
<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>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-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"]} /> <%= dgettext("ui", "Enable image analysis for the online image analysis model") %></label></div>
</div>
<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-input-group">
<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 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>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-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"]} /> <%= dgettext("ui", "Route AI tasks through the airplane endpoint") %></label></div>
</div>
<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>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-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"]} /> <%= dgettext("ui", "Enable tool calls for the offline chat model") %></label></div>
</div>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-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"]} /> <%= dgettext("ui", "Disable reasoning output for the offline chat model") %></label></div>
</div>
<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>
<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>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-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"]} /> <%= dgettext("ui", "Enable image analysis for the offline image analysis model") %></label></div>
</div>
<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>
<datalist id="settings-ai-online-models">
@@ -305,53 +305,53 @@
<% end %>
</datalist>
</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>
<% end %>
<%= if @settings_editor.technology_visible? do %>
<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}>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-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"]} /> <%= dgettext("ui", "Enable duplicate search and related-post embeddings") %></label></div>
</div>
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= translated("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-info"><label class="setting-label"><%= dgettext("ui", "Scripting Runtime") %></label></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>
</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>
<% end %>
<%= if @settings_editor.publishing_visible? do %>
<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}>
<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"><%= 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"><%= 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"><%= 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", "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", "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", "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", "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>
<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>
<% end %>
<%= if @settings_editor.mcp_visible? do %>
<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">
<%= for agent <- @settings_editor.mcp do %>
<div class="setting-row">
<div class="setting-info">
<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 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?}>
<%= if agent.configured?, do: translated("Remove"), else: translated("Add") %>
<%= if agent.configured?, do: dgettext("ui", "Remove"), else: dgettext("ui", "Add") %>
</button>
</div>
</div>
@@ -362,16 +362,16 @@
<%= if @settings_editor.data_visible? do %>
<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">
<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_media_from_files"><%= translated("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_templates_from_files"><%= translated("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="regenerate_missing_thumbnails"><%= translated("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="open_data_folder"><%= translated("Open Data Folder") %></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"><%= dgettext("ui", "Rebuild Media 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"><%= dgettext("ui", "Rebuild Templates From Files") %></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"><%= dgettext("ui", "Regenerate Missing Thumbnails") %></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"><%= dgettext("ui", "Open Data Folder") %></button>
</div>
</div>
<% end %>

View File

@@ -1,10 +1,10 @@
<div class="style-view" data-testid="style-editor">
<div class="style-view-header">
<h2 data-testid="editor-title"><%= translated("Style") %></h2>
<p><%= translated("Theme preview and renderer theme selection") %></p>
<h2 data-testid="editor-title"><%= dgettext("ui", "Style") %></h2>
<p><%= dgettext("ui", "Theme preview and renderer theme selection") %></p>
</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 %>
<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">
@@ -21,17 +21,17 @@
<div class="style-apply-row">
<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">
<option value="auto" selected={@style_editor.preview_mode == "auto"}><%= translated("Auto") %></option>
<option value="light" selected={@style_editor.preview_mode == "light"}><%= translated("Light") %></option>
<option value="dark" selected={@style_editor.preview_mode == "dark"}><%= translated("Dark") %></option>
<option value="auto" selected={@style_editor.preview_mode == "auto"}><%= dgettext("ui", "Auto") %></option>
<option value="light" selected={@style_editor.preview_mode == "light"}><%= dgettext("ui", "Light") %></option>
<option value="dark" selected={@style_editor.preview_mode == "dark"}><%= dgettext("ui", "Dark") %></option>
</select>
</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 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>

View File

@@ -6,6 +6,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
alias BDS.Desktop.ShellData
alias BDS.Desktop.UILocale
alias BDS.UI.Registry
use Gettext, backend: BDS.Gettext
def sidebar_content(assigns) do
UILocale.put(assigns.page_language)
@@ -60,9 +61,9 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
type="text"
name="sidebar_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">
<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>
@@ -75,10 +76,10 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
<%= if Map.get(@sidebar_filters_config, :has_active_filters) do %>
<div class="filter-status">
<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>
<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>
</div>
<% end %>
@@ -96,7 +97,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
phx-click="toggle_sidebar_archive"
>
<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 %>
<button class="clear-filter" type="button" phx-click="clear_sidebar_month" phx-stop-propagation>✕</button>
<% end %>
@@ -157,9 +158,9 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
phx-click="toggle_sidebar_tags"
>
<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 %>
<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 %>
</div>
<%= unless @tags_collapsed do %>
@@ -198,9 +199,9 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
phx-click="toggle_sidebar_categories"
>
<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 %>
<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 %>
</div>
<%= unless @categories_collapsed do %>
@@ -240,7 +241,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
~H"""
<div class="sidebar-load-more">
<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>
</div>
"""
@@ -266,7 +267,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
<section class="sidebar-section">
<div class="sidebar-section-title">
<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>
</div>
<div class="sidebar-list">
@@ -316,7 +317,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
<% end %>
<%= if Enum.empty?(Map.get(@sidebar_data, :sections, [])) do %>
<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>
<% end %>
"""
@@ -376,7 +377,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
</div>
<% else %>
<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>
<% end %>
"""
@@ -398,17 +399,17 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
data-route={item.route}
data-item-id={item.id}
data-open-title={item.title}
data-open-subtitle={translated(item.meta || "")}
data-open-subtitle={item.meta || ""}
type="button"
phx-click="open_sidebar_item"
phx-value-route={item.route}
phx-value-id={item.id}
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-title"><%= item.title %></span>
<span class="chat-item-date"><%= translated(item.meta || "") %></span>
<span class="chat-item-date"><%= item.meta || "" %></span>
</span>
</button>
<button
@@ -432,17 +433,17 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
data-route={item.route}
data-item-id={item.id}
data-open-title={item.title}
data-open-subtitle={translated(item.meta || "")}
data-open-subtitle={item.meta || ""}
type="button"
phx-click="open_sidebar_item"
phx-value-route={item.route}
phx-value-id={item.id}
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-title"><%= item.title %></span>
<span class="chat-item-date"><%= translated(item.meta || "") %></span>
<span class="chat-item-date"><%= item.meta || "" %></span>
</span>
</button>
<% end %>
@@ -450,7 +451,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
</div>
<% else %>
<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>
<% end %>
"""
@@ -465,17 +466,17 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
data-testid="sidebar-open-item"
data-route={item.route}
data-item-id={item.id}
data-open-title={translated(item.title)}
data-open-subtitle={translated(Map.get(@sidebar_data, :subtitle, ""))}
data-open-title={item.title}
data-open-subtitle={Map.get(@sidebar_data, :subtitle, "")}
type="button"
phx-click="open_sidebar_item"
phx-value-route={item.route}
phx-value-id={item.id}
phx-value-title={translated(item.title)}
phx-value-subtitle={translated(Map.get(@sidebar_data, :subtitle, ""))}
phx-value-title={item.title}
phx-value-subtitle={Map.get(@sidebar_data, :subtitle, "")}
>
<span class="settings-nav-entry-icon"><%= Map.get(item, :icon, "") %></span>
<span><%= translated(item.title) %></span>
<span><%= item.title %></span>
</button>
<% end %>
</div>
@@ -487,7 +488,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
<%= for section <- Map.get(@sidebar_data, :sections, []) do %>
<section class="sidebar-section">
<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 class="sidebar-section-items">
<%= for item <- Map.get(section, :items, []) do %>
@@ -499,8 +500,6 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
"""
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"]
@@ -512,13 +511,13 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
defp sidebar_delete_testid("import"), do: "sidebar-delete-import"
defp sidebar_delete_testid(route), do: "sidebar-delete-#{route}"
defp sidebar_delete_title("chat"), do: translated("sidebar.chat.deleteConversation")
defp sidebar_delete_title("post"), do: translated("Delete") <> " " <> translated("Post")
defp sidebar_delete_title("media"), do: translated("Delete") <> " " <> translated("Media")
defp sidebar_delete_title("scripts"), do: translated("Delete") <> " " <> translated("Script")
defp sidebar_delete_title("templates"), do: translated("Delete") <> " " <> translated("Template")
defp sidebar_delete_title("import"), do: translated("Delete") <> " " <> translated("Import")
defp sidebar_delete_title(_route), do: translated("Delete")
defp sidebar_delete_title("chat"), do: dgettext("ui", "Delete conversation")
defp sidebar_delete_title("post"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Post")
defp sidebar_delete_title("media"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Media")
defp sidebar_delete_title("scripts"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Script")
defp sidebar_delete_title("templates"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Template")
defp sidebar_delete_title("import"), do: dgettext("ui", "Delete") <> " " <> dgettext("ui", "Import")
defp sidebar_delete_title(_route), do: dgettext("ui", "Delete")
defp template_sidebar?(sidebar_data), do: Map.get(sidebar_data, :title) == "Templates"

View File

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

View File

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

View File

@@ -6,8 +6,9 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
alias BDS.Media.Media, as: MediaRecord
alias BDS.Posts.Post
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
case Map.get(tab_meta, {tab.type, tab.id}) do
@@ -16,7 +17,7 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
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
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."
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_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
case Scripts.get_script(script_id) do
%{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 ->
%{}
@@ -145,7 +146,7 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
defp derived_tab_meta(%{type: :templates, id: template_id}) do
case Templates.get_template(template_id) do
%{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 ->
%{}
@@ -155,7 +156,7 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
defp derived_tab_meta(%{type: :chat, id: conversation_id}) do
case AI.get_chat_conversation(conversation_id) do
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 ->
%{}
@@ -166,8 +167,8 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
case ImportDefinitions.get_definition(definition_id) do
%{name: name} ->
%{
title: blank_to_nil(name) || translated("importAnalysis.untitledImport"),
subtitle: translated("importAnalysis.headerDescription")
title: blank_to_nil(name) || dgettext("ui", "Untitled Import"),
subtitle: dgettext("ui", "Select a WordPress export file (WXR) and an uploads folder to analyze what would be imported.")
}
_other ->
@@ -176,13 +177,13 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
end
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
defp derived_tab_meta(%{type: :menu_editor}) do
%{
title: translated("menuEditor.tabTitle"),
subtitle: translated("menuEditor.description")
title: dgettext("ui", "Blog Menu"),
subtitle: dgettext("ui", "Manage the central blog navigation outline and save it to meta/menu.opml.")
}
end
@@ -234,6 +235,4 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do
trimmed -> trimmed
end
end
defp translated(text), do: ShellData.translate(text, %{}, BDS.Desktop.UILocale.current())
end

View File

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

View File

@@ -8,17 +8,17 @@
>
<div class="tags-view">
<div class="tags-view-header">
<h2><%= translated("Tags") %></h2>
<h2><%= dgettext("ui", "Tags") %></h2>
</div>
<div class="tags-view-content">
<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">
<%= if Enum.empty?(@tags_editor.tags) do %>
<div class="tags-empty-state">
<p><%= translated("No tags found") %></p>
<button class="secondary" type="button" phx-click="sync_tags_editor" phx-target={@myself}><%= translated("Discover") %></button>
<p><%= dgettext("ui", "No tags found") %></p>
<button class="secondary" type="button" phx-click="sync_tags_editor" phx-target={@myself}><%= dgettext("ui", "Discover") %></button>
</div>
<% else %>
<div class="tag-cloud">
@@ -33,13 +33,13 @@
</div>
<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">
<form class="tag-create-form" phx-change="change_new_tag_editor" phx-target={@myself}>
<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"])} />
<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>
</form>
@@ -49,13 +49,13 @@
<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"])} />
<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 %>
<option value={template.slug} selected={template.slug == @tags_editor.edit_draft["post_template_slug"]}><%= template.title %></option>
<% end %>
</select>
<button class="primary" type="button" phx-click="save_tag_editor" phx-target={@myself}><%= translated("Save") %></button>
<button class="danger" type="button" phx-click="delete_tag_editor" phx-target={@myself}><%= translated("Delete") %></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}><%= dgettext("ui", "Delete") %></button>
</div>
</form>
<% end %>
@@ -63,7 +63,7 @@
</div>
<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="merge-form">
<div class="tag-form-row">
@@ -72,16 +72,16 @@
<option value={tag_name} selected={tag_name == @tags_editor.merge_target}><%= tag_name %></option>
<% end %>
</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 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">
<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>

View File

@@ -1,7 +1,6 @@
defmodule BDS.Desktop.ShellLive.TaskLocalization do
@moduledoc false
alias BDS.Desktop.ShellData
def localize_task_status(task_status, locale) do
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
Enum.map(items, fn 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))
end)
end
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
@@ -42,7 +41,7 @@ defmodule BDS.Desktop.ShellLive.TaskLocalization do
progress = Map.get(task, :progress)
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(:group_name, localize_task_group(Map.get(task, :group_name), 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("", _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(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
status
|> to_string()
|> String.capitalize()
|> ShellData.translate(%{}, locale)
|> then(&BDS.Gettext.lgettext(locale, "ui", &1))
end
defp localized_running_task_message([], _locale), do: nil
defp localized_running_task_message([task | _rest], locale) 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
true -> task.name
end
end
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
end

View File

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

View File

@@ -7,28 +7,28 @@
"status-#{@template_editor.status}"
]} data-testid="template-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@template_editor.status) %></span>
<%= 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 %>
<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-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 danger" type="button" phx-click="delete_template_editor" phx-target={@myself}><%= BDS.Desktop.ShellData.translate("Delete", %{}, 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}><%= dgettext("ui", "Validate") %></button>
<button class="secondary danger" type="button" phx-click="delete_template_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
</div>
</div>
<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-meta">
<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><%= 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", "Title") %></label><input type="text" name="template_editor[title]" value={@template_editor.title} /></div>
<div class="editor-field"><label><%= dgettext("ui", "Slug") %></label><input type="text" name="template_editor[slug]" value={@template_editor.slug} /></div>
</div>
<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 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"><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} /> <%= dgettext("ui", "Enabled") %></label></div>
</div>
</div>
</div>
<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
id={"template-editor-monaco-shell-#{@template_editor.id}"}
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>
</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>
</div>

View File

@@ -30,6 +30,7 @@ defmodule BDS.Desktop.UILocale do
@spec put(locale()) :: :ok
def put(locale) do
Process.put(@key, locale)
BDS.Gettext.put_locale(locale)
:ok
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_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
@@ -81,33 +69,6 @@ defmodule BDS.I18n do
}
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
language
|> resolve_supported_locale()
@@ -151,8 +112,4 @@ defmodule BDS.I18n do
|> String.split("-", parts: 2)
|> List.first()
end
defp catalog_for_locale(locale) do
Map.get(@catalogs, locale, Map.get(@catalogs, @default_language, %{}))
end
end

View File

@@ -3,7 +3,6 @@ defmodule BDS.Rendering.Filters do
use Liquex.Filter
alias BDS.I18n
alias BDS.Slug
def i18n(value, language, _context) do
@@ -12,7 +11,7 @@ defmodule BDS.Rendering.Filters do
if key == "" do
""
else
I18n.translate(language, key)
BDS.Gettext.lgettext(language, "render", key)
end
end
@@ -59,7 +58,7 @@ defmodule BDS.Rendering.Filters do
default_macro_title(
Map.get(params, "title"),
language,
"render.video.youtubeTitle"
"YouTube video"
)
},
context
@@ -71,7 +70,7 @@ defmodule BDS.Rendering.Filters do
%{
"id" => Map.get(params, "id", ""),
"title" =>
default_macro_title(Map.get(params, "title"), language, "render.video.vimeoTitle")
default_macro_title(Map.get(params, "title"), language, "Vimeo video")
},
context
)
@@ -82,13 +81,17 @@ defmodule BDS.Rendering.Filters do
end)
end
defp default_macro_title(nil, language, translation_key),
do: I18n.translate(language, translation_key)
defp default_macro_title(nil, language, translation),
do: translated_macro_title(language, translation)
defp default_macro_title("", language, translation_key),
do: I18n.translate(language, translation_key)
defp default_macro_title("", language, translation),
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(""), 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
@moduledoc false
alias BDS.I18n
alias BDS.MapUtils
alias BDS.Persistence
alias BDS.Rendering.Labels
alias BDS.Rendering.LinksAndLanguages
alias BDS.Rendering.Metadata, as: RenderMetadata
alias BDS.Rendering.PostRendering
alias BDS.Rendering.TemplateSelection
use Gettext, backend: BDS.Gettext
def list_assigns(project_id, assigns) do
metadata = RenderMetadata.project_metadata(project_id)
@@ -95,7 +96,9 @@ defmodule BDS.Rendering.ListArchive do
canonical_media_path_by_source_path: canonical_media_paths,
post_data_json_by_id:
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
@@ -145,7 +148,7 @@ defmodule BDS.Rendering.ListArchive do
Map.get(
assigns,
"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:
@@ -155,9 +158,10 @@ defmodule BDS.Rendering.ListArchive do
Map.get(
assigns,
"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

View File

@@ -2,6 +2,7 @@ defmodule BDS.Rendering.PostRendering do
@moduledoc false
alias BDS.Rendering.Filters
alias BDS.Rendering.Labels
alias BDS.Rendering.LinksAndLanguages
alias BDS.Rendering.Metadata, as: RenderMetadata
alias BDS.Rendering.TemplateSelection
@@ -95,7 +96,8 @@ defmodule BDS.Rendering.PostRendering do
canonical_post_path_by_slug: canonical_post_paths,
canonical_media_path_by_source_path: canonical_media_paths,
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

View File

@@ -1,114 +1,118 @@
defmodule BDS.UI.Registry do
@moduledoc false
@sidebar_views [
%{
id: :posts,
label: "Posts",
activity_group: :top,
editor_route: :post,
entity_tab: true,
demo_kind: :entity
},
%{
id: :pages,
label: "Pages",
activity_group: :top,
editor_route: :post,
entity_tab: true,
demo_kind: :entity
},
%{
id: :media,
label: "Media",
activity_group: :top,
editor_route: :media,
entity_tab: true,
demo_kind: :entity
},
%{
id: :scripts,
label: "Scripts",
activity_group: :top,
editor_route: :scripts,
entity_tab: true,
demo_kind: :entity
},
%{
id: :templates,
label: "Templates",
activity_group: :top,
editor_route: :templates,
entity_tab: true,
demo_kind: :entity
},
%{
id: :tags,
label: "Tags",
activity_group: :top,
editor_route: :tags,
singleton: true,
demo_kind: :singleton
},
%{
id: :chat,
label: "AI Assistant",
activity_group: :top,
editor_route: :chat,
entity_tab: true,
demo_kind: :entity
},
%{
id: :import,
label: "Import",
activity_group: :top,
editor_route: :import,
entity_tab: true,
demo_kind: :entity
},
%{
id: :git,
label: "Source Control",
activity_group: :bottom,
editor_route: :git_diff,
entity_tab: true,
demo_kind: :entity
},
%{
id: :settings,
label: "Settings",
activity_group: :bottom,
editor_route: :settings,
singleton: true,
demo_kind: :singleton
}
]
@editor_routes [
%{id: :dashboard, singleton: true, entity_tab: false, title: "Dashboard"},
%{id: :post, singleton: false, entity_tab: true, title: "Post"},
%{id: :media, singleton: false, entity_tab: true, title: "Media"},
%{id: :settings, singleton: true, entity_tab: false, title: "Settings"},
%{id: :style, singleton: true, entity_tab: false, title: "Style"},
%{id: :tags, singleton: true, entity_tab: false, title: "Tags"},
%{id: :chat, singleton: false, entity_tab: true, title: "Chat"},
%{id: :import, singleton: false, entity_tab: true, title: "Import"},
%{id: :menu_editor, singleton: true, entity_tab: false, title: "Menu"},
%{id: :metadata_diff, singleton: true, entity_tab: false, title: "Metadata Diff"},
%{id: :git_diff, singleton: false, entity_tab: true, title: "Git Diff"},
%{id: :documentation, singleton: true, entity_tab: false, title: "Documentation"},
%{id: :api_documentation, singleton: true, entity_tab: false, title: "API"},
%{id: :site_validation, singleton: true, entity_tab: false, title: "Site Validation"},
%{id: :translation_validation, singleton: true, entity_tab: false, title: "Translations"},
%{id: :scripts, singleton: false, entity_tab: true, title: "Script"},
%{id: :templates, singleton: false, entity_tab: true, title: "Template"},
%{id: :find_duplicates, singleton: true, entity_tab: false, title: "Find Duplicates"}
]
use Gettext, backend: BDS.Gettext
def default_sidebar_view, do: :posts
def sidebar_views, do: @sidebar_views
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))
def sidebar_views do
[
%{
id: :posts,
label: dgettext("ui", "Posts"),
activity_group: :top,
editor_route: :post,
entity_tab: true,
demo_kind: :entity
},
%{
id: :pages,
label: dgettext("ui", "Pages"),
activity_group: :top,
editor_route: :post,
entity_tab: true,
demo_kind: :entity
},
%{
id: :media,
label: dgettext("ui", "Media"),
activity_group: :top,
editor_route: :media,
entity_tab: true,
demo_kind: :entity
},
%{
id: :scripts,
label: dgettext("ui", "Scripts"),
activity_group: :top,
editor_route: :scripts,
entity_tab: true,
demo_kind: :entity
},
%{
id: :templates,
label: dgettext("ui", "Templates"),
activity_group: :top,
editor_route: :templates,
entity_tab: true,
demo_kind: :entity
},
%{
id: :tags,
label: dgettext("ui", "Tags"),
activity_group: :top,
editor_route: :tags,
singleton: true,
demo_kind: :singleton
},
%{
id: :chat,
label: dgettext("ui", "AI Assistant"),
activity_group: :top,
editor_route: :chat,
entity_tab: true,
demo_kind: :entity
},
%{
id: :import,
label: dgettext("ui", "Import"),
activity_group: :top,
editor_route: :import,
entity_tab: true,
demo_kind: :entity
},
%{
id: :git,
label: dgettext("ui", "Source Control"),
activity_group: :bottom,
editor_route: :git_diff,
entity_tab: true,
demo_kind: :entity
},
%{
id: :settings,
label: dgettext("ui", "Settings"),
activity_group: :bottom,
editor_route: :settings,
singleton: true,
demo_kind: :singleton
}
]
end
def editor_routes do
[
%{id: :dashboard, singleton: true, entity_tab: false, title: dgettext("ui", "Dashboard")},
%{id: :post, singleton: false, entity_tab: true, title: dgettext("ui", "Post")},
%{id: :media, singleton: false, entity_tab: true, title: dgettext("ui", "Media")},
%{id: :settings, singleton: true, entity_tab: false, title: dgettext("ui", "Settings")},
%{id: :style, singleton: true, entity_tab: false, title: dgettext("ui", "Style")},
%{id: :tags, singleton: true, entity_tab: false, title: dgettext("ui", "Tags")},
%{id: :chat, singleton: false, entity_tab: true, title: dgettext("ui", "Chat")},
%{id: :import, singleton: false, entity_tab: true, title: dgettext("ui", "Import")},
%{id: :menu_editor, singleton: true, entity_tab: false, title: dgettext("ui", "Menu")},
%{id: :metadata_diff, singleton: true, entity_tab: false, title: dgettext("ui", "Metadata Diff")},
%{id: :git_diff, singleton: false, entity_tab: true, title: dgettext("ui", "Git Diff")},
%{id: :documentation, singleton: true, entity_tab: false, title: dgettext("ui", "Documentation")},
%{id: :api_documentation, singleton: true, entity_tab: false, title: dgettext("ui", "API")},
%{id: :site_validation, singleton: true, entity_tab: false, title: dgettext("ui", "Site Validation")},
%{id: :translation_validation, singleton: true, entity_tab: false, title: dgettext("ui", "Translations")},
%{id: :scripts, singleton: false, entity_tab: true, title: dgettext("ui", "Script")},
%{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 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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@
"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"},
"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"},
"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"},

View File

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

View File

@@ -1,5 +1,5 @@
{% 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 %}
{% if lang.is_current %}
<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>
{% endif %}
{% endfor %}
<div class="blog-search-widget" aria-label="{{ 'render.search.ariaLabel' | i18n: language }}">
<button type="button" class="blog-search-toggle" data-blog-search-toggle 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="{{ 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">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
<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>
</nav>
@@ -27,15 +27,15 @@
}());
</script>
{% else %}
<div class="blog-search-standalone" aria-label="{{ 'render.search.ariaLabel' | i18n: language }}">
<button type="button" class="blog-search-toggle" data-blog-search-toggle 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="{{ 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">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
<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>
{% endif %}
{% endif %}

View File

@@ -22,8 +22,8 @@
data-blog-calendar-toggle
{% if calendar_initial_year %}data-blog-calendar-year="{{ calendar_initial_year }}"{% endif %}
{% if calendar_initial_month %}data-blog-calendar-month="{{ calendar_initial_month }}"{% endif %}
aria-label="{{ 'render.calendar.open' | i18n: language }}"
title="{{ 'render.calendar.open' | i18n: language }}"
aria-label="{{ labels.calendar_open_label }}"
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">
<rect x="3" y="5" width="18" height="16" rx="2" ry="2"></rect>
@@ -37,27 +37,27 @@
id="blog-calendar"
class="blog-calendar-panel"
data-blog-calendar-panel
data-i18n-loading="{{ 'render.calendar.loading' | i18n: language }}"
data-i18n-error="{{ 'render.calendar.error' | i18n: language }}"
data-i18n-loading="{{ labels.calendar_loading_label }}"
data-i18n-error="{{ labels.calendar_error_label }}"
hidden
>
<header class="blog-calendar-header">
<strong>{{ 'render.calendar.title' | i18n: language }}</strong>
<strong>{{ labels.calendar_title_label }}</strong>
<button
type="button"
class="blog-calendar-close"
data-blog-calendar-close
aria-label="{{ 'render.calendar.close' | i18n: language }}"
title="{{ 'render.calendar.close' | i18n: language }}"
aria-label="{{ labels.calendar_close_label }}"
title="{{ labels.calendar_close_label }}"
>
×
</button>
</header>
<div class="blog-calendar-content">
<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>
</section>
</li>
{% endif %}
</ul>
</ul>

View File

@@ -1,7 +1,7 @@
<nav class="blog-menu">
{% 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 %}
{% 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 %}
</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 %}
<body>
<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 show_archive_range_heading and min_date and max_date %}
{% 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>
{% 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 %}
{% else %}
{% if archive_context.kind == 'tag' or archive_context.kind == 'category' %}
<h1 class="archive-heading">{{ archive_context.name }}</h1>
{% 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">{{ 'render.archive' | i18n: language }} {{ month_key | i18n: language }} {{ archive_context.year }}</h1>
<h1 class="archive-heading">{{ labels.archive_label }} {{ archive_month_name }} {{ archive_context.year }}</h1>
{% 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 %}
{% assign day_month_key = 'render.month.' | append: archive_context.month %}
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ archive_context.day }}. {{ day_month_key | i18n: language }} {{ archive_context.year }}</h1>
<h1 class="archive-heading">{{ labels.archive_label }} {{ archive_context.day }}. {{ archive_month_name }} {{ archive_context.year }}</h1>
{% else %}
<h1 class="archive-heading">{{ page_title }}</h1>
{% 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 }}">
{% for day_block in day_blocks %}
@@ -80,15 +78,15 @@ version: 1
</section>
{% 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 %}
<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 %}
<span class="spacer"></span>
{% endif %}
{% 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 %}
<span class="spacer"></span>
{% 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 %}
<body>
<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>
{% 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 %}
<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 %}
<a class="single-post-taxonomy-bubble single-post-taxonomy-bubble-category" href="/category/{{ category | slugify | url_encode }}/">{{ category | escape }}</a>
{% endfor %}
@@ -29,8 +29,8 @@ version: 1
<div class="post">{{ post.content }}</div>
</article>
{% if backlinks.size > 0 %}
<div class="single-post-backlinks" aria-label="{{ 'render.backlinks.ariaLabel' | i18n: language }}">
<span class="single-post-backlinks-label">{{ 'render.backlinks.label' | i18n: language }}</span>
<div class="single-post-backlinks" aria-label="{{ labels.backlinks_label }}">
<span class="single-post-backlinks-label">{{ labels.linked_from_label }}</span>
{% for backlink in backlinks %}
<a class="single-post-taxonomy-bubble single-post-backlink-bubble" href="{{ backlink.path }}">{{ backlink.display_slug }}</a>
{% 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,12 +6,10 @@
<section class="not-found" data-template="not-found">
<article>
<h1>404</h1>
{% assign default_not_found_message = 'render.notFound.message' | i18n: language %}
{% assign default_not_found_back = 'render.notFound.back' | i18n: language %}
<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>
<p>{{ not_found_message }}</p>
<p><a href="/" role="button">{{ not_found_back_label }}</a></p>
</article>
</section>
</main>
</body>
</html>
</html>

View File

@@ -1,5 +1,5 @@
{% 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 %}
{% if lang.is_current %}
<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>
{% endif %}
{% endfor %}
<div class="blog-search-widget" aria-label="{{ 'render.search.ariaLabel' | i18n: language }}">
<button type="button" class="blog-search-toggle" data-blog-search-toggle 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="{{ 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">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
<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>
</nav>
@@ -27,15 +27,15 @@
}());
</script>
{% else %}
<div class="blog-search-standalone" aria-label="{{ 'render.search.ariaLabel' | i18n: language }}">
<button type="button" class="blog-search-toggle" data-blog-search-toggle 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="{{ 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">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
<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>
{% endif %}
{% endif %}

View File

@@ -22,8 +22,8 @@
data-blog-calendar-toggle
{% if calendar_initial_year %}data-blog-calendar-year="{{ calendar_initial_year }}"{% endif %}
{% if calendar_initial_month %}data-blog-calendar-month="{{ calendar_initial_month }}"{% endif %}
aria-label="{{ 'render.calendar.open' | i18n: language }}"
title="{{ 'render.calendar.open' | i18n: language }}"
aria-label="{{ labels.calendar_open_label }}"
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">
<rect x="3" y="5" width="18" height="16" rx="2" ry="2"></rect>
@@ -37,27 +37,27 @@
id="blog-calendar"
class="blog-calendar-panel"
data-blog-calendar-panel
data-i18n-loading="{{ 'render.calendar.loading' | i18n: language }}"
data-i18n-error="{{ 'render.calendar.error' | i18n: language }}"
data-i18n-loading="{{ labels.calendar_loading_label }}"
data-i18n-error="{{ labels.calendar_error_label }}"
hidden
>
<header class="blog-calendar-header">
<strong>{{ 'render.calendar.title' | i18n: language }}</strong>
<strong>{{ labels.calendar_title_label }}</strong>
<button
type="button"
class="blog-calendar-close"
data-blog-calendar-close
aria-label="{{ 'render.calendar.close' | i18n: language }}"
title="{{ 'render.calendar.close' | i18n: language }}"
aria-label="{{ labels.calendar_close_label }}"
title="{{ labels.calendar_close_label }}"
>
×
</button>
</header>
<div class="blog-calendar-content">
<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>
</section>
</li>
{% endif %}
</ul>
</ul>

View File

@@ -1,7 +1,7 @@
<nav class="blog-menu">
{% 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 %}
{% 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 %}
</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 %}
<body>
<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 show_archive_range_heading and min_date and max_date %}
{% 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>
{% 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 %}
{% else %}
{% if archive_context.kind == 'tag' or archive_context.kind == 'category' %}
<h1 class="archive-heading">{{ archive_context.name }}</h1>
{% 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">{{ 'render.archive' | i18n: language }} {{ month_key | i18n: language }} {{ archive_context.year }}</h1>
<h1 class="archive-heading">{{ labels.archive_label }} {{ archive_month_name }} {{ archive_context.year }}</h1>
{% 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 %}
{% assign day_month_key = 'render.month.' | append: archive_context.month %}
<h1 class="archive-heading">{{ 'render.archive' | i18n: language }} {{ archive_context.day }}. {{ day_month_key | i18n: language }} {{ archive_context.year }}</h1>
<h1 class="archive-heading">{{ labels.archive_label }} {{ archive_context.day }}. {{ archive_month_name }} {{ archive_context.year }}</h1>
{% else %}
<h1 class="archive-heading">{{ page_title }}</h1>
{% 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 }}">
{% for day_block in day_blocks %}
@@ -72,15 +70,15 @@
</section>
{% 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 %}
<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 %}
<span class="spacer"></span>
{% endif %}
{% 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 %}
<span class="spacer"></span>
{% endif %}
@@ -88,4 +86,4 @@
{% endif %}
</main>
</body>
</html>
</html>

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 %}
<body>
<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>
{% 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 %}
<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 %}
<a class="single-post-taxonomy-bubble single-post-taxonomy-bubble-category" href="/category/{{ category | slugify | url_encode }}/">{{ category | escape }}</a>
{% endfor %}
@@ -21,8 +21,8 @@
<div class="post">{{ post.content }}</div>
</article>
{% if backlinks.size > 0 %}
<div class="single-post-backlinks" aria-label="{{ 'render.backlinks.ariaLabel' | i18n: language }}">
<span class="single-post-backlinks-label">{{ 'render.backlinks.label' | i18n: language }}</span>
<div class="single-post-backlinks" aria-label="{{ labels.backlinks_label }}">
<span class="single-post-backlinks-label">{{ labels.linked_from_label }}</span>
{% for backlink in backlinks %}
<a class="single-post-taxonomy-bubble single-post-backlink-bubble" href="{{ backlink.path }}">{{ backlink.display_slug }}</a>
{% endfor %}
@@ -30,4 +30,4 @@
{% endif %}
</main>
</body>
</html>
</html>

View File

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

View File

@@ -263,7 +263,7 @@ defmodule BDS.UI.ShellTest do
assert css =~ "opacity: 1;"
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
)
@@ -271,7 +271,7 @@ defmodule BDS.UI.ShellTest do
assert template =~ ~s(class="quick-action-icon">🤖</span>)
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
)
@@ -383,7 +383,7 @@ defmodule BDS.UI.ShellTest do
assert post_editor_ex =~ "defp build_data(socket)"
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
)