feat: headless server mode with ExRatatui SSH daemon (issue #26, phase 1)

This commit is contained in:
2026-07-14 19:17:06 +02:00
parent 6ec61610f1
commit 700ed79c43
15 changed files with 539 additions and 158 deletions

View File

@@ -25,6 +25,8 @@ config :bds, :desktop,
window_min_size: {800, 600}, window_min_size: {800, 600},
title: "Blogging Desktop Server" title: "Blogging Desktop Server"
config :bds, :server, ssh_port: 2222
config :bds, BDS.Desktop.Endpoint, config :bds, BDS.Desktop.Endpoint,
url: [host: "127.0.0.1"], url: [host: "127.0.0.1"],
adapter: Bandit.PhoenixAdapter, adapter: Bandit.PhoenixAdapter,

View File

@@ -5,6 +5,15 @@ defmodule BDS.Application do
@compiled_env Application.compile_env(:bds, :current_env, Mix.env()) @compiled_env Application.compile_env(:bds, :current_env, Mix.env())
# Children that depend on the boot mode (issue #26): desktop mode runs the
# wx window shell, server mode runs headless behind the SSH daemon. Both
# start the loopback HTTP endpoint and the CLI sync watcher.
def mode_children(:server, _env) do
[{BDS.Desktop.Server, []}, BDS.CliSync.Watcher, BDS.Server]
end
def mode_children(:desktop, env), do: desktop_children(env)
def desktop_children(env \\ nil) def desktop_children(env \\ nil)
def desktop_children(:test) do def desktop_children(:test) do
@@ -41,7 +50,7 @@ defmodule BDS.Application do
{Task.Supervisor, name: BDS.Scripting.TaskSupervisor}, {Task.Supervisor, name: BDS.Scripting.TaskSupervisor},
BDS.Scripting.JobSupervisor, BDS.Scripting.JobSupervisor,
BDS.Embeddings.Index BDS.Embeddings.Index
] ++ embedding_children() ++ desktop_children(current_env()) ] ++ embedding_children() ++ mode_children(BDS.Server.mode(), current_env())
opts = [strategy: :one_for_one, name: BDS.Supervisor] opts = [strategy: :one_for_one, name: BDS.Supervisor]
Supervisor.start_link(children, opts) Supervisor.start_link(children, opts)

95
lib/bds/server.ex Normal file
View File

@@ -0,0 +1,95 @@
defmodule BDS.Server do
@moduledoc """
Headless server mode (issue #26).
Boots the app without any wx window and serves clients over a single SSH
daemon: TUI sessions run server-side via `ExRatatui.SSH` (only terminal
cells cross the wire), and GUI clients tunnel to the loopback HTTP
endpoint through the same daemon (`tcpip_tunnel_out`). Authentication is
public-key only; host key and `authorized_keys` live in the private app
data dir next to the database — never in the repo or a priv dir.
"""
@doc """
Resolves the boot mode from the `BDS_MODE` environment variable.
Anything other than `"server"` (case-insensitive) is desktop mode.
"""
@spec mode(String.t() | nil) :: :desktop | :server
def mode(value \\ System.get_env("BDS_MODE"))
def mode(value) when is_binary(value) do
if String.downcase(value) == "server", do: :server, else: :desktop
end
def mode(nil), do: :desktop
@doc "SSH key-material directory: `<data dir>/ssh` next to the database."
@spec ssh_dir() :: Path.t()
def ssh_dir do
Application.get_env(:bds, BDS.Repo)[:database]
|> Path.dirname()
|> Path.join("ssh")
end
@doc """
Ensures the SSH directory exists with an `authorized_keys` file (created
empty and chmod 600 on first boot; existing keys are never touched).
"""
@spec ensure_ssh_dir!(Path.t()) :: Path.t()
def ensure_ssh_dir!(dir) do
File.mkdir_p!(dir)
keys_path = Path.join(dir, "authorized_keys")
unless File.exists?(keys_path) do
File.write!(keys_path, "")
File.chmod!(keys_path, 0o600)
end
dir
end
@doc """
Options for `ExRatatui.SSH.Daemon`: serves `BDS.TUI`, public-key auth
only, and allows TCP/IP tunneling out so GUI clients can reach the
loopback HTTP endpoint through the SSH connection.
"""
@spec daemon_opts(Path.t()) :: keyword()
def daemon_opts(dir \\ ssh_dir()) do
dir = ensure_ssh_dir!(dir)
system_dir = ExRatatui.SSH.Daemon.ensure_host_key!(dir)
[
mod: BDS.TUI,
port: ssh_port(),
system_dir: system_dir,
user_dir: String.to_charlist(dir),
auth_methods: ~c"publickey",
tcpip_tunnel_out: true
]
end
@doc "SSH listen port: `BDS_SSH_PORT` env or `:bds, :server` config."
@spec ssh_port() :: pos_integer()
def ssh_port do
case System.get_env("BDS_SSH_PORT") do
value when is_binary(value) and value != "" -> String.to_integer(value)
_other -> Application.get_env(:bds, :server)[:ssh_port] || 2222
end
end
@doc false
# Child spec that defers all filesystem work (key generation) to process
# start, so building a children list never touches the data dir.
def child_spec(_opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, []},
type: :worker
}
end
@doc false
def start_link do
ExRatatui.SSH.Daemon.start_link(daemon_opts())
end
end

38
lib/bds/tui.ex Normal file
View File

@@ -0,0 +1,38 @@
defmodule BDS.TUI do
@moduledoc """
Terminal UI entry point (issue #26), served per SSH connection by
`ExRatatui.SSH.Daemon` in server mode and runnable locally.
Placeholder shell for now: renders the app banner while the workbench
renderer lands in a later phase. The UI locale is the server-side UI
language setting, same as the GUI.
"""
use ExRatatui.App
use Gettext, backend: BDS.Gettext
alias BDS.Desktop.UILocale
alias ExRatatui.Layout.Rect
alias ExRatatui.Widgets.Paragraph
@impl true
def mount(_opts) do
UILocale.put(BDS.Desktop.ShellData.ui_language())
{:ok, %{}}
end
@impl true
def render(_state, frame) do
text =
"bDS2 — " <>
dgettext("ui", "Blogging Desktop Server") <>
"\n\n" <>
dgettext("ui", "Press q to quit.")
[{%Paragraph{text: text}, %Rect{x: 0, y: 0, width: frame.width, height: frame.height}}]
end
@impl true
def handle_event(%ExRatatui.Event.Key{code: "q"}, state), do: {:stop, state}
def handle_event(_event, state), do: {:noreply, state}
end

View File

@@ -40,6 +40,7 @@ defmodule BDS.MixProject do
{:live_toast, "~> 0.8.0"}, {:live_toast, "~> 0.8.0"},
{:saxy, "~> 1.4"}, {:saxy, "~> 1.4"},
{:desktop, "~> 1.5"}, {:desktop, "~> 1.5"},
{:ex_ratatui, "~> 0.11"},
{:image, "~> 0.67"}, {:image, "~> 0.67"},
{:nx, "~> 0.10"}, {:nx, "~> 0.10"},
{:exla, "~> 0.10"}, {:exla, "~> 0.10"},

View File

@@ -25,6 +25,7 @@
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
"ex_dbus": {:hex, :ex_dbus, "0.1.4", "053df83d45b27ba0b9b6ef55a47253922069a3ace12a2a7dd30d3aff58301e17", [:mix], [{:dbus, "~> 0.8.0", [hex: :dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "d8baeaf465eab57b70a47b70e29fdfef6eb09ba110fc37176eebe6ac7874d6d5"}, "ex_dbus": {:hex, :ex_dbus, "0.1.4", "053df83d45b27ba0b9b6ef55a47253922069a3ace12a2a7dd30d3aff58301e17", [:mix], [{:dbus, "~> 0.8.0", [hex: :dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "d8baeaf465eab57b70a47b70e29fdfef6eb09ba110fc37176eebe6ac7874d6d5"},
"ex_doc": {:hex, :ex_doc, "0.40.3", "4a972ffe64bc07dc605af487e98fc19b72a4185f55ca031b94c0552d6071c1d9", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2756e357742fecd9749b489b85d67c9ce99c465f2e75728d9e6dc8d704b973de"}, "ex_doc": {:hex, :ex_doc, "0.40.3", "4a972ffe64bc07dc605af487e98fc19b72a4185f55ca031b94c0552d6071c1d9", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2756e357742fecd9749b489b85d67c9ce99c465f2e75728d9e6dc8d704b973de"},
"ex_ratatui": {:hex, :ex_ratatui, "0.11.1", "f3cc5c9827d957bc326d3b3b48338a927716fedc2575311d65a3af0dd03ed951", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b37b02b33e1e8f49874497d7b7f8872417b3807ec77d4bc1526c07c05c7e4cc9"},
"ex_sni": {:hex, :ex_sni, "0.2.9", "81f9421035dd3edb6d69f1a4dd5f53c7071b41628130d32ba5ab7bb4bfdc2da0", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_dbus, "~> 0.1", [hex: :ex_dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "921d67d913765ed20ea8354fd1798dabc957bf66990a6842d6aaa7cd5ee5bc06"}, "ex_sni": {:hex, :ex_sni, "0.2.9", "81f9421035dd3edb6d69f1a4dd5f53c7071b41628130d32ba5ab7bb4bfdc2da0", [:mix], [{:debouncer, "~> 0.1", [hex: :debouncer, repo: "hexpm", optional: false]}, {:ex_dbus, "~> 0.1", [hex: :ex_dbus, repo: "hexpm", optional: false]}, {:saxy, "~> 1.4.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "921d67d913765ed20ea8354fd1798dabc957bf66990a6842d6aaa7cd5ee5bc06"},
"ex_stemmers": {:hex, :ex_stemmers, "0.1.0", "63a84ae3a6f0c28a1d75768411f0ae15cfe8462fb70589b60977aa1b04c9372d", [:mix], [{:rustler, "~> 0.32.1", [hex: :rustler, repo: "hexpm", optional: false]}], "hexpm", "498826e2188e502f41d1a15f3d90e7738f0d94747e197367f03a2a44c09167c0"}, "ex_stemmers": {:hex, :ex_stemmers, "0.1.0", "63a84ae3a6f0c28a1d75768411f0ae15cfe8462fb70589b60977aa1b04c9372d", [:mix], [{:rustler, "~> 0.32.1", [hex: :rustler, repo: "hexpm", optional: false]}], "hexpm", "498826e2188e502f41d1a15f3d90e7738f0d94747e197367f03a2a44c09167c0"},
"exla": {:hex, :exla, "0.10.0", "93e7d75a774fbc06ce05b96de20c4b01bda413b315238cb3c727c09a05d2bc3a", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:nx, "~> 0.10.0", [hex: :nx, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:xla, "~> 0.9.0", [hex: :xla, repo: "hexpm", optional: false]}], "hexpm", "16fffdb64667d7f0a3bc683fdcd2792b143a9b345e4b1f1d5cd50330c63d8119"}, "exla": {:hex, :exla, "0.10.0", "93e7d75a774fbc06ce05b96de20c4b01bda413b315238cb3c727c09a05d2bc3a", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:nx, "~> 0.10.0", [hex: :nx, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:xla, "~> 0.9.0", [hex: :xla, repo: "hexpm", optional: false]}], "hexpm", "16fffdb64667d7f0a3bc683fdcd2792b143a9b345e4b1f1d5cd50330c63d8119"},

View File

@@ -71,8 +71,8 @@ msgid "AI Assistant"
msgstr "KI-Assistent" msgstr "KI-Assistent"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "AI Settings" msgid "AI Settings"
msgstr "KI-Einstellungen" msgstr "KI-Einstellungen"
@@ -336,7 +336,7 @@ msgstr "Autor"
msgid "Auto" msgid "Auto"
msgstr "Automatisch" msgstr "Automatisch"
#: lib/bds/desktop/shell_live.ex:448 #: lib/bds/desktop/shell_live.ex:442
#: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/chat_editor.ex:234
#: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:171
#: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:364
@@ -486,7 +486,7 @@ msgstr "Kategorie-Standards, Render-Flags und Template-Zuordnung"
msgid "Category name is required" msgid "Category name is required"
msgstr "Kategoriename ist erforderlich" msgstr "Kategoriename ist erforderlich"
#: lib/bds/desktop/shell_live.ex:771 #: lib/bds/desktop/shell_live.ex:780
#: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:87
#: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:233
#: lib/bds/desktop/shell_live/chat_editor.ex:323 #: lib/bds/desktop/shell_live/chat_editor.ex:323
@@ -1063,7 +1063,7 @@ msgstr "Galerie"
msgid "Generate Site" msgid "Generate Site"
msgstr "Website generieren" msgstr "Website generieren"
#: lib/bds/desktop/shell_live.ex:772 #: lib/bds/desktop/shell_live.ex:781
#: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/desktop/shell_live/socket_state.ex:109
#: lib/bds/ui/sidebar.ex:789 #: lib/bds/ui/sidebar.ex:789
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@@ -1077,7 +1077,7 @@ msgid "Git Diff"
msgstr "Git-Diff" msgstr "Git-Diff"
#: lib/bds/desktop/shell_data.ex:226 #: lib/bds/desktop/shell_data.ex:226
#: lib/bds/desktop/shell_live.ex:768 #: lib/bds/desktop/shell_live.ex:777
#: lib/bds/desktop/shell_live/panel_renderer.ex:171 #: lib/bds/desktop/shell_live/panel_renderer.ex:171
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Git Log" msgid "Git Log"
@@ -1200,9 +1200,9 @@ msgstr "Importdefinitionen"
msgid "Import failed: %{error}" msgid "Import failed: %{error}"
msgstr "Import fehlgeschlagen: %{error}" msgstr "Import fehlgeschlagen: %{error}"
#: lib/bds/desktop/shell_live.ex:598 #: lib/bds/desktop/shell_live.ex:605
#: lib/bds/desktop/shell_live.ex:884 #: lib/bds/desktop/shell_live.ex:893
#: lib/bds/desktop/shell_live.ex:890 #: lib/bds/desktop/shell_live.ex:899
#: lib/bds/desktop/shell_live/sidebar_create.ex:47 #: lib/bds/desktop/shell_live/sidebar_create.ex:47
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Import media" msgid "Import media"
@@ -1784,7 +1784,7 @@ msgstr "Sonstige"
msgid "Other (%{count})" msgid "Other (%{count})"
msgstr "Andere (%{count})" msgstr "Andere (%{count})"
#: lib/bds/desktop/shell_live.ex:767 #: lib/bds/desktop/shell_live.ex:776
#: lib/bds/desktop/shell_live/panel_renderer.ex:83 #: lib/bds/desktop/shell_live/panel_renderer.ex:83
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Output" msgid "Output"
@@ -2538,7 +2538,7 @@ msgstr "Schlagwortname"
msgid "Tags" msgid "Tags"
msgstr "Tags" msgstr "Tags"
#: lib/bds/desktop/shell_live.ex:766 #: lib/bds/desktop/shell_live.ex:775
#: lib/bds/desktop/shell_live/panel_renderer.ex:54 #: lib/bds/desktop/shell_live/panel_renderer.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tasks" msgid "Tasks"
@@ -3218,12 +3218,12 @@ msgstr "Willkommen beim KI-Assistenten"
msgid "Comparing database and filesystem metadata" msgid "Comparing database and filesystem metadata"
msgstr "Vergleicht Datenbank- und Dateisystem-Metadaten" msgstr "Vergleicht Datenbank- und Dateisystem-Metadaten"
#: lib/bds/desktop/shell_live.ex:674 #: lib/bds/desktop/shell_live.ex:681
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{count} images to post" msgid "Added %{count} images to post"
msgstr "%{count} Bilder zum Beitrag hinzugefügt" msgstr "%{count} Bilder zum Beitrag hinzugefügt"
#: lib/bds/desktop/shell_live.ex:642 #: lib/bds/desktop/shell_live.ex:649
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{title}" msgid "Added %{title}"
msgstr "%{title} hinzugefügt" msgstr "%{title} hinzugefügt"
@@ -3243,18 +3243,18 @@ msgstr "Endbenutzer-Anleitung für redaktionelle Arbeitsabläufe, Medien, Vorlag
msgid "Image Import Concurrency" msgid "Image Import Concurrency"
msgstr "Gleichzeitige Bildimporte" msgstr "Gleichzeitige Bildimporte"
#: lib/bds/desktop/shell_live.ex:447 #: lib/bds/desktop/shell_live.ex:441
#: lib/bds/desktop/shell_live.ex:460 #: lib/bds/desktop/shell_live.ex:454
#: lib/bds/desktop/shell_live.ex:641 #: lib/bds/desktop/shell_live.ex:648
#: lib/bds/desktop/shell_live.ex:673 #: lib/bds/desktop/shell_live.ex:680
#: lib/bds/desktop/shell_live.ex:684 #: lib/bds/desktop/shell_live.ex:691
#: lib/bds/desktop/shell_live.ex:695 #: lib/bds/desktop/shell_live.ex:702
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Add Gallery Images" msgid "Add Gallery Images"
msgstr "Galerie-Bilder hinzufügen" msgstr "Galerie-Bilder hinzufügen"
#: lib/bds/desktop/shell_live.ex:696 #: lib/bds/desktop/shell_live.ex:703
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Failed to process %{path}: %{reason}" msgid "Failed to process %{path}: %{reason}"
msgstr "%{path} konnte nicht verarbeitet werden: %{reason}" msgstr "%{path} konnte nicht verarbeitet werden: %{reason}"
@@ -3445,12 +3445,12 @@ msgstr "umbenannt"
msgid "untracked" msgid "untracked"
msgstr "nicht verfolgt" msgstr "nicht verfolgt"
#: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live.ex:801
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Blogmark" msgid "Blogmark"
msgstr "Blogmark" msgstr "Blogmark"
#: lib/bds/desktop/shell_live.ex:835 #: lib/bds/desktop/shell_live.ex:844
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Open a project before importing a blogmark." msgid "Open a project before importing a blogmark."
msgstr "Öffnen Sie ein Projekt, bevor Sie ein Blogmark importieren." msgstr "Öffnen Sie ein Projekt, bevor Sie ein Blogmark importieren."
@@ -3491,7 +3491,7 @@ msgstr "Loeschen"
msgid "Failed to copy bookmarklet to clipboard" msgid "Failed to copy bookmarklet to clipboard"
msgstr "Bookmarklet konnte nicht in die Zwischenablage kopiert werden" msgstr "Bookmarklet konnte nicht in die Zwischenablage kopiert werden"
#: lib/bds/desktop/shell_live.ex:804 #: lib/bds/desktop/shell_live.ex:813
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The project this blogmark targets does not exist here." msgid "The project this blogmark targets does not exist here."
msgstr "Das Projekt, auf das dieses Blogmark verweist, existiert hier nicht." msgstr "Das Projekt, auf das dieses Blogmark verweist, existiert hier nicht."
@@ -3578,12 +3578,12 @@ msgstr "KI-Einstellungen gespeichert."
msgid "Could not load models" msgid "Could not load models"
msgstr "Modelle konnten nicht geladen werden" msgstr "Modelle konnten nicht geladen werden"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not save AI settings" msgid "Could not save AI settings"
msgstr "KI-Einstellungen konnten nicht gespeichert werden" msgstr "KI-Einstellungen konnten nicht gespeichert werden"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Loaded %{count} models." msgid "Loaded %{count} models."
msgstr "%{count} Modelle geladen." msgstr "%{count} Modelle geladen."
@@ -3593,7 +3593,17 @@ msgstr "%{count} Modelle geladen."
msgid "OK" msgid "OK"
msgstr "OK" msgstr "OK"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The endpoint returned no models. You can still type a model name manually." msgid "The endpoint returned no models. You can still type a model name manually."
msgstr "Der Endpunkt hat keine Modelle zurückgegeben. Sie können einen Modellnamen weiterhin manuell eingeben." msgstr "Der Endpunkt hat keine Modelle zurückgegeben. Sie können einen Modellnamen weiterhin manuell eingeben."
#: lib/bds/tui.ex:28
#, elixir-autogen, elixir-format
msgid "Blogging Desktop Server"
msgstr "Blogging Desktop Server"
#: lib/bds/tui.ex:30
#, elixir-autogen, elixir-format
msgid "Press q to quit."
msgstr "Drücken Sie q zum Beenden."

View File

@@ -71,8 +71,8 @@ msgid "AI Assistant"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "AI Settings" msgid "AI Settings"
msgstr "" msgstr ""
@@ -336,7 +336,7 @@ msgstr ""
msgid "Auto" msgid "Auto"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:448 #: lib/bds/desktop/shell_live.ex:442
#: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/chat_editor.ex:234
#: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:171
#: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:364
@@ -486,7 +486,7 @@ msgstr ""
msgid "Category name is required" msgid "Category name is required"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:771 #: lib/bds/desktop/shell_live.ex:780
#: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:87
#: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:233
#: lib/bds/desktop/shell_live/chat_editor.ex:323 #: lib/bds/desktop/shell_live/chat_editor.ex:323
@@ -1063,7 +1063,7 @@ msgstr ""
msgid "Generate Site" msgid "Generate Site"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:772 #: lib/bds/desktop/shell_live.ex:781
#: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/desktop/shell_live/socket_state.ex:109
#: lib/bds/ui/sidebar.ex:789 #: lib/bds/ui/sidebar.ex:789
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@@ -1077,7 +1077,7 @@ msgid "Git Diff"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_data.ex:226 #: lib/bds/desktop/shell_data.ex:226
#: lib/bds/desktop/shell_live.ex:768 #: lib/bds/desktop/shell_live.ex:777
#: lib/bds/desktop/shell_live/panel_renderer.ex:171 #: lib/bds/desktop/shell_live/panel_renderer.ex:171
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Git Log" msgid "Git Log"
@@ -1200,9 +1200,9 @@ msgstr ""
msgid "Import failed: %{error}" msgid "Import failed: %{error}"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:598 #: lib/bds/desktop/shell_live.ex:605
#: lib/bds/desktop/shell_live.ex:884 #: lib/bds/desktop/shell_live.ex:893
#: lib/bds/desktop/shell_live.ex:890 #: lib/bds/desktop/shell_live.ex:899
#: lib/bds/desktop/shell_live/sidebar_create.ex:47 #: lib/bds/desktop/shell_live/sidebar_create.ex:47
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Import media" msgid "Import media"
@@ -1784,7 +1784,7 @@ msgstr ""
msgid "Other (%{count})" msgid "Other (%{count})"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:767 #: lib/bds/desktop/shell_live.ex:776
#: lib/bds/desktop/shell_live/panel_renderer.ex:83 #: lib/bds/desktop/shell_live/panel_renderer.ex:83
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Output" msgid "Output"
@@ -2538,7 +2538,7 @@ msgstr ""
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:766 #: lib/bds/desktop/shell_live.ex:775
#: lib/bds/desktop/shell_live/panel_renderer.ex:54 #: lib/bds/desktop/shell_live/panel_renderer.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tasks" msgid "Tasks"
@@ -3218,12 +3218,12 @@ msgstr ""
msgid "Comparing database and filesystem metadata" msgid "Comparing database and filesystem metadata"
msgstr "Comparing database and filesystem metadata" msgstr "Comparing database and filesystem metadata"
#: lib/bds/desktop/shell_live.ex:674 #: lib/bds/desktop/shell_live.ex:681
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{count} images to post" msgid "Added %{count} images to post"
msgstr "Added %{count} images to post" msgstr "Added %{count} images to post"
#: lib/bds/desktop/shell_live.ex:642 #: lib/bds/desktop/shell_live.ex:649
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{title}" msgid "Added %{title}"
msgstr "Added %{title}" msgstr "Added %{title}"
@@ -3243,18 +3243,18 @@ msgstr ""
msgid "Image Import Concurrency" msgid "Image Import Concurrency"
msgstr "Image Import Concurrency" msgstr "Image Import Concurrency"
#: lib/bds/desktop/shell_live.ex:447 #: lib/bds/desktop/shell_live.ex:441
#: lib/bds/desktop/shell_live.ex:460 #: lib/bds/desktop/shell_live.ex:454
#: lib/bds/desktop/shell_live.ex:641 #: lib/bds/desktop/shell_live.ex:648
#: lib/bds/desktop/shell_live.ex:673 #: lib/bds/desktop/shell_live.ex:680
#: lib/bds/desktop/shell_live.ex:684 #: lib/bds/desktop/shell_live.ex:691
#: lib/bds/desktop/shell_live.ex:695 #: lib/bds/desktop/shell_live.ex:702
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Add Gallery Images" msgid "Add Gallery Images"
msgstr "Add Gallery Images" msgstr "Add Gallery Images"
#: lib/bds/desktop/shell_live.ex:696 #: lib/bds/desktop/shell_live.ex:703
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Failed to process %{path}: %{reason}" msgid "Failed to process %{path}: %{reason}"
msgstr "Failed to process %{path}: %{reason}" msgstr "Failed to process %{path}: %{reason}"
@@ -3445,12 +3445,12 @@ msgstr ""
msgid "untracked" msgid "untracked"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live.ex:801
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
msgid "Blogmark" msgid "Blogmark"
msgstr "Blogmark" msgstr "Blogmark"
#: lib/bds/desktop/shell_live.ex:835 #: lib/bds/desktop/shell_live.ex:844
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Open a project before importing a blogmark." msgid "Open a project before importing a blogmark."
msgstr "" msgstr ""
@@ -3491,7 +3491,7 @@ msgstr ""
msgid "Failed to copy bookmarklet to clipboard" msgid "Failed to copy bookmarklet to clipboard"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:804 #: lib/bds/desktop/shell_live.ex:813
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The project this blogmark targets does not exist here." msgid "The project this blogmark targets does not exist here."
msgstr "" msgstr ""
@@ -3578,12 +3578,12 @@ msgstr ""
msgid "Could not load models" msgid "Could not load models"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not save AI settings" msgid "Could not save AI settings"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Loaded %{count} models." msgid "Loaded %{count} models."
msgstr "" msgstr ""
@@ -3593,7 +3593,17 @@ msgstr ""
msgid "OK" msgid "OK"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The endpoint returned no models. You can still type a model name manually." msgid "The endpoint returned no models. You can still type a model name manually."
msgstr "" msgstr ""
#: lib/bds/tui.ex:28
#, elixir-autogen, elixir-format
msgid "Blogging Desktop Server"
msgstr ""
#: lib/bds/tui.ex:30
#, elixir-autogen, elixir-format
msgid "Press q to quit."
msgstr ""

View File

@@ -71,8 +71,8 @@ msgid "AI Assistant"
msgstr "Asistente de IA" msgstr "Asistente de IA"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "AI Settings" msgid "AI Settings"
msgstr "Configuración de IA" msgstr "Configuración de IA"
@@ -336,7 +336,7 @@ msgstr "Autor"
msgid "Auto" msgid "Auto"
msgstr "Automático" msgstr "Automático"
#: lib/bds/desktop/shell_live.ex:448 #: lib/bds/desktop/shell_live.ex:442
#: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/chat_editor.ex:234
#: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:171
#: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:364
@@ -486,7 +486,7 @@ msgstr "Valores predeterminados de categoría, opciones de renderizado y conexi
msgid "Category name is required" msgid "Category name is required"
msgstr "El nombre de la categoría es obligatorio" msgstr "El nombre de la categoría es obligatorio"
#: lib/bds/desktop/shell_live.ex:771 #: lib/bds/desktop/shell_live.ex:780
#: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:87
#: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:233
#: lib/bds/desktop/shell_live/chat_editor.ex:323 #: lib/bds/desktop/shell_live/chat_editor.ex:323
@@ -1063,7 +1063,7 @@ msgstr "Galeria"
msgid "Generate Site" msgid "Generate Site"
msgstr "Generar sitio" msgstr "Generar sitio"
#: lib/bds/desktop/shell_live.ex:772 #: lib/bds/desktop/shell_live.ex:781
#: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/desktop/shell_live/socket_state.ex:109
#: lib/bds/ui/sidebar.ex:789 #: lib/bds/ui/sidebar.ex:789
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@@ -1077,7 +1077,7 @@ msgid "Git Diff"
msgstr "Diff de Git" msgstr "Diff de Git"
#: lib/bds/desktop/shell_data.ex:226 #: lib/bds/desktop/shell_data.ex:226
#: lib/bds/desktop/shell_live.ex:768 #: lib/bds/desktop/shell_live.ex:777
#: lib/bds/desktop/shell_live/panel_renderer.ex:171 #: lib/bds/desktop/shell_live/panel_renderer.ex:171
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Git Log" msgid "Git Log"
@@ -1200,9 +1200,9 @@ msgstr "Definiciones de importación"
msgid "Import failed: %{error}" msgid "Import failed: %{error}"
msgstr "La importación falló: %{error}" msgstr "La importación falló: %{error}"
#: lib/bds/desktop/shell_live.ex:598 #: lib/bds/desktop/shell_live.ex:605
#: lib/bds/desktop/shell_live.ex:884 #: lib/bds/desktop/shell_live.ex:893
#: lib/bds/desktop/shell_live.ex:890 #: lib/bds/desktop/shell_live.ex:899
#: lib/bds/desktop/shell_live/sidebar_create.ex:47 #: lib/bds/desktop/shell_live/sidebar_create.ex:47
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Import media" msgid "Import media"
@@ -1784,7 +1784,7 @@ msgstr "Otros"
msgid "Other (%{count})" msgid "Other (%{count})"
msgstr "Otros (%{count})" msgstr "Otros (%{count})"
#: lib/bds/desktop/shell_live.ex:767 #: lib/bds/desktop/shell_live.ex:776
#: lib/bds/desktop/shell_live/panel_renderer.ex:83 #: lib/bds/desktop/shell_live/panel_renderer.ex:83
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Output" msgid "Output"
@@ -2538,7 +2538,7 @@ msgstr "Nombre de la etiqueta"
msgid "Tags" msgid "Tags"
msgstr "Etiquetas" msgstr "Etiquetas"
#: lib/bds/desktop/shell_live.ex:766 #: lib/bds/desktop/shell_live.ex:775
#: lib/bds/desktop/shell_live/panel_renderer.ex:54 #: lib/bds/desktop/shell_live/panel_renderer.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tasks" msgid "Tasks"
@@ -3218,12 +3218,12 @@ msgstr "Bienvenido al asistente de IA"
msgid "Comparing database and filesystem metadata" msgid "Comparing database and filesystem metadata"
msgstr "Comparando metadatos de la base de datos y del sistema de archivos" msgstr "Comparando metadatos de la base de datos y del sistema de archivos"
#: lib/bds/desktop/shell_live.ex:674 #: lib/bds/desktop/shell_live.ex:681
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{count} images to post" msgid "Added %{count} images to post"
msgstr "%{count} imágenes añadidas a la publicación" msgstr "%{count} imágenes añadidas a la publicación"
#: lib/bds/desktop/shell_live.ex:642 #: lib/bds/desktop/shell_live.ex:649
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{title}" msgid "Added %{title}"
msgstr "%{title} añadido" msgstr "%{title} añadido"
@@ -3243,18 +3243,18 @@ msgstr "Guía del usuario para flujos editoriales, medios, plantillas, traducci
msgid "Image Import Concurrency" msgid "Image Import Concurrency"
msgstr "Importación simultánea de imágenes" msgstr "Importación simultánea de imágenes"
#: lib/bds/desktop/shell_live.ex:447 #: lib/bds/desktop/shell_live.ex:441
#: lib/bds/desktop/shell_live.ex:460 #: lib/bds/desktop/shell_live.ex:454
#: lib/bds/desktop/shell_live.ex:641 #: lib/bds/desktop/shell_live.ex:648
#: lib/bds/desktop/shell_live.ex:673 #: lib/bds/desktop/shell_live.ex:680
#: lib/bds/desktop/shell_live.ex:684 #: lib/bds/desktop/shell_live.ex:691
#: lib/bds/desktop/shell_live.ex:695 #: lib/bds/desktop/shell_live.ex:702
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Add Gallery Images" msgid "Add Gallery Images"
msgstr "Añadir imágenes a la galería" msgstr "Añadir imágenes a la galería"
#: lib/bds/desktop/shell_live.ex:696 #: lib/bds/desktop/shell_live.ex:703
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Failed to process %{path}: %{reason}" msgid "Failed to process %{path}: %{reason}"
msgstr "No se pudo procesar %{path}: %{reason}" msgstr "No se pudo procesar %{path}: %{reason}"
@@ -3445,12 +3445,12 @@ msgstr "renombrado"
msgid "untracked" msgid "untracked"
msgstr "sin seguimiento" msgstr "sin seguimiento"
#: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live.ex:801
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Blogmark" msgid "Blogmark"
msgstr "Blogmark" msgstr "Blogmark"
#: lib/bds/desktop/shell_live.ex:835 #: lib/bds/desktop/shell_live.ex:844
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Open a project before importing a blogmark." msgid "Open a project before importing a blogmark."
msgstr "Abre un proyecto antes de importar un blogmark." msgstr "Abre un proyecto antes de importar un blogmark."
@@ -3491,7 +3491,7 @@ msgstr "Eliminar"
msgid "Failed to copy bookmarklet to clipboard" msgid "Failed to copy bookmarklet to clipboard"
msgstr "Error al copiar el bookmarklet al portapapeles" msgstr "Error al copiar el bookmarklet al portapapeles"
#: lib/bds/desktop/shell_live.ex:804 #: lib/bds/desktop/shell_live.ex:813
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The project this blogmark targets does not exist here." msgid "The project this blogmark targets does not exist here."
msgstr "El proyecto al que apunta este blogmark no existe aquí." msgstr "El proyecto al que apunta este blogmark no existe aquí."
@@ -3578,12 +3578,12 @@ msgstr "Configuración de IA guardada."
msgid "Could not load models" msgid "Could not load models"
msgstr "No se pudieron cargar los modelos" msgstr "No se pudieron cargar los modelos"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not save AI settings" msgid "Could not save AI settings"
msgstr "No se pudo guardar la configuración de IA" msgstr "No se pudo guardar la configuración de IA"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Loaded %{count} models." msgid "Loaded %{count} models."
msgstr "%{count} modelos cargados." msgstr "%{count} modelos cargados."
@@ -3593,7 +3593,17 @@ msgstr "%{count} modelos cargados."
msgid "OK" msgid "OK"
msgstr "OK" msgstr "OK"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The endpoint returned no models. You can still type a model name manually." msgid "The endpoint returned no models. You can still type a model name manually."
msgstr "El endpoint no devolvió ningún modelo. Aún puedes escribir el nombre de un modelo manualmente." msgstr "El endpoint no devolvió ningún modelo. Aún puedes escribir el nombre de un modelo manualmente."
#: lib/bds/tui.ex:28
#, elixir-autogen, elixir-format
msgid "Blogging Desktop Server"
msgstr "Blogging Desktop Server"
#: lib/bds/tui.ex:30
#, elixir-autogen, elixir-format
msgid "Press q to quit."
msgstr "Pulsa q para salir."

View File

@@ -71,8 +71,8 @@ msgid "AI Assistant"
msgstr "Assistant IA" msgstr "Assistant IA"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "AI Settings" msgid "AI Settings"
msgstr "Paramètres IA" msgstr "Paramètres IA"
@@ -336,7 +336,7 @@ msgstr "Auteur"
msgid "Auto" msgid "Auto"
msgstr "Automatique" msgstr "Automatique"
#: lib/bds/desktop/shell_live.ex:448 #: lib/bds/desktop/shell_live.ex:442
#: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/chat_editor.ex:234
#: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:171
#: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:364
@@ -486,7 +486,7 @@ msgstr "Valeurs par défaut des catégories, options de rendu et liaison des mod
msgid "Category name is required" msgid "Category name is required"
msgstr "Le nom de la catégorie est requis" msgstr "Le nom de la catégorie est requis"
#: lib/bds/desktop/shell_live.ex:771 #: lib/bds/desktop/shell_live.ex:780
#: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:87
#: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:233
#: lib/bds/desktop/shell_live/chat_editor.ex:323 #: lib/bds/desktop/shell_live/chat_editor.ex:323
@@ -1063,7 +1063,7 @@ msgstr "Galerie"
msgid "Generate Site" msgid "Generate Site"
msgstr "Générer le site" msgstr "Générer le site"
#: lib/bds/desktop/shell_live.ex:772 #: lib/bds/desktop/shell_live.ex:781
#: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/desktop/shell_live/socket_state.ex:109
#: lib/bds/ui/sidebar.ex:789 #: lib/bds/ui/sidebar.ex:789
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@@ -1077,7 +1077,7 @@ msgid "Git Diff"
msgstr "Diff Git" msgstr "Diff Git"
#: lib/bds/desktop/shell_data.ex:226 #: lib/bds/desktop/shell_data.ex:226
#: lib/bds/desktop/shell_live.ex:768 #: lib/bds/desktop/shell_live.ex:777
#: lib/bds/desktop/shell_live/panel_renderer.ex:171 #: lib/bds/desktop/shell_live/panel_renderer.ex:171
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Git Log" msgid "Git Log"
@@ -1200,9 +1200,9 @@ msgstr "Définitions dimport"
msgid "Import failed: %{error}" msgid "Import failed: %{error}"
msgstr "Échec de limport : %{error}" msgstr "Échec de limport : %{error}"
#: lib/bds/desktop/shell_live.ex:598 #: lib/bds/desktop/shell_live.ex:605
#: lib/bds/desktop/shell_live.ex:884 #: lib/bds/desktop/shell_live.ex:893
#: lib/bds/desktop/shell_live.ex:890 #: lib/bds/desktop/shell_live.ex:899
#: lib/bds/desktop/shell_live/sidebar_create.ex:47 #: lib/bds/desktop/shell_live/sidebar_create.ex:47
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Import media" msgid "Import media"
@@ -1784,7 +1784,7 @@ msgstr "Autre"
msgid "Other (%{count})" msgid "Other (%{count})"
msgstr "Autres (%{count})" msgstr "Autres (%{count})"
#: lib/bds/desktop/shell_live.ex:767 #: lib/bds/desktop/shell_live.ex:776
#: lib/bds/desktop/shell_live/panel_renderer.ex:83 #: lib/bds/desktop/shell_live/panel_renderer.ex:83
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Output" msgid "Output"
@@ -2538,7 +2538,7 @@ msgstr "Nom du mot-clé"
msgid "Tags" msgid "Tags"
msgstr "Tags" msgstr "Tags"
#: lib/bds/desktop/shell_live.ex:766 #: lib/bds/desktop/shell_live.ex:775
#: lib/bds/desktop/shell_live/panel_renderer.ex:54 #: lib/bds/desktop/shell_live/panel_renderer.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tasks" msgid "Tasks"
@@ -3218,12 +3218,12 @@ msgstr "Bienvenue dans lassistant IA"
msgid "Comparing database and filesystem metadata" msgid "Comparing database and filesystem metadata"
msgstr "Comparaison des métadonnées entre la base et le système de fichiers" msgstr "Comparaison des métadonnées entre la base et le système de fichiers"
#: lib/bds/desktop/shell_live.ex:674 #: lib/bds/desktop/shell_live.ex:681
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{count} images to post" msgid "Added %{count} images to post"
msgstr "%{count} images ajoutées à l'article" msgstr "%{count} images ajoutées à l'article"
#: lib/bds/desktop/shell_live.ex:642 #: lib/bds/desktop/shell_live.ex:649
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{title}" msgid "Added %{title}"
msgstr "%{title} ajouté" msgstr "%{title} ajouté"
@@ -3243,18 +3243,18 @@ msgstr "Guide utilisateur pour les flux éditoriaux, médias, modèles, traducti
msgid "Image Import Concurrency" msgid "Image Import Concurrency"
msgstr "Importation simultanée d'images" msgstr "Importation simultanée d'images"
#: lib/bds/desktop/shell_live.ex:447 #: lib/bds/desktop/shell_live.ex:441
#: lib/bds/desktop/shell_live.ex:460 #: lib/bds/desktop/shell_live.ex:454
#: lib/bds/desktop/shell_live.ex:641 #: lib/bds/desktop/shell_live.ex:648
#: lib/bds/desktop/shell_live.ex:673 #: lib/bds/desktop/shell_live.ex:680
#: lib/bds/desktop/shell_live.ex:684 #: lib/bds/desktop/shell_live.ex:691
#: lib/bds/desktop/shell_live.ex:695 #: lib/bds/desktop/shell_live.ex:702
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Add Gallery Images" msgid "Add Gallery Images"
msgstr "Ajouter des images à la galerie" msgstr "Ajouter des images à la galerie"
#: lib/bds/desktop/shell_live.ex:696 #: lib/bds/desktop/shell_live.ex:703
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Failed to process %{path}: %{reason}" msgid "Failed to process %{path}: %{reason}"
msgstr "Impossible de traiter %{path} : %{reason}" msgstr "Impossible de traiter %{path} : %{reason}"
@@ -3445,12 +3445,12 @@ msgstr "renommé"
msgid "untracked" msgid "untracked"
msgstr "non suivi" msgstr "non suivi"
#: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live.ex:801
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Blogmark" msgid "Blogmark"
msgstr "Blogmark" msgstr "Blogmark"
#: lib/bds/desktop/shell_live.ex:835 #: lib/bds/desktop/shell_live.ex:844
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Open a project before importing a blogmark." msgid "Open a project before importing a blogmark."
msgstr "Ouvrez un projet avant dimporter un blogmark." msgstr "Ouvrez un projet avant dimporter un blogmark."
@@ -3491,7 +3491,7 @@ msgstr "Supprimer"
msgid "Failed to copy bookmarklet to clipboard" msgid "Failed to copy bookmarklet to clipboard"
msgstr "Échec de la copie du bookmarklet dans le presse-papiers" msgstr "Échec de la copie du bookmarklet dans le presse-papiers"
#: lib/bds/desktop/shell_live.ex:804 #: lib/bds/desktop/shell_live.ex:813
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The project this blogmark targets does not exist here." msgid "The project this blogmark targets does not exist here."
msgstr "Le projet ciblé par ce blogmark n'existe pas ici." msgstr "Le projet ciblé par ce blogmark n'existe pas ici."
@@ -3578,12 +3578,12 @@ msgstr "Paramètres IA enregistrés."
msgid "Could not load models" msgid "Could not load models"
msgstr "Impossible de charger les modèles" msgstr "Impossible de charger les modèles"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not save AI settings" msgid "Could not save AI settings"
msgstr "Impossible d'enregistrer les paramètres IA" msgstr "Impossible d'enregistrer les paramètres IA"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Loaded %{count} models." msgid "Loaded %{count} models."
msgstr "%{count} modèles chargés." msgstr "%{count} modèles chargés."
@@ -3593,7 +3593,17 @@ msgstr "%{count} modèles chargés."
msgid "OK" msgid "OK"
msgstr "OK" msgstr "OK"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The endpoint returned no models. You can still type a model name manually." msgid "The endpoint returned no models. You can still type a model name manually."
msgstr "Le point de terminaison n'a renvoyé aucun modèle. Vous pouvez toujours saisir un nom de modèle manuellement." msgstr "Le point de terminaison n'a renvoyé aucun modèle. Vous pouvez toujours saisir un nom de modèle manuellement."
#: lib/bds/tui.ex:28
#, elixir-autogen, elixir-format
msgid "Blogging Desktop Server"
msgstr "Blogging Desktop Server"
#: lib/bds/tui.ex:30
#, elixir-autogen, elixir-format
msgid "Press q to quit."
msgstr "Appuyez sur q pour quitter."

View File

@@ -71,8 +71,8 @@ msgid "AI Assistant"
msgstr "Assistente IA" msgstr "Assistente IA"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "AI Settings" msgid "AI Settings"
msgstr "Impostazioni IA" msgstr "Impostazioni IA"
@@ -336,7 +336,7 @@ msgstr "Autore"
msgid "Auto" msgid "Auto"
msgstr "Automatico" msgstr "Automatico"
#: lib/bds/desktop/shell_live.ex:448 #: lib/bds/desktop/shell_live.ex:442
#: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/chat_editor.ex:234
#: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:171
#: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:364
@@ -486,7 +486,7 @@ msgstr "Valori predefiniti delle categorie, opzioni di rendering e collegamento
msgid "Category name is required" msgid "Category name is required"
msgstr "Il nome della categoria è obbligatorio" msgstr "Il nome della categoria è obbligatorio"
#: lib/bds/desktop/shell_live.ex:771 #: lib/bds/desktop/shell_live.ex:780
#: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:87
#: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:233
#: lib/bds/desktop/shell_live/chat_editor.ex:323 #: lib/bds/desktop/shell_live/chat_editor.ex:323
@@ -1063,7 +1063,7 @@ msgstr "Galleria"
msgid "Generate Site" msgid "Generate Site"
msgstr "Genera sito" msgstr "Genera sito"
#: lib/bds/desktop/shell_live.ex:772 #: lib/bds/desktop/shell_live.ex:781
#: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/desktop/shell_live/socket_state.ex:109
#: lib/bds/ui/sidebar.ex:789 #: lib/bds/ui/sidebar.ex:789
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@@ -1077,7 +1077,7 @@ msgid "Git Diff"
msgstr "Diff Git" msgstr "Diff Git"
#: lib/bds/desktop/shell_data.ex:226 #: lib/bds/desktop/shell_data.ex:226
#: lib/bds/desktop/shell_live.ex:768 #: lib/bds/desktop/shell_live.ex:777
#: lib/bds/desktop/shell_live/panel_renderer.ex:171 #: lib/bds/desktop/shell_live/panel_renderer.ex:171
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Git Log" msgid "Git Log"
@@ -1200,9 +1200,9 @@ msgstr "Definizioni di importazione"
msgid "Import failed: %{error}" msgid "Import failed: %{error}"
msgstr "Importazione non riuscita: %{error}" msgstr "Importazione non riuscita: %{error}"
#: lib/bds/desktop/shell_live.ex:598 #: lib/bds/desktop/shell_live.ex:605
#: lib/bds/desktop/shell_live.ex:884 #: lib/bds/desktop/shell_live.ex:893
#: lib/bds/desktop/shell_live.ex:890 #: lib/bds/desktop/shell_live.ex:899
#: lib/bds/desktop/shell_live/sidebar_create.ex:47 #: lib/bds/desktop/shell_live/sidebar_create.ex:47
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Import media" msgid "Import media"
@@ -1784,7 +1784,7 @@ msgstr "Altro"
msgid "Other (%{count})" msgid "Other (%{count})"
msgstr "Altro (%{count})" msgstr "Altro (%{count})"
#: lib/bds/desktop/shell_live.ex:767 #: lib/bds/desktop/shell_live.ex:776
#: lib/bds/desktop/shell_live/panel_renderer.ex:83 #: lib/bds/desktop/shell_live/panel_renderer.ex:83
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Output" msgid "Output"
@@ -2538,7 +2538,7 @@ msgstr "Nome del tag"
msgid "Tags" msgid "Tags"
msgstr "Tag" msgstr "Tag"
#: lib/bds/desktop/shell_live.ex:766 #: lib/bds/desktop/shell_live.ex:775
#: lib/bds/desktop/shell_live/panel_renderer.ex:54 #: lib/bds/desktop/shell_live/panel_renderer.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tasks" msgid "Tasks"
@@ -3218,12 +3218,12 @@ msgstr "Benvenuto nellassistente IA"
msgid "Comparing database and filesystem metadata" msgid "Comparing database and filesystem metadata"
msgstr "Confronto tra i metadati del database e del filesystem" msgstr "Confronto tra i metadati del database e del filesystem"
#: lib/bds/desktop/shell_live.ex:674 #: lib/bds/desktop/shell_live.ex:681
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{count} images to post" msgid "Added %{count} images to post"
msgstr "%{count} immagini aggiunte al post" msgstr "%{count} immagini aggiunte al post"
#: lib/bds/desktop/shell_live.ex:642 #: lib/bds/desktop/shell_live.ex:649
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{title}" msgid "Added %{title}"
msgstr "%{title} aggiunto" msgstr "%{title} aggiunto"
@@ -3243,18 +3243,18 @@ msgstr "Guida per l'utente finale per flussi editoriali, media, modelli, traduzi
msgid "Image Import Concurrency" msgid "Image Import Concurrency"
msgstr "Importazione simultanea immagini" msgstr "Importazione simultanea immagini"
#: lib/bds/desktop/shell_live.ex:447 #: lib/bds/desktop/shell_live.ex:441
#: lib/bds/desktop/shell_live.ex:460 #: lib/bds/desktop/shell_live.ex:454
#: lib/bds/desktop/shell_live.ex:641 #: lib/bds/desktop/shell_live.ex:648
#: lib/bds/desktop/shell_live.ex:673 #: lib/bds/desktop/shell_live.ex:680
#: lib/bds/desktop/shell_live.ex:684 #: lib/bds/desktop/shell_live.ex:691
#: lib/bds/desktop/shell_live.ex:695 #: lib/bds/desktop/shell_live.ex:702
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Add Gallery Images" msgid "Add Gallery Images"
msgstr "Aggiungi immagini alla galleria" msgstr "Aggiungi immagini alla galleria"
#: lib/bds/desktop/shell_live.ex:696 #: lib/bds/desktop/shell_live.ex:703
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Failed to process %{path}: %{reason}" msgid "Failed to process %{path}: %{reason}"
msgstr "Impossibile elaborare %{path}: %{reason}" msgstr "Impossibile elaborare %{path}: %{reason}"
@@ -3445,12 +3445,12 @@ msgstr "rinominato"
msgid "untracked" msgid "untracked"
msgstr "non tracciato" msgstr "non tracciato"
#: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live.ex:801
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Blogmark" msgid "Blogmark"
msgstr "Blogmark" msgstr "Blogmark"
#: lib/bds/desktop/shell_live.ex:835 #: lib/bds/desktop/shell_live.ex:844
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Open a project before importing a blogmark." msgid "Open a project before importing a blogmark."
msgstr "Apri un progetto prima di importare un blogmark." msgstr "Apri un progetto prima di importare un blogmark."
@@ -3491,7 +3491,7 @@ msgstr "Elimina"
msgid "Failed to copy bookmarklet to clipboard" msgid "Failed to copy bookmarklet to clipboard"
msgstr "Impossibile copiare il bookmarklet negli appunti" msgstr "Impossibile copiare il bookmarklet negli appunti"
#: lib/bds/desktop/shell_live.ex:804 #: lib/bds/desktop/shell_live.ex:813
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The project this blogmark targets does not exist here." msgid "The project this blogmark targets does not exist here."
msgstr "Il progetto a cui punta questo blogmark non esiste qui." msgstr "Il progetto a cui punta questo blogmark non esiste qui."
@@ -3578,12 +3578,12 @@ msgstr "Impostazioni IA salvate."
msgid "Could not load models" msgid "Could not load models"
msgstr "Impossibile caricare i modelli" msgstr "Impossibile caricare i modelli"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not save AI settings" msgid "Could not save AI settings"
msgstr "Impossibile salvare le impostazioni IA" msgstr "Impossibile salvare le impostazioni IA"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Loaded %{count} models." msgid "Loaded %{count} models."
msgstr "Caricati %{count} modelli." msgstr "Caricati %{count} modelli."
@@ -3593,7 +3593,17 @@ msgstr "Caricati %{count} modelli."
msgid "OK" msgid "OK"
msgstr "OK" msgstr "OK"
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The endpoint returned no models. You can still type a model name manually." msgid "The endpoint returned no models. You can still type a model name manually."
msgstr "L'endpoint non ha restituito alcun modello. Puoi comunque digitare manualmente il nome di un modello." msgstr "L'endpoint non ha restituito alcun modello. Puoi comunque digitare manualmente il nome di un modello."
#: lib/bds/tui.ex:28
#, elixir-autogen, elixir-format
msgid "Blogging Desktop Server"
msgstr "Blogging Desktop Server"
#: lib/bds/tui.ex:30
#, elixir-autogen, elixir-format
msgid "Press q to quit."
msgstr "Premi q per uscire."

View File

@@ -84,8 +84,8 @@ msgid "AI Assistant"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:93
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:193
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:208 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:211
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "AI Settings" msgid "AI Settings"
msgstr "" msgstr ""
@@ -349,7 +349,7 @@ msgstr ""
msgid "Auto" msgid "Auto"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:448 #: lib/bds/desktop/shell_live.ex:442
#: lib/bds/desktop/shell_live/chat_editor.ex:234 #: lib/bds/desktop/shell_live/chat_editor.ex:234
#: lib/bds/desktop/shell_live/media_editor.ex:171 #: lib/bds/desktop/shell_live/media_editor.ex:171
#: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:364
@@ -499,7 +499,7 @@ msgstr ""
msgid "Category name is required" msgid "Category name is required"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:771 #: lib/bds/desktop/shell_live.ex:780
#: lib/bds/desktop/shell_live/chat_editor.ex:87 #: lib/bds/desktop/shell_live/chat_editor.ex:87
#: lib/bds/desktop/shell_live/chat_editor.ex:233 #: lib/bds/desktop/shell_live/chat_editor.ex:233
#: lib/bds/desktop/shell_live/chat_editor.ex:323 #: lib/bds/desktop/shell_live/chat_editor.ex:323
@@ -1076,7 +1076,7 @@ msgstr ""
msgid "Generate Site" msgid "Generate Site"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:772 #: lib/bds/desktop/shell_live.ex:781
#: lib/bds/desktop/shell_live/socket_state.ex:109 #: lib/bds/desktop/shell_live/socket_state.ex:109
#: lib/bds/ui/sidebar.ex:789 #: lib/bds/ui/sidebar.ex:789
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@@ -1090,7 +1090,7 @@ msgid "Git Diff"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_data.ex:226 #: lib/bds/desktop/shell_data.ex:226
#: lib/bds/desktop/shell_live.ex:768 #: lib/bds/desktop/shell_live.ex:777
#: lib/bds/desktop/shell_live/panel_renderer.ex:171 #: lib/bds/desktop/shell_live/panel_renderer.ex:171
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Git Log" msgid "Git Log"
@@ -1213,9 +1213,9 @@ msgstr ""
msgid "Import failed: %{error}" msgid "Import failed: %{error}"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:598 #: lib/bds/desktop/shell_live.ex:605
#: lib/bds/desktop/shell_live.ex:884 #: lib/bds/desktop/shell_live.ex:893
#: lib/bds/desktop/shell_live.ex:890 #: lib/bds/desktop/shell_live.ex:899
#: lib/bds/desktop/shell_live/sidebar_create.ex:47 #: lib/bds/desktop/shell_live/sidebar_create.ex:47
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Import media" msgid "Import media"
@@ -1797,7 +1797,7 @@ msgstr ""
msgid "Other (%{count})" msgid "Other (%{count})"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:767 #: lib/bds/desktop/shell_live.ex:776
#: lib/bds/desktop/shell_live/panel_renderer.ex:83 #: lib/bds/desktop/shell_live/panel_renderer.ex:83
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Output" msgid "Output"
@@ -2551,7 +2551,7 @@ msgstr ""
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:766 #: lib/bds/desktop/shell_live.ex:775
#: lib/bds/desktop/shell_live/panel_renderer.ex:54 #: lib/bds/desktop/shell_live/panel_renderer.ex:54
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Tasks" msgid "Tasks"
@@ -3231,12 +3231,12 @@ msgstr ""
msgid "Comparing database and filesystem metadata" msgid "Comparing database and filesystem metadata"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:674 #: lib/bds/desktop/shell_live.ex:681
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{count} images to post" msgid "Added %{count} images to post"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:642 #: lib/bds/desktop/shell_live.ex:649
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Added %{title}" msgid "Added %{title}"
msgstr "" msgstr ""
@@ -3256,18 +3256,18 @@ msgstr ""
msgid "Image Import Concurrency" msgid "Image Import Concurrency"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:447 #: lib/bds/desktop/shell_live.ex:441
#: lib/bds/desktop/shell_live.ex:460 #: lib/bds/desktop/shell_live.ex:454
#: lib/bds/desktop/shell_live.ex:641 #: lib/bds/desktop/shell_live.ex:648
#: lib/bds/desktop/shell_live.ex:673 #: lib/bds/desktop/shell_live.ex:680
#: lib/bds/desktop/shell_live.ex:684 #: lib/bds/desktop/shell_live.ex:691
#: lib/bds/desktop/shell_live.ex:695 #: lib/bds/desktop/shell_live.ex:702
#: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:420
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Add Gallery Images" msgid "Add Gallery Images"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:696 #: lib/bds/desktop/shell_live.ex:703
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Failed to process %{path}: %{reason}" msgid "Failed to process %{path}: %{reason}"
msgstr "" msgstr ""
@@ -3458,12 +3458,12 @@ msgstr ""
msgid "untracked" msgid "untracked"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live.ex:801
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Blogmark" msgid "Blogmark"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:835 #: lib/bds/desktop/shell_live.ex:844
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Open a project before importing a blogmark." msgid "Open a project before importing a blogmark."
msgstr "" msgstr ""
@@ -3504,7 +3504,7 @@ msgstr ""
msgid "Failed to copy bookmarklet to clipboard" msgid "Failed to copy bookmarklet to clipboard"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live.ex:804 #: lib/bds/desktop/shell_live.ex:813
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The project this blogmark targets does not exist here." msgid "The project this blogmark targets does not exist here."
msgstr "" msgstr ""
@@ -3591,12 +3591,12 @@ msgstr ""
msgid "Could not load models" msgid "Could not load models"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:187 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:190
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Could not save AI settings" msgid "Could not save AI settings"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:182 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:184
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Loaded %{count} models." msgid "Loaded %{count} models."
msgstr "" msgstr ""
@@ -3606,7 +3606,17 @@ msgstr ""
msgid "OK" msgid "OK"
msgstr "" msgstr ""
#: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:173 #: lib/bds/desktop/shell_live/settings_editor/ai_settings.ex:174
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "The endpoint returned no models. You can still type a model name manually." msgid "The endpoint returned no models. You can still type a model name manually."
msgstr "" msgstr ""
#: lib/bds/tui.ex:28
#, elixir-autogen, elixir-format
msgid "Blogging Desktop Server"
msgstr ""
#: lib/bds/tui.ex:30
#, elixir-autogen, elixir-format
msgid "Press q to quit."
msgstr ""

62
specs/server.allium Normal file
View File

@@ -0,0 +1,62 @@
-- allium: 1
-- Headless Server Mode (issue #26, phase 1)
-- Scope: extension (client/server split — SSH transport for TUI and GUI)
-- Distilled from: lib/bds/server.ex, lib/bds/application.ex, lib/bds/tui.ex
entity BootMode {
kind: desktop | server
-- Resolved from the BDS_MODE environment variable at application start.
-- Anything other than "server" (case-insensitive) is desktop.
}
entity SshKeyMaterial {
dir: String -- <data dir>/ssh, next to the database
host_key: String -- ssh_host_rsa_key, generated on first boot
authorized_keys: String -- created empty (mode 600) on first boot
-- Invariant: key material lives in the private app data dir,
-- never in the repo or an application priv dir.
}
surface ServerRuntimeSurface {
facing _: ServerRuntime
provides:
ApplicationStarted(mode)
SshClientConnected()
}
rule ModeResolution {
when: ApplicationStarted(mode)
-- BDS_MODE environment variable decides the mode once per boot
ensures: BootMode.created(kind: mode)
}
rule DesktopBoot {
when: ApplicationStarted(mode: desktop)
-- Unchanged behavior: wx window shell, loopback HTTP endpoint.
ensures: DesktopWindowStarted()
}
rule ServerBoot {
when: ApplicationStarted(mode: server)
-- Headless: no wx children at all. Works on macOS, Windows, Linux.
ensures: LoopbackEndpointStarted()
-- HTTP endpoint stays bound to 127.0.0.1; never exposed directly
ensures: CliSyncWatcherStarted()
ensures: SshDaemonStarted()
-- ExRatatui.SSH.Daemon: public-key auth only (no passwords),
-- tcpip_tunnel_out enabled so GUI clients tunnel to the loopback
-- endpoint through the same SSH connection and the same keys
ensures: SshKeyMaterial.created()
-- host key generated and authorized_keys touched on first boot only
}
rule TuiSession {
when: SshClientConnected()
-- Each SSH client gets its own TUI app instance on the server;
-- only terminal cells cross the wire.
ensures: TuiAppMounted()
-- UI locale comes from the server-side UI language setting,
-- identical for every client
}

View File

@@ -127,7 +127,9 @@ defmodule BDS.Scripting.JobTest do
_running_job = wait_for_job(job.id, &(&1.status == :running)) _running_job = wait_for_job(job.id, &(&1.status == :running))
assert :ok = BDS.Scripting.cancel_job(job.id) assert :ok = BDS.Scripting.cancel_job(job.id)
assert_receive :job_cleanup_ran, 1_000 # Generous timeout: cleanup runs in a separate shutdown path and 1s
# flakes when the full suite loads the machine.
assert_receive :job_cleanup_ran, 5_000
cancelled_job = wait_for_job(job.id, &(&1.status == :cancelled)) cancelled_job = wait_for_job(job.id, &(&1.status == :cancelled))
assert cancelled_job.finished_at != nil assert cancelled_job.finished_at != nil

111
test/bds/server_test.exs Normal file
View File

@@ -0,0 +1,111 @@
defmodule BDS.ServerTest do
use ExUnit.Case, async: false
alias BDS.Server
describe "mode/1" do
test "defaults to :desktop" do
assert Server.mode(nil) == :desktop
assert Server.mode("") == :desktop
end
test "recognizes server mode" do
assert Server.mode("server") == :server
assert Server.mode("SERVER") == :server
end
test "unknown values fall back to :desktop" do
assert Server.mode("garbage") == :desktop
end
end
describe "ensure_ssh_dir!/1" do
setup do
dir = Path.join(System.tmp_dir!(), "bds-ssh-test-#{System.unique_integer([:positive])}")
on_exit(fn -> File.rm_rf!(dir) end)
{:ok, dir: dir}
end
test "creates the directory and an empty authorized_keys file", %{dir: dir} do
assert Server.ensure_ssh_dir!(dir) == dir
assert File.dir?(dir)
keys_path = Path.join(dir, "authorized_keys")
assert File.exists?(keys_path)
assert File.read!(keys_path) == ""
%File.Stat{mode: mode} = File.stat!(keys_path)
assert Bitwise.band(mode, 0o777) == 0o600
end
test "leaves an existing authorized_keys file untouched", %{dir: dir} do
File.mkdir_p!(dir)
keys_path = Path.join(dir, "authorized_keys")
File.write!(keys_path, "ssh-ed25519 AAAA... user@host\n")
Server.ensure_ssh_dir!(dir)
assert File.read!(keys_path) == "ssh-ed25519 AAAA... user@host\n"
end
end
describe "daemon_opts/1" do
setup do
dir = Path.join(System.tmp_dir!(), "bds-ssh-test-#{System.unique_integer([:positive])}")
on_exit(fn -> File.rm_rf!(dir) end)
{:ok, dir: dir}
end
test "serves the TUI app over public-key auth only, with GUI tunneling", %{dir: dir} do
opts = Server.daemon_opts(dir)
assert opts[:mod] == BDS.TUI
assert opts[:auth_methods] == ~c"publickey"
assert opts[:tcpip_tunnel_out] == true
refute Keyword.has_key?(opts, :pwdfun)
assert opts[:system_dir] == String.to_charlist(dir)
assert opts[:user_dir] == String.to_charlist(dir)
end
test "generates a host key on first use and reuses it", %{dir: dir} do
Server.daemon_opts(dir)
key_path = Path.join(dir, "ssh_host_rsa_key")
assert File.exists?(key_path)
first = File.read!(key_path)
Server.daemon_opts(dir)
assert File.read!(key_path) == first
end
test "uses the configured SSH port", %{dir: dir} do
assert Server.daemon_opts(dir)[:port] == Application.get_env(:bds, :server)[:ssh_port]
end
end
describe "BDS.Application.mode_children/2" do
test "server mode starts endpoint, cli sync watcher, and the SSH daemon" do
children = BDS.Application.mode_children(:server, :prod)
assert {BDS.Desktop.Server, []} in children
assert BDS.CliSync.Watcher in children
# BDS.Server wraps ExRatatui.SSH.Daemon, deferring key generation to
# process start so building this list never touches the data dir.
assert BDS.Server in children
end
test "server mode starts no wx window children" do
children = BDS.Application.mode_children(:server, :prod)
refute Enum.any?(children, fn
{Desktop.Window, _} -> true
_other -> false
end)
end
test "desktop mode delegates to desktop_children" do
assert BDS.Application.mode_children(:desktop, :test) ==
BDS.Application.desktop_children(:test)
end
end
end