diff --git a/lib/bds/application.ex b/lib/bds/application.ex index cee6b1f..360bb71 100644 --- a/lib/bds/application.ex +++ b/lib/bds/application.ex @@ -12,6 +12,11 @@ defmodule BDS.Application do [{BDS.Desktop.Server, []}, BDS.CliSync.Watcher, BDS.Server] end + # CLI mode (issue #25): a one-shot command run against the shared database. + # No HTTP listener, SSH daemon, window, or sync watcher — the CLI is the + # external writer the watcher in a concurrently running app listens for. + def mode_children(:cli, _env), do: [] + # Local TUI: the headless server plus a TUI on the launching terminal, so # a GUI (via tunnel) and the local terminal can work in parallel. Quitting # this TUI stops the whole VM — the terminal is the app in this mode. diff --git a/lib/bds/cli.ex b/lib/bds/cli.ex new file mode 100644 index 0000000..b1d0533 --- /dev/null +++ b/lib/bds/cli.ex @@ -0,0 +1,335 @@ +defmodule BDS.CLI do + @moduledoc """ + Command-line interface for managing content in a bDS2 workspace (issue #25). + + Runs inside the release VM (`BDS_MODE=cli`) against the same settings and + cache database as the GUI/TUI application; mutations write + `BDS.CliSync.Notification` rows so a concurrently running app picks them + up through `BDS.CliSync.Watcher`. + + The launcher script (`cli/bin/bds-cli` in the release, installable to + `~/.local/bin` via `BDS.CLI.Install`) passes the argv via `BDS_CLI_ARGV` + (unit-separator joined) because `bin/bds eval` cannot forward arguments. + """ + + alias BDS.CLI.Commands + + @argv_env "BDS_CLI_ARGV" + @argv_separator <<0x1F>> + + @doc "Release entry point: run the command from `BDS_CLI_ARGV` and halt." + @spec main() :: no_return() + def main do + System.halt(run(env_argv())) + end + + @doc "Decodes the unit-separator-joined argv from the launcher script." + @spec env_argv(String.t() | nil) :: [String.t()] + def env_argv(value \\ System.get_env(@argv_env)) + def env_argv(nil), do: [] + def env_argv(value), do: String.split(value, @argv_separator, trim: true) + + @doc "Parses and executes an argv, returning the process exit code." + @spec run([String.t()]) :: non_neg_integer() + def run(argv) when is_list(argv) do + optimus = optimus() + + case Optimus.parse(optimus, argv) do + {:ok, %Optimus.ParseResult{}} -> + print_help(optimus, []) + 0 + + {:ok, subcommand_path, parse_result} -> + execute(subcommand_path, parse_result) + + :help -> + print_help(optimus, []) + 0 + + {:help, subcommand_path} -> + print_help(optimus, subcommand_path) + 0 + + :version -> + IO.puts(version()) + 0 + + {:error, errors} -> + print_errors(optimus, [], errors) + 1 + + {:error, subcommand_path, errors} -> + print_errors(optimus, subcommand_path, errors) + 1 + end + end + + @doc "Executes a parsed subcommand, returning the process exit code." + @spec execute([atom()], Optimus.ParseResult.t()) :: non_neg_integer() + def execute(subcommand_path, parse_result) do + {:ok, _apps} = Application.ensure_all_started(:bds) + + subcommand_path + |> Commands.dispatch(parse_result) + |> report() + end + + defp report(:ok), do: 0 + + defp report({:ok, message}) when is_binary(message) do + IO.puts(message) + 0 + end + + defp report({:error, message}) do + IO.puts(:stderr, "Error: " <> format_error(message)) + 1 + end + + defp format_error(message) when is_binary(message), do: message + defp format_error(%{message: message}) when is_binary(message), do: message + defp format_error(other), do: inspect(other) + + @repair_parts ~w(post-links media-links thumbnails embeddings search) + + @doc "The Optimus command definition (public for tests and help rendering)." + def optimus do + Optimus.new!( + name: "bds-cli", + description: "bDS2 workspace CLI", + about: + "Manages content in a bDS2 workspace using the same settings and " <> + "cache database as the desktop application.", + version: version(), + allow_unknown_args: false, + parse_double_dash: true, + subcommands: [ + rebuild: [ + name: "rebuild", + about: "Rebuild the caching database from the workspace files", + flags: [ + incremental: [ + long: "--incremental", + help: + "Run a metadata diff and auto-apply every difference from " <> + "file to database instead of a full rebuild" + ] + ] + ], + repair: [ + name: "repair", + about: "Run one of the standard repair tasks outside the full rebuild", + args: [ + part: [ + value_name: "PART", + help: "One of: " <> Enum.join(@repair_parts, ", "), + required: true, + parser: &parse_repair_part/1 + ] + ] + ], + render: [ + name: "render", + about: "Render the blog from the current content", + flags: [ + incremental: [ + long: "--incremental", + help: "Validate the generated output and apply only the differences" + ], + force: [ + long: "--force", + help: "Full re-render ignoring (but updating) stored content hashes" + ] + ] + ], + upload: [ + name: "upload", + about: "Upload the rendered site to the configured host (rsync/scp)" + ], + push: [ + name: "push", + about: "Push the project repository to its origin" + ], + pull: [ + name: "pull", + about: "Pull the project repository and update the cache database" + ], + post: [ + name: "post", + about: + "Create a post from parameters or from JSON on stdin " <> + "(keys: title, content, excerpt, author, language, tags, categories, template)", + flags: [ + stdin: [long: "--stdin", help: "Read post data as JSON from stdin"], + no_translate: [ + long: "--no-translate", + help: "Skip automatic translation of the created post" + ] + ], + options: post_content_options() + ], + media: [ + name: "media", + about: + "Import an image with automatically generated title, alt text, " <> + "caption and translations", + args: [ + file: [ + value_name: "FILE", + help: "Path of the image file to import", + required: true + ] + ], + options: [ + language: [ + long: "--language", + value_name: "LANG", + help: "Language for the generated texts (defaults to the project main language)" + ] + ] + ], + gallery: [ + name: "gallery", + about: + "Create a gallery post and import all referenced images as new " <> + "media with generated texts and translations", + allow_unknown_args: true, + flags: [ + stdin: [ + long: "--stdin", + help: "Read gallery data as JSON from stdin (adds an \"images\" key to post keys)" + ] + ], + options: post_content_options() + ], + config: [ + name: "config", + about: "Read and write global preference values", + subcommands: [ + get: [ + name: "get", + about: "Print a preference value", + args: [key: [value_name: "KEY", help: "Preference key", required: true]] + ], + set: [ + name: "set", + about: "Set a preference value", + args: [ + key: [value_name: "KEY", help: "Preference key", required: true], + value: [value_name: "VALUE", help: "New value", required: true] + ] + ], + list: [ + name: "list", + about: "List all preference keys and values" + ] + ] + ], + project: [ + name: "project", + about: "Manage the projects registered in the cache database", + subcommands: [ + list: [ + name: "list", + about: "List all projects" + ], + add: [ + name: "add", + about: "Open a project folder and add it to the cache database", + args: [ + path: [value_name: "PATH", help: "Project data folder", required: true] + ], + options: [ + name: [ + long: "--name", + value_name: "NAME", + help: "Project name (defaults to the folder name)" + ] + ] + ], + switch: [ + name: "switch", + about: "Switch the active project", + args: [ + project: [ + value_name: "PROJECT", + help: "Project id, slug, or name", + required: true + ] + ] + ] + ] + ], + tui: [ + name: "tui", + about: "Open the app in TUI mode for interactive use" + ], + lua: [ + name: "lua", + about: "Run a utility (long-running task) Lua script from the database", + allow_unknown_args: true, + args: [ + script: [ + value_name: "SCRIPT", + help: "Script slug in the active project", + required: true + ] + ] + ] + ] + ) + end + + defp post_content_options do + [ + title: [long: "--title", value_name: "TITLE", help: "Post title"], + content: [long: "--content", value_name: "MARKDOWN", help: "Post body (markdown)"], + excerpt: [long: "--excerpt", value_name: "TEXT", help: "Post excerpt"], + author: [long: "--author", value_name: "NAME", help: "Post author"], + language: [ + long: "--language", + value_name: "LANG", + help: "Post language (auto-detected from the content when omitted)" + ], + template: [long: "--template", value_name: "SLUG", help: "Template slug"], + tags: [long: "--tags", value_name: "TAGS", help: "Comma-separated tags"], + categories: [ + long: "--categories", + value_name: "CATEGORIES", + help: "Comma-separated categories" + ] + ] + end + + defp parse_repair_part(value) when value in @repair_parts, do: {:ok, value} + + defp parse_repair_part(value), + do: {:error, "unknown repair part #{inspect(value)}; expected one of: #{Enum.join(@repair_parts, ", ")}"} + + defp print_help(optimus, subcommand_path) do + optimus + |> Optimus.Help.help(subcommand_path, terminal_width()) + |> Enum.each(&IO.puts/1) + end + + defp print_errors(optimus, [], errors) do + optimus |> Optimus.Errors.format(errors) |> Enum.each(&IO.puts(:stderr, &1)) + end + + defp print_errors(optimus, subcommand_path, errors) do + optimus + |> Optimus.Errors.format(subcommand_path, errors) + |> Enum.each(&IO.puts(:stderr, &1)) + end + + defp terminal_width do + case Optimus.Term.width() do + {:ok, width} -> width + _other -> 80 + end + end + + defp version do + to_string(Application.spec(:bds, :vsn) || "dev") + end +end diff --git a/lib/bds/cli/commands.ex b/lib/bds/cli/commands.ex new file mode 100644 index 0000000..78e3f48 --- /dev/null +++ b/lib/bds/cli/commands.ex @@ -0,0 +1,695 @@ +defmodule BDS.CLI.Commands do + @moduledoc """ + Implementations of the `bds-cli` subcommands (issue #25). + + Every command runs synchronously against the engine modules the GUI uses + and returns `:ok`, `{:ok, message}`, or `{:error, message}`. Mutations + write `BDS.CliSync` notification rows so a concurrently running app + refreshes its state (see `specs/cli_sync.allium`). + """ + + import Ecto.Query + + alias BDS.AI + alias BDS.CliSync + alias BDS.Desktop.ShellLive.GalleryImport + alias BDS.Generation + alias BDS.Git + alias BDS.Maintenance + alias BDS.Media + alias BDS.Metadata + alias BDS.Posts + alias BDS.Posts.AutoTranslation + alias BDS.Projects + alias BDS.Publishing + alias BDS.Repo + alias BDS.Scripting + alias BDS.Scripts + alias BDS.Scripts.Script + alias BDS.Search + alias BDS.Settings + alias BDS.Tasks + + @site_sections [:core, :single, :category, :tag, :date] + @bulk_entity_types ~w(post media script template) + @gallery_concurrency 2 + @poll_interval_ms 250 + + @doc "Dispatches a parsed subcommand path to its implementation." + @spec dispatch([atom()], Optimus.ParseResult.t()) :: :ok | {:ok, String.t()} | {:error, term()} + def dispatch([:rebuild], %{flags: %{incremental: true}}), + do: with_project(&incremental_rebuild/1) + + def dispatch([:rebuild], _parse_result), do: with_project(&full_rebuild/1) + + def dispatch([:repair], %{args: %{part: part}}), + do: with_project(&repair(part, &1)) + + def dispatch([:render], %{flags: %{incremental: true, force: true}}), + do: {:error, "--incremental and --force are mutually exclusive"} + + def dispatch([:render], %{flags: %{incremental: true}}), + do: with_project(&incremental_render/1) + + def dispatch([:render], %{flags: flags}), + do: with_project(&full_render(&1, flags[:force] == true)) + + def dispatch([:upload], _parse_result), do: with_project(&upload/1) + + def dispatch([:push], _parse_result) do + with_project(fn project -> + with {:ok, result} <- Git.push(project.id) do + print_git_output(result) + {:ok, "Pushed"} + end + end) + end + + def dispatch([:pull], _parse_result) do + with_project(fn project -> + with {:ok, result} <- Git.pull(project.id) do + print_git_output(result) + # Update the cache database from the pulled files, like the app's + # incremental rebuild: file→db for every difference plus orphans. + incremental_rebuild(project) + end + end) + end + + def dispatch([:post], %{flags: flags, options: options}) do + with_project(fn project -> + with {:ok, attrs} <- post_attrs(flags, options), + {:ok, attrs} <- ensure_language(attrs), + {:ok, post} <- create_post(project, attrs) do + unless flags[:no_translate], do: translate_post(post) + {:ok, "Created post #{post.id} (#{post.slug}, #{post.language})"} + end + end) + end + + def dispatch([:media], %{args: %{file: file}, options: options}) do + with_project(fn project -> + with {:ok, metadata} <- Metadata.get_project_metadata(project.id), + :ok <- ensure_files_exist([file]) do + language = present(options[:language]) || metadata.main_language + targets = GalleryImport.translate_targets(metadata, language) + + case GalleryImport.import_and_enrich(file, project.id, language, targets) do + {:ok, media, title} -> + :ok = notify("media", media.id, :created) + {:ok, "Imported media #{media.id} (#{title})"} + + {:error, reason} -> + {:error, format_reason(reason)} + end + end + end) + end + + def dispatch([:gallery], %{flags: flags, options: options, unknown: unknown}) do + with_project(fn project -> + with {:ok, attrs, images} <- gallery_attrs(flags, options, unknown), + :ok <- ensure_files_exist(images), + {:ok, attrs} <- ensure_language(attrs), + {:ok, post} <- create_post(project, attrs) do + import_gallery_images(project, post, images) + :ok = notify("media", "*", :created) + :ok = notify("post", post.id, :updated) + unless flags[:no_translate], do: translate_post(post) + {:ok, "Created gallery post #{post.id} (#{post.slug}) with #{length(images)} images"} + end + end) + end + + def dispatch([:config, :get], %{args: %{key: key}}) do + case Settings.get_global_setting(key) do + nil -> {:error, "#{key} is not set"} + value -> {:ok, value} + end + end + + def dispatch([:config, :set], %{args: %{key: key, value: value}}) do + with :ok <- Settings.put_global_setting(key, value) do + :ok = notify("setting", key, :updated) + {:ok, "#{key} = #{value}"} + end + end + + def dispatch([:config, :list], _parse_result) do + Settings.list_global_settings() + |> Enum.each(fn {key, value} -> IO.puts("#{key}=#{value}") end) + + :ok + end + + def dispatch([:project, :list], _parse_result) do + Enum.each(Projects.list_projects(), fn project -> + marker = if project.is_active, do: "* ", else: " " + IO.puts("#{marker}#{project.id} #{project.slug} #{project.name}") + end) + + :ok + end + + def dispatch([:project, :add], %{args: %{path: path}, options: options}) do + expanded = Path.expand(path) + + if File.dir?(expanded) do + name = present(options[:name]) || Path.basename(expanded) + + with {:ok, project} <- Projects.create_project(%{name: name, data_path: expanded}) do + :ok = notify("project", project.id, :created) + {:ok, "Added project #{project.id} (#{project.slug}); switch with: project switch #{project.slug}"} + end + else + {:error, "#{expanded} is not a directory"} + end + end + + def dispatch([:project, :switch], %{args: %{project: reference}}) do + with {:ok, project} <- resolve_project(reference), + {:ok, project} <- Projects.set_active_project(project.id) do + :ok = notify("project", project.id, :updated) + {:ok, "Active project: #{project.name}"} + else + {:error, :not_found} -> {:error, "No project matches #{inspect(reference)}"} + {:error, reason} -> {:error, format_reason(reason)} + end + end + + def dispatch([:tui], _parse_result) do + {:error, + "The TUI is started by the bds-cli launcher script (BDS_MODE=tui); " <> + "it is not available through a direct BDS.CLI invocation"} + end + + def dispatch([:lua], %{args: %{script: slug}, unknown: script_args}) do + with_project(fn project -> + case Repo.one( + from script in Script, + where: script.project_id == ^project.id and script.slug == ^slug + ) do + nil -> + {:error, "No script with slug #{inspect(slug)} in the active project"} + + %Script{kind: kind} when kind != :utility -> + {:error, "Script #{inspect(slug)} is a #{kind} script; only utility (task) scripts can be run"} + + %Script{enabled: false} -> + {:error, "Script #{inspect(slug)} is disabled"} + + script -> + run_lua_job(project, script, script_args) + end + end) + end + + # ── rebuild ─────────────────────────────────────────────────────────────── + + defp full_rebuild(project) do + project.id + |> Maintenance.full_rebuild_steps() + |> Enum.reduce_while(:ok, fn step, :ok -> + IO.puts("==> #{step.name}") + + case step.work.(progress_printer()) do + {:error, message} -> {:halt, {:error, message}} + _result -> {:cont, :ok} + end + end) + |> case do + :ok -> + notify_bulk_change() + {:ok, "Rebuild complete"} + + error -> + error + end + end + + defp incremental_rebuild(project) do + reporter = progress_printer() + {:ok, diff} = Maintenance.metadata_diff(project.id, on_progress: reporter) + + items = + Enum.map(diff.diff_reports, &%{entity_type: &1.entity_type, entity_id: &1.entity_id}) + + orphans = Enum.map(diff.orphan_reports, &%{file_path: &1.file_path}) + + if items != [] do + {:ok, _repair} = + Maintenance.repair_metadata_diff(project.id, :file_to_db, items, on_progress: reporter) + end + + if orphans != [] do + {:ok, _import} = + Maintenance.import_metadata_diff_orphans(project.id, orphans, on_progress: reporter) + end + + if items != [] or orphans != [], do: notify_bulk_change() + + {:ok, + "Applied #{length(items)} differences and imported #{length(orphans)} orphan files from the filesystem"} + end + + # ── repair ──────────────────────────────────────────────────────────────── + + defp repair("post-links", project) do + :ok = Posts.rebuild_post_links(project.id, on_progress: progress_printer()) + :ok = notify("post", "*", :updated) + {:ok, "Post links rebuilt"} + end + + defp repair("media-links", project) do + {:ok, result} = Media.rebuild_media_links(project.id, on_progress: progress_printer()) + :ok = notify("media", "*", :updated) + {:ok, "Media links rebuilt (#{result.links} links)"} + end + + defp repair("thumbnails", project) do + result = Media.regenerate_missing_thumbnails(project.id, on_progress: progress_printer()) + :ok = notify("media", "*", :updated) + {:ok, "Missing thumbnails regenerated (#{inspect(Map.get(result, :generated, 0))} generated)"} + end + + defp repair("embeddings", project) do + case Maintenance.rebuild_embedding_index(project.id, on_progress: progress_printer()) do + {:error, message} -> {:error, message} + result -> {:ok, "Embedding index rebuilt (#{result.rebuilt_count} posts)"} + end + end + + defp repair("search", project) do + :ok = Search.reindex_posts(project.id, on_progress: progress_printer()) + :ok = Search.reindex_media(project.id, on_progress: progress_printer()) + {:ok, "Search text reindexed"} + end + + # ── render ──────────────────────────────────────────────────────────────── + + defp full_render(project, force?) do + render_opts = if force?, do: [force: true], else: [] + + Enum.each(@site_sections, fn section -> + IO.puts("==> Rendering #{section}") + + {:ok, _result} = + Generation.render_site_section( + project.id, + section, + [on_progress: progress_printer()] ++ render_opts + ) + end) + + IO.puts("==> Building search index") + + {:ok, _index} = + Generation.build_site_search_index( + project.id, + [on_progress: progress_printer()] ++ render_opts + ) + + {:ok, "Site rendered"} + end + + defp incremental_render(project) do + {:ok, report} = + Generation.validate_site(project.id, @site_sections, on_progress: progress_printer()) + + {:ok, applied} = + Generation.apply_validation(project.id, report, on_progress: progress_printer()) + + {:ok, + "Validation applied (#{applied.rendered_url_count} rendered, " <> + "#{applied.deleted_url_count} deleted)"} + end + + # ── upload ──────────────────────────────────────────────────────────────── + + defp upload(project) do + with {:ok, metadata} <- Metadata.get_project_metadata(project.id), + {:ok, credentials} <- upload_credentials(metadata.publishing_preferences), + {:ok, job} <- Publishing.upload_site(project.id, credentials) do + await_task(job.task_id, "Upload complete") + end + end + + defp upload_credentials(prefs) when is_map(prefs) do + credentials = %{ + ssh_host: Map.get(prefs, "ssh_host"), + ssh_user: Map.get(prefs, "ssh_user"), + ssh_remote_path: Map.get(prefs, "ssh_remote_path"), + ssh_mode: Map.get(prefs, "ssh_mode") + } + + if Enum.all?( + [credentials.ssh_host, credentials.ssh_user, credentials.ssh_remote_path], + &is_binary/1 + ) do + {:ok, credentials} + else + {:error, "Publishing preferences are incomplete; configure the upload host in Settings"} + end + end + + defp upload_credentials(_prefs), + do: {:error, "Publishing preferences are incomplete; configure the upload host in Settings"} + + # ── post / gallery ──────────────────────────────────────────────────────── + + defp post_attrs(%{stdin: true}, _options) do + with {:ok, data} <- read_stdin_json() do + json_post_attrs(data) + end + end + + defp post_attrs(_flags, options) do + case present(options[:title]) do + nil -> + {:error, "--title is required (or pass --stdin with JSON post data)"} + + title -> + {:ok, + %{ + title: title, + content: options[:content] || "", + excerpt: present(options[:excerpt]), + author: present(options[:author]), + language: present(options[:language]), + template_slug: present(options[:template]), + tags: split_list(options[:tags]), + categories: split_list(options[:categories]) + }} + end + end + + defp gallery_attrs(%{stdin: true}, _options, _unknown) do + with {:ok, data} <- read_stdin_json(), + {:ok, attrs} <- json_post_attrs(data) do + case Map.get(data, "images") do + images when is_list(images) and images != [] -> {:ok, attrs, images} + _other -> {:error, "JSON gallery data needs a non-empty \"images\" array"} + end + end + end + + defp gallery_attrs(flags, options, unknown) do + case unknown do + [] -> + {:error, "Pass the image files as arguments (or use --stdin with JSON gallery data)"} + + images -> + with {:ok, attrs} <- post_attrs(flags, options) do + {:ok, attrs, images} + end + end + end + + defp json_post_attrs(data) when is_map(data) do + case present(Map.get(data, "title")) do + nil -> + {:error, "JSON post data needs a \"title\""} + + title -> + {:ok, + %{ + title: title, + content: Map.get(data, "content") || "", + excerpt: present(Map.get(data, "excerpt")), + author: present(Map.get(data, "author")), + language: present(Map.get(data, "language")), + template_slug: present(Map.get(data, "template")), + tags: json_list(Map.get(data, "tags")), + categories: json_list(Map.get(data, "categories")) + }} + end + end + + defp json_post_attrs(_data), do: {:error, "JSON post data must be an object"} + + defp read_stdin_json do + case IO.read(:stdio, :eof) do + :eof -> + {:error, "No JSON data on stdin"} + + {:error, reason} -> + {:error, "Could not read stdin: #{inspect(reason)}"} + + data -> + case Jason.decode(data) do + {:ok, decoded} -> {:ok, decoded} + {:error, error} -> {:error, "Invalid JSON on stdin: #{Exception.message(error)}"} + end + end + end + + # Language auto-detection (issue #25): ask the configured AI endpoint — + # BDS.AI internally routes to the local model in airplane mode — and fall + # back to the offline search heuristic, informing the user (the CLI + # equivalent of the airplane-mode toast). + defp ensure_language(%{language: language} = attrs) when is_binary(language), do: {:ok, attrs} + + defp ensure_language(attrs) do + text = String.trim("#{attrs.title}\n#{attrs.content}") + + language = + case AI.detect_language(text) do + {:ok, %{language_code: language}} when is_binary(language) -> + language + + _other -> + IO.puts("AI language detection unavailable; using the offline heuristic") + Search.detect_language(text) + end + + {:ok, %{attrs | language: language}} + end + + defp create_post(project, attrs) do + case Posts.create_post(Map.put(attrs, :project_id, project.id)) do + {:ok, post} -> + :ok = notify("post", post.id, :created) + {:ok, post} + + {:error, %Ecto.Changeset{} = changeset} -> + {:error, changeset_message(changeset)} + end + end + + # Automatic translation mirrors the GUI: schedule the background tasks and + # wait for them, so the CLI exits only when the work is done. When nothing + # gets scheduled (airplane mode without a local model, or a single-language + # blog) the user is told instead of a silent no-op. + defp translate_post(post) do + before_count = length(active_tasks()) + :ok = AutoTranslation.maybe_schedule(post) + + case length(active_tasks()) - before_count do + queued when queued > 0 -> + IO.puts("Waiting for #{queued} translation task(s)...") + await_active_tasks() + :ok = notify("post", post.id, :updated) + + _none -> + IO.puts("Automatic translation was not scheduled (offline, unconfigured AI, or nothing to translate)") + end + + :ok + end + + defp import_gallery_images(project, post, images) do + {:ok, metadata} = Metadata.get_project_metadata(project.id) + language = post.language || metadata.main_language + parent = self() + + runner = + Task.async(fn -> + GalleryImport.start( + images, + project.id, + post.id, + language, + @gallery_concurrency, + parent + ) + end) + + print_gallery_progress() + Task.await(runner, :infinity) + end + + defp print_gallery_progress do + receive do + {:add_image_processed, title} -> + IO.puts("Imported #{title}") + print_gallery_progress() + + {:add_image_error, path, reason} -> + IO.puts(:stderr, "Failed to import #{path}: #{format_reason(reason)}") + print_gallery_progress() + + {:add_images_complete, count} -> + IO.puts("Processed #{count} image(s)") + :ok + end + end + + defp ensure_files_exist(paths) do + case Enum.reject(paths, &File.regular?/1) do + [] -> :ok + missing -> {:error, "No such file: #{Enum.join(missing, ", ")}"} + end + end + + # ── lua ─────────────────────────────────────────────────────────────────── + + # Utility ("task") scripts run with the managed-job execution budget + # (unlimited time/reductions) but synchronously — the CLI is the one + # waiting for the long-running activity. + defp run_lua_job(project, script, args) do + scripting_config = Application.fetch_env!(:bds, :scripting) + + case Scripting.execute_project_script( + project.id, + Scripts.resolved_content(script), + script.entrypoint || "main", + args, + timeout: Keyword.get(scripting_config, :job_timeout, :infinity), + max_reductions: Keyword.get(scripting_config, :job_max_reductions, :none) + ) do + {:ok, nil} -> {:ok, "Script finished"} + {:ok, result} -> {:ok, "Script finished: #{format_reason(result)}"} + {:error, reason} -> {:error, "Script failed: #{format_reason(reason)}"} + end + end + + # ── shared helpers ──────────────────────────────────────────────────────── + + defp with_project(fun) do + case Projects.get_active_project() do + nil -> {:error, "No active project selected; use: project switch "} + project -> fun.(project) + end + end + + defp resolve_project(reference) do + projects = Projects.list_projects() + + found = + Enum.find(projects, fn project -> + reference in [project.id, project.slug, project.name] + end) + + if found, do: {:ok, found}, else: {:error, :not_found} + end + + defp await_task(task_id, success_message) do + case Tasks.get_task(task_id) do + nil -> + {:error, "Task disappeared before completion"} + + %{status: :completed} -> + {:ok, success_message} + + %{status: :failed} = task -> + {:error, format_reason(task.error || "task failed")} + + %{message: message} = task -> + print_progress(task.progress || 0.0, message) + Process.sleep(@poll_interval_ms) + await_task(task_id, success_message) + end + end + + defp active_tasks do + Enum.filter(Tasks.list_tasks(), &(&1.status in [:pending, :running])) + end + + defp await_active_tasks do + case active_tasks() do + [] -> + :ok + + _tasks -> + Process.sleep(@poll_interval_ms) + await_active_tasks() + end + end + + @doc """ + A 2-arity progress callback that prints deduplicated `[ 42%] message` + lines to stdout. + """ + def progress_printer do + &print_progress/2 + end + + defp print_progress(value, message) do + percent = value |> Kernel.*(100) |> round() |> min(100) |> max(0) + line = {percent, message} + + if Process.get({__MODULE__, :last_progress}) != line do + Process.put({__MODULE__, :last_progress}, line) + + if is_binary(message) and message != "" do + IO.puts("[#{String.pad_leading(Integer.to_string(percent), 3)}%] #{message}") + end + end + + :ok + end + + defp print_git_output(%{output: output}) when is_binary(output) and output != "" do + IO.puts(String.trim_trailing(output)) + end + + defp print_git_output(_result), do: :ok + + defp notify(entity_type, entity_id, action) do + {:ok, _notification} = CliSync.cli_mutation_performed(entity_type, entity_id, action) + :ok + end + + # Bulk maintenance touched an unknown set of rows; a wildcard notification + # per entity type makes a running app reload its lists. + defp notify_bulk_change do + Enum.each(@bulk_entity_types, ¬ify(&1, "*", :updated)) + end + + defp split_list(nil), do: [] + + defp split_list(value) when is_binary(value) do + value + |> String.split(",") + |> Enum.map(&String.trim/1) + |> Enum.reject(&(&1 == "")) + end + + defp json_list(value) when is_list(value), do: Enum.map(value, &to_string/1) + defp json_list(value) when is_binary(value), do: split_list(value) + defp json_list(_value), do: [] + + defp present(nil), do: nil + + defp present(value) when is_binary(value) do + case String.trim(value) do + "" -> nil + trimmed -> trimmed + end + end + + defp changeset_message(%Ecto.Changeset{} = changeset) do + changeset + |> Ecto.Changeset.traverse_errors(fn {message, opts} -> + Enum.reduce(opts, message, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", to_string(value)) + end) + end) + |> Enum.map_join("; ", fn {field, messages} -> "#{field}: #{Enum.join(messages, ", ")}" end) + end + + defp format_reason(reason) when is_binary(reason), do: reason + defp format_reason(%{message: message}) when is_binary(message), do: message + defp format_reason(%{guidance: guidance}) when is_binary(guidance), do: guidance + defp format_reason(reason), do: inspect(reason) +end diff --git a/lib/bds/cli/install.ex b/lib/bds/cli/install.ex new file mode 100644 index 0000000..a8f4206 --- /dev/null +++ b/lib/bds/cli/install.ex @@ -0,0 +1,57 @@ +defmodule BDS.CLI.Install do + @moduledoc """ + Installs the `bds-cli` launcher into `~/.local/bin` (issue #25), used by + the install buttons in the GUI and TUI settings. + + The installed file is a two-line shim exec'ing the release's + `cli/bin/bds-cli` launcher (shipped via `rel/overlays`), so the CLI always + runs the same release — and therefore the same settings and cache + database — as the installed application. + """ + + @launcher_name "bds-cli" + + @doc """ + Writes the `~/.local/bin/bds-cli` shim. Returns `{:ok, installed_path}`, + or `{:error, :no_release}` when not running from a packaged release (dev + runs have no launcher to point at). + """ + @spec install(keyword()) :: {:ok, Path.t()} | {:error, :no_release | File.posix()} + def install(opts \\ []) do + bin_dir = Keyword.get(opts, :bin_dir, Path.expand("~/.local/bin")) + + with {:ok, launcher} <- launcher_path(Keyword.get(opts, :release_root)) do + installed = Path.join(bin_dir, @launcher_name) + + with :ok <- File.mkdir_p(bin_dir), + :ok <- File.write(installed, shim_script(launcher)), + :ok <- File.chmod(installed, 0o755) do + {:ok, installed} + end + end + end + + @doc """ + The release's CLI launcher script, resolved from `RELEASE_ROOT` (set by + release scripts) or the code root dir (the release root when running a + release). `{:error, :no_release}` when neither contains the launcher. + """ + @spec launcher_path(Path.t() | nil) :: {:ok, Path.t()} | {:error, :no_release} + def launcher_path(release_root \\ nil) do + [release_root, System.get_env("RELEASE_ROOT"), to_string(:code.root_dir())] + |> Enum.reject(&(&1 in [nil, ""])) + |> Enum.map(&Path.join([&1, "cli", "bin", @launcher_name])) + |> Enum.find(&File.regular?/1) + |> case do + nil -> {:error, :no_release} + path -> {:ok, path} + end + end + + defp shim_script(launcher) do + """ + #!/bin/sh + exec "#{launcher}" "$@" + """ + end +end diff --git a/lib/bds/desktop/shell_commands.ex b/lib/bds/desktop/shell_commands.ex index 5edc195..15d44fc 100644 --- a/lib/bds/desktop/shell_commands.ex +++ b/lib/bds/desktop/shell_commands.ex @@ -120,7 +120,7 @@ defmodule BDS.Desktop.ShellCommands do "rebuild_embedding_index", "Rebuild Embedding Index", "Embeddings", - fn report -> rebuild_embedding_index_work(project, report) end + fn report -> Maintenance.rebuild_embedding_index(project.id, on_progress: report) end ) end @@ -221,7 +221,7 @@ defmodule BDS.Desktop.ShellCommands do defp dispatch("rebuild_database", project, _params) do group_id = task_group_id("rebuild_database") attrs = %{group_id: group_id, group_name: "Maintenance"} - [first_step | remaining_steps] = rebuild_database_steps(project) + [first_step | remaining_steps] = Maintenance.full_rebuild_steps(project.id) {:ok, posts_task} = Tasks.submit_task(first_step.name, first_step.work, attrs) @@ -692,102 +692,6 @@ defmodule BDS.Desktop.ShellCommands do |> length() > 1 end - defp rebuild_database_steps(project) do - [ - %{ - name: "Rebuild Posts From Files", - work: fn report -> - {:ok, posts} = - Maintenance.rebuild_from_filesystem(project.id, "post", - on_progress: report, - rebuild_embeddings: false - ) - - report.(1.0, "Post rebuild complete") - %{project_id: project.id, counts: %{posts: length(posts)}} - end - }, - %{ - name: "Rebuild Media From Files", - work: fn report -> - {:ok, media} = - Maintenance.rebuild_from_filesystem(project.id, "media", on_progress: report) - - report.(1.0, "Media rebuild complete") - %{project_id: project.id, counts: %{media: length(media)}} - end - }, - %{ - name: "Rebuild Scripts From Files", - work: fn report -> - {:ok, scripts} = - Maintenance.rebuild_from_filesystem(project.id, "script", on_progress: report) - - report.(1.0, "Script rebuild complete") - %{project_id: project.id, counts: %{scripts: length(scripts)}} - end - }, - %{ - name: "Rebuild Templates From Files", - work: fn report -> - {:ok, templates} = - Maintenance.rebuild_from_filesystem(project.id, "template", on_progress: report) - - report.(1.0, "Template rebuild complete") - %{project_id: project.id, counts: %{templates: length(templates)}} - end - }, - %{ - name: "Rebuild Post Links", - work: fn report -> - :ok = Posts.rebuild_post_links(project.id, on_progress: report) - report.(1.0, "Post links rebuilt") - %{project_id: project.id} - end - }, - %{ - name: "Regenerate Missing Thumbnails", - work: fn report -> - result = BDS.Media.regenerate_missing_thumbnails(project.id, on_progress: report) - report.(1.0, "Missing thumbnails regenerated") - Map.put(result, :project_id, project.id) - end - }, - %{ - name: "Rebuild Embedding Index", - work: fn report -> rebuild_embedding_index_work(project, report) end - } - ] - end - - defp rebuild_embedding_index_work(project, report) do - case Embeddings.rebuild_project(project.id, on_progress: report) do - {:ok, rebuilt_post_ids} -> - report.(1.0, "Embedding index rebuilt") - - %{ - project_id: project.id, - rebuilt_post_ids: rebuilt_post_ids, - rebuilt_count: length(rebuilt_post_ids) - } - - {:error, reason} -> - {:error, embedding_error_message(reason)} - end - end - - defp embedding_error_message(reason) do - detail = - case reason do - message when is_binary(message) -> message - {:embedding_backend_unavailable, _inner} -> "the embedding service did not start" - other -> inspect(other) - end - - "Could not build the embedding index: #{detail}. The model is downloaded on first use, " <> - "so check your internet connection — or turn off semantic similarity in Settings." - end - defp run_rebuild_sequence(_group_id, _attrs, []), do: :ok defp run_rebuild_sequence(group_id, attrs, [step | remaining_steps]) do diff --git a/lib/bds/desktop/shell_live/gallery_import.ex b/lib/bds/desktop/shell_live/gallery_import.ex index b520f52..e549fd9 100644 --- a/lib/bds/desktop/shell_live/gallery_import.ex +++ b/lib/bds/desktop/shell_live/gallery_import.ex @@ -15,14 +15,7 @@ defmodule BDS.Desktop.ShellLive.GalleryImport do @spec start(list(String.t()), String.t(), String.t(), String.t(), integer(), pid()) :: :ok def start(paths, project_id, post_id, language, concurrency_limit, parent) do {:ok, metadata} = Metadata.get_project_metadata(project_id) - main_language = metadata.main_language || language - blog_languages = metadata.blog_languages || [] - - translate_targets = - [main_language | blog_languages] - |> Enum.reject(&(&1 == language or is_nil(&1))) - |> Enum.uniq() - + translate_targets = translate_targets(metadata, language) {in_flight, remaining} = Enum.split(paths, concurrency_limit) tasks = @@ -48,6 +41,36 @@ defmodule BDS.Desktop.ShellLive.GalleryImport do send(parent, {:add_images_complete, length(paths)}) end + @doc """ + The translation targets for AI-generated media texts: the project main + language plus all blog languages, minus the source `language`. + """ + def translate_targets(metadata, language) do + main_language = metadata.main_language || language + blog_languages = metadata.blog_languages || [] + + [main_language | blog_languages] + |> Enum.reject(&(&1 == language or is_nil(&1))) + |> Enum.uniq() + end + + @doc """ + Imports a single file and runs the same best-effort AI enrichment + (title/alt/caption plus translations) as the gallery pipeline, without + linking the media to a post. Used by the CLI `media` command (issue #25). + + Returns `{:ok, media, display_title}` or `{:error, reason}`. + """ + def import_and_enrich(path, project_id, language, translate_targets) do + case Media.import_media(%{project_id: project_id, source_path: path}) do + {:ok, media} -> + {:ok, media, enrich_media(media, language, translate_targets)} + + {:error, reason} -> + {:error, reason} + end + end + defp drain_tasks( [], tasks, diff --git a/lib/bds/desktop/shell_live/settings_editor.ex b/lib/bds/desktop/shell_live/settings_editor.ex index bd2f26a..9ad4411 100644 --- a/lib/bds/desktop/shell_live/settings_editor.ex +++ b/lib/bds/desktop/shell_live/settings_editor.ex @@ -69,6 +69,15 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do {:noreply, socket} end + def handle_event("install_cli_tool", _params, socket) do + case BDS.UI.SettingsForm.run_action("install_cli") do + {:ok, message} -> LiveToast.send_toast(:info, message) + {:error, message} -> LiveToast.send_toast(:error, message) + end + + {:noreply, socket} + end + def handle_event("change_settings_search", %{"query" => query}, socket) do socket = socket @@ -302,7 +311,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do query, ~w(mcp claude copilot gemini opencode mistral codex agent server) ), - data_visible?: section_matches?(query, ~w(data rebuild maintenance folder filesystem)), + data_visible?: section_matches?(query, ~w(data rebuild maintenance folder filesystem cli)), supported_languages: @supported_languages, protected_categories: ManagedCategories.protected_categories() } diff --git a/lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex b/lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex index 1ca5204..b75dba6 100644 --- a/lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex +++ b/lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex @@ -381,6 +381,15 @@ +
+
+ +

<%= dgettext("ui", "Install the bds-cli tool to ~/.local/bin; it uses the same settings and cache database as the app") %>

+
+
+ +
+
<% end %> diff --git a/lib/bds/maintenance.ex b/lib/bds/maintenance.ex index 72dfa9a..553df46 100644 --- a/lib/bds/maintenance.ex +++ b/lib/bds/maintenance.ex @@ -142,4 +142,106 @@ defmodule BDS.Maintenance do {:ok, %{diff_reports: diff_reports, orphan_reports: orphan_reports}} end + + @doc """ + The canonical full-rebuild sequence, shared by the GUI "Rebuild Database" + command and the CLI `rebuild` command so the two can never drift. Each step + is `%{name: String.t(), work: (report -> result)}` where `report` is a + 2-arity progress callback and a `{:error, message}` result marks failure. + """ + def full_rebuild_steps(project_id) when is_binary(project_id) do + [ + %{ + name: "Rebuild Posts From Files", + work: fn report -> + {:ok, posts} = + rebuild_from_filesystem(project_id, "post", + on_progress: report, + rebuild_embeddings: false + ) + + report.(1.0, "Post rebuild complete") + %{project_id: project_id, counts: %{posts: length(posts)}} + end + }, + %{ + name: "Rebuild Media From Files", + work: fn report -> + {:ok, media} = rebuild_from_filesystem(project_id, "media", on_progress: report) + report.(1.0, "Media rebuild complete") + %{project_id: project_id, counts: %{media: length(media)}} + end + }, + %{ + name: "Rebuild Scripts From Files", + work: fn report -> + {:ok, scripts} = rebuild_from_filesystem(project_id, "script", on_progress: report) + report.(1.0, "Script rebuild complete") + %{project_id: project_id, counts: %{scripts: length(scripts)}} + end + }, + %{ + name: "Rebuild Templates From Files", + work: fn report -> + {:ok, templates} = rebuild_from_filesystem(project_id, "template", on_progress: report) + report.(1.0, "Template rebuild complete") + %{project_id: project_id, counts: %{templates: length(templates)}} + end + }, + %{ + name: "Rebuild Post Links", + work: fn report -> + :ok = BDS.Posts.rebuild_post_links(project_id, on_progress: report) + report.(1.0, "Post links rebuilt") + %{project_id: project_id} + end + }, + %{ + name: "Regenerate Missing Thumbnails", + work: fn report -> + result = BDS.Media.regenerate_missing_thumbnails(project_id, on_progress: report) + report.(1.0, "Missing thumbnails regenerated") + Map.put(result, :project_id, project_id) + end + }, + %{ + name: "Rebuild Embedding Index", + work: fn report -> rebuild_embedding_index(project_id, on_progress: report) end + } + ] + end + + @doc """ + Rebuilds the embedding index for the project, mapping backend failures to a + user-facing message (`{:error, message}`). + """ + def rebuild_embedding_index(project_id, opts \\ []) when is_binary(project_id) do + on_progress = progress_callback(opts) + + case Embeddings.rebuild_project(project_id, on_progress: on_progress) do + {:ok, rebuilt_post_ids} -> + on_progress.(1.0, "Embedding index rebuilt") + + %{ + project_id: project_id, + rebuilt_post_ids: rebuilt_post_ids, + rebuilt_count: length(rebuilt_post_ids) + } + + {:error, reason} -> + {:error, embedding_error_message(reason)} + end + end + + defp embedding_error_message(reason) do + detail = + case reason do + message when is_binary(message) -> message + {:embedding_backend_unavailable, _inner} -> "the embedding service did not start" + other -> inspect(other) + end + + "Could not build the embedding index: #{detail}. The model is downloaded on first use, " <> + "so check your internet connection — or turn off semantic similarity in Settings." + end end diff --git a/lib/bds/server.ex b/lib/bds/server.ex index 917b272..7926e9d 100644 --- a/lib/bds/server.ex +++ b/lib/bds/server.ex @@ -13,15 +13,18 @@ defmodule BDS.Server do @doc """ Resolves the boot mode from the `BDS_MODE` environment variable: `"server"` (headless + SSH daemon), `"tui"` (headless + SSH daemon + - a TUI attached to the launching terminal), anything else is desktop. + a TUI attached to the launching terminal), `"cli"` (headless one-shot + command run — no SSH daemon, no HTTP listener, no watcher; issue #25), + anything else is desktop. """ - @spec mode(String.t() | nil) :: :desktop | :server | :tui + @spec mode(String.t() | nil) :: :desktop | :server | :tui | :cli def mode(value \\ System.get_env("BDS_MODE")) def mode(value) when is_binary(value) do case String.downcase(value) do "server" -> :server "tui" -> :tui + "cli" -> :cli _other -> :desktop end end @@ -41,9 +44,9 @@ defmodule BDS.Server do signal and a local launch always has a graphical session. Windows always boots the GUI. """ - @spec effective_mode(:desktop | :server | :tui, {atom(), atom()}, %{ + @spec effective_mode(:desktop | :server | :tui | :cli, {atom(), atom()}, %{ optional(String.t()) => String.t() - }) :: :desktop | :server | :tui + }) :: :desktop | :server | :tui | :cli def effective_mode(mode \\ mode(), os_type \\ :os.type(), env \\ System.get_env()) def effective_mode(:desktop, {:unix, :darwin}, env) do @@ -79,11 +82,14 @@ defmodule BDS.Server do Called from `config/runtime.exs`: runtime config is the only hook that runs before dependency applications start, in both `mix run` and releases. """ - @spec prepare_boot_env(:desktop | :server | :tui, (-> :ok)) :: :desktop | :server | :tui + @spec prepare_boot_env(:desktop | :server | :tui | :cli, (-> :ok)) :: + :desktop | :server | :tui | :cli def prepare_boot_env(mode \\ effective_mode(), redirect_logs \\ &redirect_logging_to_file/0) do if mode != :desktop, do: System.put_env("NO_WX", "1") - if mode == :tui do + # CLI mode owns stdout for command output the same way the local TUI owns + # the terminal: console logging goes to the rotating file instead. + if mode in [:tui, :cli] do Application.put_env(:bumblebee, :progress_bar_enabled, false) redirect_logs.() end @@ -138,7 +144,7 @@ defmodule BDS.Server do and clients arrive through the key-authenticated SSH tunnel, which the per-boot webview token would otherwise lock out. """ - @spec desktop_auth_required?(:desktop | :server | :tui) :: boolean() + @spec desktop_auth_required?(:desktop | :server | :tui | :cli) :: boolean() def desktop_auth_required?(mode \\ effective_mode()) def desktop_auth_required?(:desktop), do: true def desktop_auth_required?(_mode), do: false diff --git a/lib/bds/settings.ex b/lib/bds/settings.ex index aaf367b..f1ebdd2 100644 --- a/lib/bds/settings.ex +++ b/lib/bds/settings.ex @@ -3,10 +3,17 @@ defmodule BDS.Settings do Persistence layer for global application settings stored as key-value pairs in the database. """ + import Ecto.Query, only: [from: 2] + alias BDS.Persistence alias BDS.Repo alias BDS.Settings.Setting + @spec list_global_settings() :: [{String.t(), String.t()}] + def list_global_settings do + Repo.all(from setting in Setting, order_by: setting.key, select: {setting.key, setting.value}) + end + @spec get_global_setting(String.t()) :: String.t() | nil def get_global_setting(key) do case Repo.get(Setting, key) do diff --git a/lib/bds/tui.ex b/lib/bds/tui.ex index bb0ae4e..6ee059a 100644 --- a/lib/bds/tui.ex +++ b/lib/bds/tui.ex @@ -487,6 +487,15 @@ defmodule BDS.TUI do index = Enum.find_index(options, &(&1 == field.value)) || 0 {:noreply, update_field(state, field.key, Enum.at(options, rem(index + 1, length(options))))} + %{type: :action} = field -> + message = + case SettingsForm.run_action(field.key) do + {:ok, message} -> message + {:error, message} -> message + end + + {:noreply, toast(state, field.label, message)} + _other -> {:noreply, state} end diff --git a/lib/bds/ui/settings_form.ex b/lib/bds/ui/settings_form.ex index b6c8177..5c7cb44 100644 --- a/lib/bds/ui/settings_form.ex +++ b/lib/bds/ui/settings_form.ex @@ -3,8 +3,8 @@ defmodule BDS.UI.SettingsForm do Renderer-agnostic preferences forms (issue #29). Exposes each settings section of the GUI settings editor as a flat list - of typed fields (`:text`, `:bool`, `:enum`, `:info`) that the TUI can - render and edit generically. `save/3` writes through the same backends + of typed fields (`:text`, `:bool`, `:enum`, `:info`, `:action`) that the + TUI can render and edit generically. `save/3` writes through the same backends as the GUI editor — `BDS.Metadata`, `BDS.Settings`, `BDS.AI` and `BDS.MCP.AgentConfig` — so both frontends operate on the same preferences. @@ -28,7 +28,7 @@ defmodule BDS.UI.SettingsForm do @type field :: %{ key: String.t(), label: String.t(), - type: :text | :bool | :enum | :info, + type: :text | :bool | :enum | :info | :action, value: term(), options: [String.t()] } @@ -217,6 +217,11 @@ defmodule BDS.UI.SettingsForm do "data_hint", dgettext("ui", "Maintenance"), dgettext("ui", "Rebuild and maintenance commands are available under the : prompt.") + ), + action( + "install_cli", + dgettext("ui", "Command Line Tool"), + dgettext("ui", "Install CLI Tool") ) ]) end @@ -351,6 +356,28 @@ defmodule BDS.UI.SettingsForm do def save(_section, _project_id, _values), do: :ok + # ── Actions ────────────────────────────────────────────────────────────── + + @doc """ + Runs an `:action` field (issue #25) and returns a localized result + message for the invoking frontend (GUI toast or TUI status line). + """ + @spec run_action(String.t()) :: {:ok, String.t()} | {:error, String.t()} + def run_action("install_cli") do + case BDS.CLI.Install.install() do + {:ok, path} -> + {:ok, dgettext("ui", "CLI tool installed to %{path}", path: path)} + + {:error, :no_release} -> + {:error, dgettext("ui", "The CLI tool can only be installed from the packaged application")} + + {:error, reason} -> + {:error, dgettext("ui", "Installing the CLI tool failed: %{reason}", reason: inspect(reason))} + end + end + + def run_action(_key), do: {:error, dgettext("ui", "Unknown settings action")} + # ── Helpers ────────────────────────────────────────────────────────────── defp form(section, title, fields), do: %{section: section, title: title, fields: fields} @@ -367,6 +394,9 @@ defmodule BDS.UI.SettingsForm do defp info(key, label, value), do: %{key: key, label: label, type: :info, value: value, options: []} + defp action(key, label, caption), + do: %{key: key, label: label, type: :action, value: caption, options: []} + defp editor_settings do %{ "default_mode" => Settings.get_global_setting("ui.preferred_editor_mode") || "markdown", diff --git a/mix.exs b/mix.exs index 27e8ec7..6b453d9 100644 --- a/mix.exs +++ b/mix.exs @@ -50,6 +50,7 @@ defmodule BDS.MixProject do {:ecto_sqlite3, "~> 0.24"}, {:luerl, "~> 1.5"}, {:jason, "~> 1.4"}, + {:optimus, "~> 0.5"}, {:mdex, "~> 0.13"}, {:liquex, "~> 0.13.1"}, {:plug, "~> 1.18"}, diff --git a/mix.lock b/mix.lock index 07f568d..98ac638 100644 --- a/mix.lock +++ b/mix.lock @@ -62,6 +62,7 @@ "nx_image": {:hex, :nx_image, "0.1.2", "0c6e3453c1dc30fc80c723a54861204304cebc8a89ed3b806b972c73ee5d119d", [:mix], [{:nx, "~> 0.4", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "9161863c42405ddccb6dbbbeae078ad23e30201509cc804b3b3a7c9e98764b81"}, "nx_signal": {:hex, :nx_signal, "0.2.0", "e1ca0318877b17c81ce8906329f5125f1e2361e4c4235a5baac8a95ee88ea98e", [:mix], [{:nx, "~> 0.6", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "7247e5e18a177a59c4cb5355952900c62fdeadeb2bad02a9a34237b68744e2bb"}, "oncrash": {:hex, :oncrash, "0.1.0", "9cf4ae8eba4ea250b579470172c5e9b8c75418b2264de7dbcf42e408d62e30fb", [:mix], [], "hexpm", "6968e775491cd857f9b6ff940bf2574fd1c2fab84fa7e14d5f56c39174c00018"}, + "optimus": {:hex, :optimus, "0.6.1", "b493bdc7ee71035fdf8cbfbf158b1863e2d622b65c3906153e4042815c901fca", [:mix], [], "hexpm", "c0db4107a51f5af94de8b05e4208333ebb8016a3bfdbcd74df6e5c99829db17f"}, "phoenix": {:hex, :phoenix, "1.8.8", "ada3d761359274178180c0e992ef0c2b536bd7c3bd75ebba94acbf39ab4347fe", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "f0c843037bd2e7012fc1d1ec9574dfa6972b7e3d09e9b77fd23aa283af0aa994"}, "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.28", "8a8e123d018025f756605a2fb02a4854f0d3cd7b207f710fef1fd5d9d72d0254", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "24faad535b65089642c3a7d84088109dc58f49c1f1c5a978659855d643466353"}, diff --git a/priv/gettext/de/LC_MESSAGES/ui.po b/priv/gettext/de/LC_MESSAGES/ui.po index c335ce9..5a908e2 100644 --- a/priv/gettext/de/LC_MESSAGES/ui.po +++ b/priv/gettext/de/LC_MESSAGES/ui.po @@ -80,13 +80,13 @@ msgstr "KI-Einstellungen" #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:42 #: lib/bds/desktop/shell_live/overlay_manager.ex:72 -#: lib/bds/desktop/shell_live/post_editor.ex:920 +#: lib/bds/desktop/shell_live/post_editor.ex:906 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 -#: lib/bds/tui.ex:937 -#: lib/bds/tui.ex:940 -#: lib/bds/tui.ex:943 -#: lib/bds/tui.ex:951 -#: lib/bds/tui.ex:1753 +#: lib/bds/tui.ex:946 +#: lib/bds/tui.ex:949 +#: lib/bds/tui.ex:952 +#: lib/bds/tui.ex:960 +#: lib/bds/tui.ex:1762 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "KI-Vorschlaege" @@ -349,8 +349,8 @@ msgstr "Automatisch" #: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:557 #: lib/bds/desktop/shell_live/overlay_manager.ex:73 -#: lib/bds/desktop/shell_live/post_editor.ex:765 -#: lib/bds/desktop/shell_live/post_editor.ex:814 +#: lib/bds/desktop/shell_live/post_editor.ex:752 +#: lib/bds/desktop/shell_live/post_editor.ex:801 #, elixir-autogen, elixir-format msgid "Automatic AI actions stay gated by airplane mode." msgstr "Automatische KI-Aktionen bleiben durch den Flugmodus gesperrt." @@ -574,8 +574,8 @@ msgstr "Tab schließen" msgid "Collapse unchanged diff hunks" msgstr "Unveränderte Diff-Blöcke einklappen" -#: lib/bds/desktop/shell_live/overlay_manager.ex:422 -#: lib/bds/tui.ex:1849 +#: lib/bds/desktop/shell_live/overlay_manager.ex:423 +#: lib/bds/tui.ex:1858 #, elixir-autogen, elixir-format msgid "Command completed" msgstr "Befehl abgeschlossen" @@ -593,7 +593,7 @@ msgstr "Bestaetigen" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 -#: lib/bds/tui.ex:1219 +#: lib/bds/tui.ex:1228 #: lib/bds/ui/settings_form.ex:137 #: lib/bds/ui/sidebar.ex:801 #, elixir-autogen, elixir-format @@ -751,7 +751,7 @@ msgstr "Standard-Bearbeitungsmodus und Diff-Darstellung" msgid "Delete" msgstr "Loeschen" -#: lib/bds/desktop/shell_live/overlay_manager.ex:278 +#: lib/bds/desktop/shell_live/overlay_manager.ex:279 #, elixir-autogen, elixir-format msgid "Delete Media" msgstr "Medium loeschen" @@ -798,9 +798,9 @@ msgstr "Erkennen" #: lib/bds/desktop/shell_live/media_editor.ex:214 #: lib/bds/desktop/shell_live/media_editor.ex:220 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:59 -#: lib/bds/desktop/shell_live/post_editor.ex:764 -#: lib/bds/desktop/shell_live/post_editor.ex:793 -#: lib/bds/desktop/shell_live/post_editor.ex:799 +#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:780 +#: lib/bds/desktop/shell_live/post_editor.ex:786 #, elixir-autogen, elixir-format msgid "Detect Language" msgstr "Sprache erkennen" @@ -1040,7 +1040,7 @@ msgid "Filename" msgstr "Dateiname" #: lib/bds/desktop/menu_bar.ex:254 -#: lib/bds/tui.ex:1813 +#: lib/bds/tui.ex:1822 #, elixir-autogen, elixir-format msgid "Fill Missing Translations" msgstr "Fehlende Übersetzungen ergänzen" @@ -1051,7 +1051,7 @@ msgid "Find" msgstr "Suchen" #: lib/bds/desktop/menu_bar.ex:255 -#: lib/bds/tui.ex:1816 +#: lib/bds/tui.ex:1825 #, elixir-autogen, elixir-format msgid "Find Duplicate Posts" msgstr "Doppelte Beiträge finden" @@ -1078,14 +1078,14 @@ msgid "Gallery" msgstr "Galerie" #: lib/bds/desktop/menu_bar.ex:256 -#: lib/bds/tui.ex:1797 +#: lib/bds/tui.ex:1806 #, elixir-autogen, elixir-format msgid "Generate Site" msgstr "Website generieren" #: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live/socket_state.ex:109 -#: lib/bds/tui.ex:1662 +#: lib/bds/tui.ex:1671 #: lib/bds/ui/sidebar.ex:826 #, elixir-autogen, elixir-format msgid "Git" @@ -1128,7 +1128,7 @@ msgstr "Host" #: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 -#: lib/bds/desktop/shell_live/post_editor.ex:1038 +#: lib/bds/desktop/shell_live/post_editor.ex:1024 #, elixir-autogen, elixir-format msgid "Idle" msgstr "Leerlauf" @@ -1299,7 +1299,7 @@ msgid "Language" msgstr "Sprache" #: lib/bds/desktop/shell_live/media_editor.ex:221 -#: lib/bds/desktop/shell_live/post_editor.ex:800 +#: lib/bds/desktop/shell_live/post_editor.ex:787 #, elixir-autogen, elixir-format msgid "Language detection failed." msgstr "Spracherkennung fehlgeschlagen." @@ -1345,7 +1345,7 @@ msgstr "Mehr laden" #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:50 #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:57 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:351 -#: lib/bds/ui/settings_form.ex:234 +#: lib/bds/ui/settings_form.ex:239 #: lib/bds/ui/sidebar.ex:816 #, elixir-autogen, elixir-format msgid "MCP" @@ -1386,7 +1386,7 @@ msgstr "Zuordnen zu..." msgid "Mapped" msgstr "Zugeordnet" -#: lib/bds/desktop/shell_live/post_editor.ex:1041 +#: lib/bds/desktop/shell_live/post_editor.ex:1027 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:123 #, elixir-autogen, elixir-format msgid "Markdown" @@ -1404,9 +1404,9 @@ msgstr "Maximale Beiträge pro Seite" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 -#: lib/bds/tui.ex:1657 -#: lib/bds/tui.ex:1878 -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1666 +#: lib/bds/tui.ex:1887 +#: lib/bds/tui.ex:1890 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:578 @@ -1452,8 +1452,8 @@ msgstr "Metadaten" #: lib/bds/tui.ex:278 #: lib/bds/tui.ex:284 #: lib/bds/tui.ex:295 -#: lib/bds/tui.ex:1412 -#: lib/bds/tui.ex:1794 +#: lib/bds/tui.ex:1421 +#: lib/bds/tui.ex:1803 #: lib/bds/ui/registry.ex:111 #, elixir-autogen, elixir-format msgid "Metadata Diff" @@ -1787,8 +1787,8 @@ msgid "Open Data Folder" msgstr "Datenordner öffnen" #: lib/bds/desktop/shell_live/index.html.heex:644 -#: lib/bds/tui.ex:792 -#: lib/bds/tui.ex:797 +#: lib/bds/tui.ex:801 +#: lib/bds/tui.ex:806 #, elixir-autogen, elixir-format msgid "Open Existing Blog" msgstr "Bestehenden Blog öffnen" @@ -1800,7 +1800,7 @@ msgid "Open Settings" msgstr "Einstellungen öffnen" #: lib/bds/desktop/menu_bar.ex:217 -#: lib/bds/tui.ex:1818 +#: lib/bds/tui.ex:1827 #, elixir-autogen, elixir-format msgid "Open in Browser" msgstr "Im Browser öffnen" @@ -1896,16 +1896,16 @@ msgid "Persist the detected language for this media item" msgstr "Die erkannte Sprache für dieses Medium speichern" #: lib/bds/desktop/shell_live/misc_editor.ex:733 -#: lib/bds/desktop/shell_live/post_editor.ex:539 -#: lib/bds/desktop/shell_live/post_editor.ex:543 -#: lib/bds/desktop/shell_live/post_editor.ex:582 -#: lib/bds/desktop/shell_live/post_editor.ex:586 -#: lib/bds/desktop/shell_live/post_editor.ex:624 -#: lib/bds/desktop/shell_live/post_editor.ex:639 -#: lib/bds/desktop/shell_live/post_editor.ex:668 -#: lib/bds/desktop/shell_live/post_editor.ex:671 -#: lib/bds/desktop/shell_live/post_editor.ex:751 -#: lib/bds/desktop/shell_live/post_editor.ex:754 +#: lib/bds/desktop/shell_live/post_editor.ex:526 +#: lib/bds/desktop/shell_live/post_editor.ex:530 +#: lib/bds/desktop/shell_live/post_editor.ex:569 +#: lib/bds/desktop/shell_live/post_editor.ex:573 +#: lib/bds/desktop/shell_live/post_editor.ex:611 +#: lib/bds/desktop/shell_live/post_editor.ex:626 +#: lib/bds/desktop/shell_live/post_editor.ex:655 +#: lib/bds/desktop/shell_live/post_editor.ex:658 +#: lib/bds/desktop/shell_live/post_editor.ex:738 +#: lib/bds/desktop/shell_live/post_editor.ex:741 #: lib/bds/desktop/shell_live/sidebar_components.ex:651 #: lib/bds/desktop/shell_live/sidebar_delete.ex:174 #: lib/bds/ui/registry.ex:99 @@ -1936,12 +1936,12 @@ msgstr "Beitragsvorlage" msgid "Post is marked as do-not-translate but has translations" msgstr "Beitrag ist als nicht-übersetzen markiert, hat aber Übersetzungen" -#: lib/bds/desktop/shell_live/post_editor.ex:582 +#: lib/bds/desktop/shell_live/post_editor.ex:569 #, elixir-autogen, elixir-format msgid "Post published" msgstr "Beitrag veröffentlicht" -#: lib/bds/desktop/shell_live/post_editor.ex:539 +#: lib/bds/desktop/shell_live/post_editor.ex:526 #, elixir-autogen, elixir-format msgid "Post saved" msgstr "Beitrag gespeichert" @@ -1949,8 +1949,8 @@ msgstr "Beitrag gespeichert" #: lib/bds/desktop/menu_bar.ex:233 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 -#: lib/bds/tui.ex:1656 -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1665 +#: lib/bds/tui.ex:1680 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1967,7 +1967,7 @@ msgstr "Beiträge (%{count})" msgid "Preferences" msgstr "Einstellungen" -#: lib/bds/desktop/shell_live/post_editor.ex:1042 +#: lib/bds/desktop/shell_live/post_editor.ex:1028 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:124 #, elixir-autogen, elixir-format msgid "Preview" @@ -2010,8 +2010,8 @@ msgstr "Projekt und Veröffentlichung" #: lib/bds/desktop/shell_live/chat_surface.ex:15 #: lib/bds/desktop/shell_live/index.html.heex:623 -#: lib/bds/tui.ex:705 -#: lib/bds/tui.ex:710 +#: lib/bds/tui.ex:714 +#: lib/bds/tui.ex:719 #, elixir-autogen, elixir-format msgid "Projects" msgstr "Projekte" @@ -2040,7 +2040,7 @@ msgid "Publish Selected" msgstr "Ausgewähltes veröffentlichen" #: lib/bds/desktop/shell_data.ex:165 -#: lib/bds/desktop/shell_live/post_editor.ex:1036 +#: lib/bds/desktop/shell_live/post_editor.ex:1022 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 #, elixir-autogen, elixir-format @@ -2079,15 +2079,15 @@ msgid "Ready to import:" msgstr "Bereit zum Import:" #: lib/bds/desktop/menu_bar.ex:248 -#: lib/bds/tui.ex:789 -#: lib/bds/tui.ex:1798 +#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:1807 #, elixir-autogen, elixir-format msgid "Rebuild Database" msgstr "Datenbank neu aufbauen" #: lib/bds/desktop/menu_bar.ex:250 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 -#: lib/bds/tui.ex:1802 +#: lib/bds/tui.ex:1811 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "Embedding-Index neu aufbauen" @@ -2145,7 +2145,7 @@ msgid "Refresh Translation" msgstr "Übersetzung aktualisieren" #: lib/bds/desktop/menu_bar.ex:252 -#: lib/bds/tui.ex:1805 +#: lib/bds/tui.ex:1814 #, elixir-autogen, elixir-format msgid "Regenerate Calendar" msgstr "Kalender neu erzeugen" @@ -2156,7 +2156,7 @@ msgid "Regenerate Missing Thumbnails" msgstr "Fehlende Vorschaubilder neu erzeugen" #: lib/bds/desktop/menu_bar.ex:249 -#: lib/bds/tui.ex:1799 +#: lib/bds/tui.ex:1808 #, elixir-autogen, elixir-format msgid "Reindex Text" msgstr "Text neu indizieren" @@ -2240,7 +2240,7 @@ msgstr "Lösung" msgid "Result" msgstr "Ergebnis" -#: lib/bds/desktop/shell_live/post_editor.ex:1037 +#: lib/bds/desktop/shell_live/post_editor.ex:1023 #, elixir-autogen, elixir-format msgid "Reverted" msgstr "Zurückgesetzt" @@ -2293,7 +2293,7 @@ msgid "Save Translation" msgstr "Übersetzung speichern" #: lib/bds/desktop/shell_live/media_editor.ex:730 -#: lib/bds/desktop/shell_live/post_editor.ex:1035 +#: lib/bds/desktop/shell_live/post_editor.ex:1021 #, elixir-autogen, elixir-format msgid "Saved" msgstr "Gespeichert" @@ -2344,7 +2344,7 @@ msgstr "Scripting-Funktionen werden in der Neufassung auf Anwendungsebene konfig #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 -#: lib/bds/tui.ex:1659 +#: lib/bds/tui.ex:1668 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2451,7 +2451,7 @@ msgstr "Semantische Ähnlichkeit" #: lib/bds/desktop/shell_live/settings_editor/project_settings.ex:60 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:11 -#: lib/bds/tui.ex:1661 +#: lib/bds/tui.ex:1670 #: lib/bds/ui/registry.ex:86 #: lib/bds/ui/registry.ex:101 #: lib/bds/ui/sidebar.ex:795 @@ -2477,7 +2477,7 @@ msgstr "Website" #: lib/bds/desktop/shell_live/misc_editor.ex:412 #: lib/bds/tui.ex:311 #: lib/bds/tui.ex:313 -#: lib/bds/tui.ex:1417 +#: lib/bds/tui.ex:1426 #: lib/bds/ui/registry.ex:125 #, elixir-autogen, elixir-format msgid "Site Validation" @@ -2541,7 +2541,7 @@ msgstr "Stopp" #: lib/bds/desktop/shell_live/settings_editor/style_editor.ex:76 #: lib/bds/desktop/shell_live/settings_editor_html/style_editor.html.heex:3 #: lib/bds/ui/registry.ex:102 -#: lib/bds/ui/settings_form.ex:241 +#: lib/bds/ui/settings_form.ex:246 #: lib/bds/ui/sidebar.ex:817 #, elixir-autogen, elixir-format msgid "Style" @@ -2600,7 +2600,7 @@ msgstr "Schlagwortname" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 -#: lib/bds/tui.ex:1660 +#: lib/bds/tui.ex:1669 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2612,7 +2612,7 @@ msgstr "Tags" #: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "Tasks" msgstr "Aufgaben" @@ -2658,7 +2658,7 @@ msgstr "Template-Syntax ist gültig" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 -#: lib/bds/tui.ex:1658 +#: lib/bds/tui.ex:1667 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2772,7 +2772,7 @@ msgstr "Seitenleiste umschalten" #: lib/bds/desktop/shell_live/media_editor.ex:363 #: lib/bds/desktop/shell_live/media_editor.ex:556 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:76 -#: lib/bds/desktop/shell_live/post_editor.ex:813 +#: lib/bds/desktop/shell_live/post_editor.ex:800 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:60 #, elixir-autogen, elixir-format msgid "Translate" @@ -2849,7 +2849,7 @@ msgstr "Verknüpfung mit Beitrag aufheben" #: lib/bds/desktop/shell_live/media_editor.ex:729 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:10 -#: lib/bds/desktop/shell_live/post_editor.ex:1034 +#: lib/bds/desktop/shell_live/post_editor.ex:1020 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:7 #, elixir-autogen, elixir-format msgid "Unsaved" @@ -2882,7 +2882,7 @@ msgid "Updated URLs" msgstr "Aktualisierte URLs" #: lib/bds/desktop/menu_bar.ex:259 -#: lib/bds/tui.ex:1817 +#: lib/bds/tui.ex:1826 #, elixir-autogen, elixir-format msgid "Upload Site" msgstr "Website hochladen" @@ -2910,13 +2910,13 @@ msgid "Validate" msgstr "Validieren" #: lib/bds/desktop/menu_bar.ex:258 -#: lib/bds/tui.ex:1795 +#: lib/bds/tui.ex:1804 #, elixir-autogen, elixir-format msgid "Validate Site" msgstr "Website validieren" #: lib/bds/desktop/menu_bar.ex:253 -#: lib/bds/tui.ex:1808 +#: lib/bds/tui.ex:1817 #, elixir-autogen, elixir-format msgid "Validate Translations" msgstr "Übersetzungen validieren" @@ -3350,12 +3350,12 @@ msgstr "Archivieren" msgid "Move this post to the archive" msgstr "Diesen Beitrag ins Archiv verschieben" -#: lib/bds/desktop/shell_live/post_editor.ex:668 +#: lib/bds/desktop/shell_live/post_editor.ex:655 #, elixir-autogen, elixir-format msgid "Post archived" msgstr "Beitrag archiviert" -#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:738 #, elixir-autogen, elixir-format msgid "Post unarchived" msgstr "Beitrag wiederhergestellt" @@ -3397,7 +3397,7 @@ msgid "Commit" msgstr "Commit" #: lib/bds/desktop/shell_live/sidebar_components.ex:555 -#: lib/bds/tui.ex:1239 +#: lib/bds/tui.ex:1248 #, elixir-autogen, elixir-format msgid "Commit message" msgstr "Commit-Nachricht" @@ -3421,7 +3421,7 @@ msgid "Fetch" msgstr "Abrufen" #: lib/bds/desktop/shell_live/sidebar_components.ex:586 -#: lib/bds/tui.ex:1074 +#: lib/bds/tui.ex:1083 #, elixir-autogen, elixir-format msgid "History" msgstr "Verlauf" @@ -3469,7 +3469,7 @@ msgstr "LFS bereinigen" #: lib/bds/desktop/shell_live/git_handler.ex:17 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 -#: lib/bds/tui.ex:957 +#: lib/bds/tui.ex:966 #, elixir-autogen, elixir-format msgid "Pull" msgstr "Pull" @@ -3477,7 +3477,7 @@ msgstr "Pull" #: lib/bds/desktop/shell_live/git_handler.ex:18 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 -#: lib/bds/tui.ex:958 +#: lib/bds/tui.ex:967 #, elixir-autogen, elixir-format msgid "Push" msgstr "Push" @@ -3543,18 +3543,18 @@ msgstr "Blogmark" msgid "Open a project before importing a blogmark." msgstr "Öffnen Sie ein Projekt, bevor Sie ein Blogmark importieren." -#: lib/bds/desktop/shell_live/post_editor.ex:694 +#: lib/bds/desktop/shell_live/post_editor.ex:681 #, elixir-autogen, elixir-format msgid "Added %{name}" msgstr "%{name} hinzugefügt" -#: lib/bds/desktop/shell_live/post_editor.ex:701 +#: lib/bds/desktop/shell_live/post_editor.ex:688 #, elixir-autogen, elixir-format msgid "Failed to import %{path}: %{reason}" msgstr "Import von %{path} fehlgeschlagen: %{reason}" -#: lib/bds/desktop/shell_live/post_editor.ex:693 -#: lib/bds/desktop/shell_live/post_editor.ex:700 +#: lib/bds/desktop/shell_live/post_editor.ex:680 +#: lib/bds/desktop/shell_live/post_editor.ex:687 #, elixir-autogen, elixir-format msgid "Insert Image" msgstr "Bild einfügen" @@ -3569,7 +3569,7 @@ msgstr "Bookmarklet in die Zwischenablage kopiert" msgid "Copy Bookmarklet to Clipboard" msgstr "Bookmarklet in die Zwischenablage kopieren" -#: lib/bds/desktop/shell_live/overlay_manager.ex:300 +#: lib/bds/desktop/shell_live/overlay_manager.ex:301 #, elixir-autogen, elixir-format msgid "Delete Tag" msgstr "Tag löschen" @@ -3590,7 +3590,7 @@ msgid "Suggested tags" msgstr "Vorgeschlagene Tags" #: lib/bds/desktop/menu_bar.ex:257 -#: lib/bds/tui.ex:1796 +#: lib/bds/tui.ex:1805 #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "Website vollständig neu generieren" @@ -3687,12 +3687,12 @@ msgstr "OK" 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." -#: lib/bds/tui.ex:1754 +#: lib/bds/tui.ex:1763 #, elixir-autogen, elixir-format msgid "AI is unavailable in airplane mode without a local endpoint." msgstr "KI ist im Flugmodus ohne lokalen Endpunkt nicht verfügbar." -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1890 #, elixir-autogen, elixir-format msgid "Could not load this file." msgstr "Diese Datei konnte nicht geladen werden." @@ -3702,68 +3702,68 @@ msgstr "Diese Datei konnte nicht geladen werden." msgid "Editing this item is not available in the terminal UI yet." msgstr "Die Bearbeitung dieses Eintrags ist in der Terminal-Oberfläche noch nicht verfügbar." -#: lib/bds/tui.ex:940 +#: lib/bds/tui.ex:949 #, elixir-autogen, elixir-format msgid "No suggestions returned." msgstr "Keine Vorschläge erhalten." -#: lib/bds/tui.ex:1878 +#: lib/bds/tui.ex:1887 #, elixir-autogen, elixir-format msgid "Only images can be previewed." msgstr "Nur Bilder können in der Vorschau angezeigt werden." -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1680 #, elixir-autogen, elixir-format msgid "Post not found." msgstr "Beitrag nicht gefunden." -#: lib/bds/tui.ex:1174 +#: lib/bds/tui.ex:1183 #, elixir-autogen, elixir-format msgid "Press enter to preview %{name}." msgstr "Drücken Sie Enter, um %{name} in der Vorschau anzuzeigen." -#: lib/bds/tui.ex:1724 +#: lib/bds/tui.ex:1733 #, elixir-autogen, elixir-format msgid "Published." msgstr "Veröffentlicht." -#: lib/bds/tui.ex:1740 +#: lib/bds/tui.ex:1749 #, elixir-autogen, elixir-format msgid "Save before switching languages." msgstr "Speichern Sie vor dem Sprachwechsel." -#: lib/bds/tui.ex:524 -#: lib/bds/tui.ex:1725 +#: lib/bds/tui.ex:533 +#: lib/bds/tui.ex:1734 #, elixir-autogen, elixir-format msgid "Saved." msgstr "Gespeichert." -#: lib/bds/tui.ex:1177 +#: lib/bds/tui.ex:1186 #, elixir-autogen, elixir-format msgid "Select an entry and press enter to open it." msgstr "Wählen Sie einen Eintrag aus und öffnen Sie ihn mit Enter." -#: lib/bds/tui.ex:937 +#: lib/bds/tui.ex:946 #, elixir-autogen, elixir-format msgid "Suggestions applied." msgstr "Vorschläge übernommen." -#: lib/bds/tui.ex:1189 +#: lib/bds/tui.ex:1198 #, elixir-autogen, elixir-format msgid "Title (ctrl+t to edit)" msgstr "Titel (Strg+T zum Bearbeiten)" -#: lib/bds/tui.ex:1188 +#: lib/bds/tui.ex:1197 #, elixir-autogen, elixir-format msgid "Title (editing — enter to confirm)" msgstr "Titel (Bearbeitung — Enter zum Bestätigen)" -#: lib/bds/tui.ex:1241 +#: lib/bds/tui.ex:1250 #, elixir-autogen, elixir-format msgid "Working…" msgstr "Wird bearbeitet…" -#: lib/bds/tui.ex:1263 +#: lib/bds/tui.ex:1272 #, elixir-autogen, elixir-format msgid "esc to close" msgstr "Esc zum Schließen" @@ -3800,52 +3800,52 @@ msgstr "Serveradresse (user@host oder user@host:port), Public-Key-Authentifizier msgid "Use the form user@host or user@host:port." msgstr "Verwenden Sie das Format user@host oder user@host:port." -#: lib/bds/tui.ex:1208 +#: lib/bds/tui.ex:1217 #, elixir-autogen, elixir-format msgid "Preview (ctrl+e to edit)" msgstr "Vorschau (ctrl+e zum Bearbeiten)" -#: lib/bds/tui.ex:1520 +#: lib/bds/tui.ex:1529 #, elixir-autogen, elixir-format msgid "ctrl+s save · ctrl+p publish · ctrl+e preview · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" msgstr "Strg+S Speichern · Strg+P Veröffentlichen · Strg+E Vorschau · Strg+T Titel · Strg+L Sprache · Strg+G KI · Esc Zurück" -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "All tasks finished." msgstr "Alle Aufgaben abgeschlossen." -#: lib/bds/tui.ex:1289 +#: lib/bds/tui.ex:1298 #, elixir-autogen, elixir-format msgid "Commands — esc to close" msgstr "Befehle — Esc zum Schließen" -#: lib/bds/tui.ex:814 +#: lib/bds/tui.ex:823 #, elixir-autogen, elixir-format msgid "Unknown command." msgstr "Unbekannter Befehl." -#: lib/bds/tui.ex:1279 +#: lib/bds/tui.ex:1288 #, elixir-autogen, elixir-format msgid "[report/apply in GUI]" msgstr "[Bericht/Anwenden in der GUI]" -#: lib/bds/tui.ex:1428 +#: lib/bds/tui.ex:1437 #, elixir-autogen, elixir-format msgid "%{diffs} differences · %{orphans} orphan files" msgstr "%{diffs} Unterschiede · %{orphans} verwaiste Dateien" -#: lib/bds/tui.ex:1460 +#: lib/bds/tui.ex:1469 #, elixir-autogen, elixir-format msgid "Expected %{expected} · Existing %{existing}" msgstr "Erwartet %{expected} · Vorhanden %{existing}" -#: lib/bds/tui.ex:1477 +#: lib/bds/tui.ex:1486 #, elixir-autogen, elixir-format msgid "Extra files (will be removed):" msgstr "Überzählige Dateien (werden entfernt):" -#: lib/bds/tui.ex:1473 +#: lib/bds/tui.ex:1482 #, elixir-autogen, elixir-format msgid "Missing pages (will be rendered):" msgstr "Fehlende Seiten (werden gerendert):" @@ -3860,57 +3860,57 @@ msgstr "Nichts anzuwenden." msgid "Nothing to repair." msgstr "Nichts zu reparieren." -#: lib/bds/tui.ex:1451 +#: lib/bds/tui.ex:1460 #, elixir-autogen, elixir-format msgid "Orphan files (not in database):" msgstr "Verwaiste Dateien (nicht in der Datenbank):" -#: lib/bds/tui.ex:1467 +#: lib/bds/tui.ex:1476 #, elixir-autogen, elixir-format msgid "Sitemap changed." msgstr "Sitemap geändert." -#: lib/bds/tui.ex:1481 +#: lib/bds/tui.ex:1490 #, elixir-autogen, elixir-format msgid "Updated posts (will be re-rendered):" msgstr "Aktualisierte Beiträge (werden neu gerendert):" -#: lib/bds/tui.ex:1418 +#: lib/bds/tui.ex:1427 #, elixir-autogen, elixir-format msgid "enter apply changes · esc close" msgstr "Enter Änderungen anwenden · Esc Schließen" -#: lib/bds/tui.ex:1413 +#: lib/bds/tui.ex:1422 #, elixir-autogen, elixir-format msgid "enter repair all from files · esc close" msgstr "Enter alles aus Dateien reparieren · Esc Schließen" -#: lib/bds/tui.ex:779 +#: lib/bds/tui.ex:788 #, elixir-autogen, elixir-format msgid "Imported Blog" msgstr "Importierter Blog" -#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:807 #, elixir-autogen, elixir-format msgid "Not a folder: %{path}" msgstr "Kein Ordner: %{path}" -#: lib/bds/tui.ex:1355 +#: lib/bds/tui.ex:1364 #, elixir-autogen, elixir-format msgid "Projects — enter switch · o open existing · esc close" msgstr "Projekte — Enter Wechseln · O Bestehenden öffnen · Esc Schließen" -#: lib/bds/tui.ex:706 +#: lib/bds/tui.ex:715 #, elixir-autogen, elixir-format msgid "Switched to %{name}." msgstr "Zu %{name} gewechselt." -#: lib/bds/tui.ex:1383 +#: lib/bds/tui.ex:1392 #, elixir-autogen, elixir-format msgid "Matching folders" msgstr "Passende Ordner" -#: lib/bds/tui.ex:1321 +#: lib/bds/tui.ex:1330 #, elixir-autogen, elixir-format msgid "Open Existing Blog — type a folder path · tab complete · enter open · esc back" msgstr "Bestehenden Blog öffnen — Ordnerpfad eingeben · Tab Vervollständigen · Enter Öffnen · Esc Zurück" @@ -3925,12 +3925,12 @@ msgstr "Commit-Nachricht erforderlich." msgid "Committed." msgstr "Commit erstellt." -#: lib/bds/tui.ex:1165 +#: lib/bds/tui.ex:1174 #, elixir-autogen, elixir-format msgid "Diff (pgup/pgdn scroll)" msgstr "Diff (Bild↑/Bild↓ blättern)" -#: lib/bds/tui.ex:1548 +#: lib/bds/tui.ex:1557 #, elixir-autogen, elixir-format msgid "No changes." msgstr "Keine Änderungen." @@ -3941,7 +3941,7 @@ msgid "No diff for this file." msgstr "Kein Diff für diese Datei." #: lib/bds/tui.ex:388 -#: lib/bds/tui.ex:1152 +#: lib/bds/tui.ex:1161 #, elixir-autogen, elixir-format msgid "Not a git repository." msgstr "Kein Git-Repository." @@ -4031,32 +4031,71 @@ msgstr "SSH-Benutzer" msgid "Show Title" msgstr "Titel anzeigen" -#: lib/bds/ui/settings_form.ex:242 +#: lib/bds/ui/settings_form.ex:247 #, elixir-autogen, elixir-format msgid "Theme" msgstr "Theme" -#: lib/bds/tui.ex:1136 +#: lib/bds/tui.ex:1145 #, elixir-autogen, elixir-format msgid "enter edit · ctrl+s save · esc close" msgstr "Enter Bearbeiten · Strg+S Speichern · Esc Schließen" -#: lib/bds/tui.ex:1499 +#: lib/bds/tui.ex:1508 #, elixir-autogen, elixir-format msgid "enter edit/toggle/cycle · ctrl+s save · esc close · ctrl+q quit" msgstr "Enter Bearbeiten/Umschalten/Wechseln · Strg+S Speichern · Esc Schließen · Strg+Q Beenden" -#: lib/bds/ui/settings_form.ex:230 +#: lib/bds/ui/settings_form.ex:235 #, elixir-autogen, elixir-format msgid "not supported yet" msgstr "noch nicht unterstützt" -#: lib/bds/tui.ex:1506 +#: lib/bds/tui.ex:1515 #, elixir-autogen, elixir-format msgid "c commit · u pull · s push · enter jump to file · pgup/pgdn scroll diff · 1-7 views · ctrl+q quit" msgstr "C Commit · U Pull · S Push · Enter Zur Datei springen · Bild↑/Bild↓ Diff blättern · 1-7 Ansichten · Strg+Q Beenden" -#: lib/bds/tui.ex:1513 +#: lib/bds/tui.ex:1522 #, elixir-autogen, elixir-format msgid "enter open · n new post · 1-7 views · p projects · / search · : commands (:? help) · r refresh · ctrl+q quit" msgstr "Enter Öffnen · N Neuer Beitrag · 1-7 Ansichten · P Projekte · / Suche · : Befehle (:? Hilfe) · R Aktualisieren · Strg+Q Beenden" + +#: lib/bds/ui/settings_form.ex:369 +#, elixir-autogen, elixir-format +msgid "CLI tool installed to %{path}" +msgstr "CLI-Tool wurde nach %{path} installiert" + +#: lib/bds/desktop/shell_live/settings_editor.ex:75 +#: lib/bds/desktop/shell_live/settings_editor.ex:78 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:386 +#: lib/bds/ui/settings_form.ex:223 +#, elixir-autogen, elixir-format +msgid "Command Line Tool" +msgstr "Kommandozeilenwerkzeug" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:390 +#: lib/bds/ui/settings_form.ex:224 +#, elixir-autogen, elixir-format +msgid "Install CLI Tool" +msgstr "CLI-Tool installieren" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:387 +#, elixir-autogen, elixir-format +msgid "Install the bds-cli tool to ~/.local/bin; it uses the same settings and cache database as the app" +msgstr "Installiert das bds-cli-Tool nach ~/.local/bin; es verwendet dieselben Einstellungen und dieselbe Cache-Datenbank wie die App" + +#: lib/bds/ui/settings_form.ex:375 +#, elixir-autogen, elixir-format +msgid "Installing the CLI tool failed: %{reason}" +msgstr "Installation des CLI-Tools fehlgeschlagen: %{reason}" + +#: lib/bds/ui/settings_form.ex:372 +#, elixir-autogen, elixir-format +msgid "The CLI tool can only be installed from the packaged application" +msgstr "Das CLI-Tool kann nur aus der paketierten Anwendung installiert werden" + +#: lib/bds/ui/settings_form.ex:379 +#, elixir-autogen, elixir-format +msgid "Unknown settings action" +msgstr "Unbekannte Einstellungsaktion" diff --git a/priv/gettext/en/LC_MESSAGES/ui.po b/priv/gettext/en/LC_MESSAGES/ui.po index f831bfd..e57be5e 100644 --- a/priv/gettext/en/LC_MESSAGES/ui.po +++ b/priv/gettext/en/LC_MESSAGES/ui.po @@ -80,13 +80,13 @@ msgstr "" #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:42 #: lib/bds/desktop/shell_live/overlay_manager.ex:72 -#: lib/bds/desktop/shell_live/post_editor.ex:920 +#: lib/bds/desktop/shell_live/post_editor.ex:906 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 -#: lib/bds/tui.ex:937 -#: lib/bds/tui.ex:940 -#: lib/bds/tui.ex:943 -#: lib/bds/tui.ex:951 -#: lib/bds/tui.ex:1753 +#: lib/bds/tui.ex:946 +#: lib/bds/tui.ex:949 +#: lib/bds/tui.ex:952 +#: lib/bds/tui.ex:960 +#: lib/bds/tui.ex:1762 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "" @@ -349,8 +349,8 @@ msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:557 #: lib/bds/desktop/shell_live/overlay_manager.ex:73 -#: lib/bds/desktop/shell_live/post_editor.ex:765 -#: lib/bds/desktop/shell_live/post_editor.ex:814 +#: lib/bds/desktop/shell_live/post_editor.ex:752 +#: lib/bds/desktop/shell_live/post_editor.ex:801 #, elixir-autogen, elixir-format msgid "Automatic AI actions stay gated by airplane mode." msgstr "" @@ -574,8 +574,8 @@ msgstr "" msgid "Collapse unchanged diff hunks" msgstr "" -#: lib/bds/desktop/shell_live/overlay_manager.ex:422 -#: lib/bds/tui.ex:1849 +#: lib/bds/desktop/shell_live/overlay_manager.ex:423 +#: lib/bds/tui.ex:1858 #, elixir-autogen, elixir-format msgid "Command completed" msgstr "" @@ -593,7 +593,7 @@ msgstr "" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 -#: lib/bds/tui.ex:1219 +#: lib/bds/tui.ex:1228 #: lib/bds/ui/settings_form.ex:137 #: lib/bds/ui/sidebar.ex:801 #, elixir-autogen, elixir-format @@ -751,7 +751,7 @@ msgstr "" msgid "Delete" msgstr "" -#: lib/bds/desktop/shell_live/overlay_manager.ex:278 +#: lib/bds/desktop/shell_live/overlay_manager.ex:279 #, elixir-autogen, elixir-format msgid "Delete Media" msgstr "" @@ -798,9 +798,9 @@ msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:214 #: lib/bds/desktop/shell_live/media_editor.ex:220 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:59 -#: lib/bds/desktop/shell_live/post_editor.ex:764 -#: lib/bds/desktop/shell_live/post_editor.ex:793 -#: lib/bds/desktop/shell_live/post_editor.ex:799 +#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:780 +#: lib/bds/desktop/shell_live/post_editor.ex:786 #, elixir-autogen, elixir-format msgid "Detect Language" msgstr "" @@ -1040,7 +1040,7 @@ msgid "Filename" msgstr "" #: lib/bds/desktop/menu_bar.ex:254 -#: lib/bds/tui.ex:1813 +#: lib/bds/tui.ex:1822 #, elixir-autogen, elixir-format msgid "Fill Missing Translations" msgstr "" @@ -1051,7 +1051,7 @@ msgid "Find" msgstr "" #: lib/bds/desktop/menu_bar.ex:255 -#: lib/bds/tui.ex:1816 +#: lib/bds/tui.ex:1825 #, elixir-autogen, elixir-format msgid "Find Duplicate Posts" msgstr "" @@ -1078,14 +1078,14 @@ msgid "Gallery" msgstr "" #: lib/bds/desktop/menu_bar.ex:256 -#: lib/bds/tui.ex:1797 +#: lib/bds/tui.ex:1806 #, elixir-autogen, elixir-format msgid "Generate Site" msgstr "" #: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live/socket_state.ex:109 -#: lib/bds/tui.ex:1662 +#: lib/bds/tui.ex:1671 #: lib/bds/ui/sidebar.ex:826 #, elixir-autogen, elixir-format msgid "Git" @@ -1128,7 +1128,7 @@ msgstr "" #: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 -#: lib/bds/desktop/shell_live/post_editor.ex:1038 +#: lib/bds/desktop/shell_live/post_editor.ex:1024 #, elixir-autogen, elixir-format msgid "Idle" msgstr "" @@ -1299,7 +1299,7 @@ msgid "Language" msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:221 -#: lib/bds/desktop/shell_live/post_editor.ex:800 +#: lib/bds/desktop/shell_live/post_editor.ex:787 #, elixir-autogen, elixir-format msgid "Language detection failed." msgstr "" @@ -1345,7 +1345,7 @@ msgstr "" #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:50 #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:57 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:351 -#: lib/bds/ui/settings_form.ex:234 +#: lib/bds/ui/settings_form.ex:239 #: lib/bds/ui/sidebar.ex:816 #, elixir-autogen, elixir-format msgid "MCP" @@ -1386,7 +1386,7 @@ msgstr "" msgid "Mapped" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:1041 +#: lib/bds/desktop/shell_live/post_editor.ex:1027 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:123 #, elixir-autogen, elixir-format msgid "Markdown" @@ -1404,9 +1404,9 @@ msgstr "" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 -#: lib/bds/tui.ex:1657 -#: lib/bds/tui.ex:1878 -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1666 +#: lib/bds/tui.ex:1887 +#: lib/bds/tui.ex:1890 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:578 @@ -1452,8 +1452,8 @@ msgstr "" #: lib/bds/tui.ex:278 #: lib/bds/tui.ex:284 #: lib/bds/tui.ex:295 -#: lib/bds/tui.ex:1412 -#: lib/bds/tui.ex:1794 +#: lib/bds/tui.ex:1421 +#: lib/bds/tui.ex:1803 #: lib/bds/ui/registry.ex:111 #, elixir-autogen, elixir-format msgid "Metadata Diff" @@ -1787,8 +1787,8 @@ msgid "Open Data Folder" msgstr "" #: lib/bds/desktop/shell_live/index.html.heex:644 -#: lib/bds/tui.ex:792 -#: lib/bds/tui.ex:797 +#: lib/bds/tui.ex:801 +#: lib/bds/tui.ex:806 #, elixir-autogen, elixir-format msgid "Open Existing Blog" msgstr "" @@ -1800,7 +1800,7 @@ msgid "Open Settings" msgstr "" #: lib/bds/desktop/menu_bar.ex:217 -#: lib/bds/tui.ex:1818 +#: lib/bds/tui.ex:1827 #, elixir-autogen, elixir-format msgid "Open in Browser" msgstr "" @@ -1896,16 +1896,16 @@ msgid "Persist the detected language for this media item" msgstr "" #: lib/bds/desktop/shell_live/misc_editor.ex:733 -#: lib/bds/desktop/shell_live/post_editor.ex:539 -#: lib/bds/desktop/shell_live/post_editor.ex:543 -#: lib/bds/desktop/shell_live/post_editor.ex:582 -#: lib/bds/desktop/shell_live/post_editor.ex:586 -#: lib/bds/desktop/shell_live/post_editor.ex:624 -#: lib/bds/desktop/shell_live/post_editor.ex:639 -#: lib/bds/desktop/shell_live/post_editor.ex:668 -#: lib/bds/desktop/shell_live/post_editor.ex:671 -#: lib/bds/desktop/shell_live/post_editor.ex:751 -#: lib/bds/desktop/shell_live/post_editor.ex:754 +#: lib/bds/desktop/shell_live/post_editor.ex:526 +#: lib/bds/desktop/shell_live/post_editor.ex:530 +#: lib/bds/desktop/shell_live/post_editor.ex:569 +#: lib/bds/desktop/shell_live/post_editor.ex:573 +#: lib/bds/desktop/shell_live/post_editor.ex:611 +#: lib/bds/desktop/shell_live/post_editor.ex:626 +#: lib/bds/desktop/shell_live/post_editor.ex:655 +#: lib/bds/desktop/shell_live/post_editor.ex:658 +#: lib/bds/desktop/shell_live/post_editor.ex:738 +#: lib/bds/desktop/shell_live/post_editor.ex:741 #: lib/bds/desktop/shell_live/sidebar_components.ex:651 #: lib/bds/desktop/shell_live/sidebar_delete.ex:174 #: lib/bds/ui/registry.ex:99 @@ -1936,12 +1936,12 @@ msgstr "" msgid "Post is marked as do-not-translate but has translations" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:582 +#: lib/bds/desktop/shell_live/post_editor.ex:569 #, elixir-autogen, elixir-format msgid "Post published" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:539 +#: lib/bds/desktop/shell_live/post_editor.ex:526 #, elixir-autogen, elixir-format msgid "Post saved" msgstr "" @@ -1949,8 +1949,8 @@ msgstr "" #: lib/bds/desktop/menu_bar.ex:233 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 -#: lib/bds/tui.ex:1656 -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1665 +#: lib/bds/tui.ex:1680 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1967,7 +1967,7 @@ msgstr "" msgid "Preferences" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:1042 +#: lib/bds/desktop/shell_live/post_editor.ex:1028 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:124 #, elixir-autogen, elixir-format msgid "Preview" @@ -2010,8 +2010,8 @@ msgstr "" #: lib/bds/desktop/shell_live/chat_surface.ex:15 #: lib/bds/desktop/shell_live/index.html.heex:623 -#: lib/bds/tui.ex:705 -#: lib/bds/tui.ex:710 +#: lib/bds/tui.ex:714 +#: lib/bds/tui.ex:719 #, elixir-autogen, elixir-format msgid "Projects" msgstr "" @@ -2040,7 +2040,7 @@ msgid "Publish Selected" msgstr "" #: lib/bds/desktop/shell_data.ex:165 -#: lib/bds/desktop/shell_live/post_editor.ex:1036 +#: lib/bds/desktop/shell_live/post_editor.ex:1022 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 #, elixir-autogen, elixir-format @@ -2079,15 +2079,15 @@ msgid "Ready to import:" msgstr "" #: lib/bds/desktop/menu_bar.ex:248 -#: lib/bds/tui.ex:789 -#: lib/bds/tui.ex:1798 +#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:1807 #, elixir-autogen, elixir-format msgid "Rebuild Database" msgstr "" #: lib/bds/desktop/menu_bar.ex:250 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 -#: lib/bds/tui.ex:1802 +#: lib/bds/tui.ex:1811 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "" @@ -2145,7 +2145,7 @@ msgid "Refresh Translation" msgstr "" #: lib/bds/desktop/menu_bar.ex:252 -#: lib/bds/tui.ex:1805 +#: lib/bds/tui.ex:1814 #, elixir-autogen, elixir-format msgid "Regenerate Calendar" msgstr "" @@ -2156,7 +2156,7 @@ msgid "Regenerate Missing Thumbnails" msgstr "" #: lib/bds/desktop/menu_bar.ex:249 -#: lib/bds/tui.ex:1799 +#: lib/bds/tui.ex:1808 #, elixir-autogen, elixir-format msgid "Reindex Text" msgstr "" @@ -2240,7 +2240,7 @@ msgstr "" msgid "Result" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:1037 +#: lib/bds/desktop/shell_live/post_editor.ex:1023 #, elixir-autogen, elixir-format msgid "Reverted" msgstr "" @@ -2293,7 +2293,7 @@ msgid "Save Translation" msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:730 -#: lib/bds/desktop/shell_live/post_editor.ex:1035 +#: lib/bds/desktop/shell_live/post_editor.ex:1021 #, elixir-autogen, elixir-format msgid "Saved" msgstr "" @@ -2344,7 +2344,7 @@ msgstr "" #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 -#: lib/bds/tui.ex:1659 +#: lib/bds/tui.ex:1668 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2451,7 +2451,7 @@ msgstr "" #: lib/bds/desktop/shell_live/settings_editor/project_settings.ex:60 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:11 -#: lib/bds/tui.ex:1661 +#: lib/bds/tui.ex:1670 #: lib/bds/ui/registry.ex:86 #: lib/bds/ui/registry.ex:101 #: lib/bds/ui/sidebar.ex:795 @@ -2477,7 +2477,7 @@ msgstr "" #: lib/bds/desktop/shell_live/misc_editor.ex:412 #: lib/bds/tui.ex:311 #: lib/bds/tui.ex:313 -#: lib/bds/tui.ex:1417 +#: lib/bds/tui.ex:1426 #: lib/bds/ui/registry.ex:125 #, elixir-autogen, elixir-format msgid "Site Validation" @@ -2541,7 +2541,7 @@ msgstr "" #: lib/bds/desktop/shell_live/settings_editor/style_editor.ex:76 #: lib/bds/desktop/shell_live/settings_editor_html/style_editor.html.heex:3 #: lib/bds/ui/registry.ex:102 -#: lib/bds/ui/settings_form.ex:241 +#: lib/bds/ui/settings_form.ex:246 #: lib/bds/ui/sidebar.ex:817 #, elixir-autogen, elixir-format msgid "Style" @@ -2600,7 +2600,7 @@ msgstr "" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 -#: lib/bds/tui.ex:1660 +#: lib/bds/tui.ex:1669 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2612,7 +2612,7 @@ msgstr "" #: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "Tasks" msgstr "" @@ -2658,7 +2658,7 @@ msgstr "" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 -#: lib/bds/tui.ex:1658 +#: lib/bds/tui.ex:1667 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2772,7 +2772,7 @@ msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:363 #: lib/bds/desktop/shell_live/media_editor.ex:556 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:76 -#: lib/bds/desktop/shell_live/post_editor.ex:813 +#: lib/bds/desktop/shell_live/post_editor.ex:800 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:60 #, elixir-autogen, elixir-format msgid "Translate" @@ -2849,7 +2849,7 @@ msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:729 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:10 -#: lib/bds/desktop/shell_live/post_editor.ex:1034 +#: lib/bds/desktop/shell_live/post_editor.ex:1020 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:7 #, elixir-autogen, elixir-format msgid "Unsaved" @@ -2882,7 +2882,7 @@ msgid "Updated URLs" msgstr "" #: lib/bds/desktop/menu_bar.ex:259 -#: lib/bds/tui.ex:1817 +#: lib/bds/tui.ex:1826 #, elixir-autogen, elixir-format msgid "Upload Site" msgstr "" @@ -2910,13 +2910,13 @@ msgid "Validate" msgstr "" #: lib/bds/desktop/menu_bar.ex:258 -#: lib/bds/tui.ex:1795 +#: lib/bds/tui.ex:1804 #, elixir-autogen, elixir-format msgid "Validate Site" msgstr "" #: lib/bds/desktop/menu_bar.ex:253 -#: lib/bds/tui.ex:1808 +#: lib/bds/tui.ex:1817 #, elixir-autogen, elixir-format msgid "Validate Translations" msgstr "" @@ -3350,12 +3350,12 @@ msgstr "" msgid "Move this post to the archive" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:668 +#: lib/bds/desktop/shell_live/post_editor.ex:655 #, elixir-autogen, elixir-format, fuzzy msgid "Post archived" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:738 #, elixir-autogen, elixir-format, fuzzy msgid "Post unarchived" msgstr "" @@ -3397,7 +3397,7 @@ msgid "Commit" msgstr "" #: lib/bds/desktop/shell_live/sidebar_components.ex:555 -#: lib/bds/tui.ex:1239 +#: lib/bds/tui.ex:1248 #, elixir-autogen, elixir-format msgid "Commit message" msgstr "" @@ -3421,7 +3421,7 @@ msgid "Fetch" msgstr "" #: lib/bds/desktop/shell_live/sidebar_components.ex:586 -#: lib/bds/tui.ex:1074 +#: lib/bds/tui.ex:1083 #, elixir-autogen, elixir-format msgid "History" msgstr "" @@ -3469,7 +3469,7 @@ msgstr "" #: lib/bds/desktop/shell_live/git_handler.ex:17 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 -#: lib/bds/tui.ex:957 +#: lib/bds/tui.ex:966 #, elixir-autogen, elixir-format msgid "Pull" msgstr "" @@ -3477,7 +3477,7 @@ msgstr "" #: lib/bds/desktop/shell_live/git_handler.ex:18 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 -#: lib/bds/tui.ex:958 +#: lib/bds/tui.ex:967 #, elixir-autogen, elixir-format, fuzzy msgid "Push" msgstr "" @@ -3543,18 +3543,18 @@ msgstr "Blogmark" msgid "Open a project before importing a blogmark." msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:694 +#: lib/bds/desktop/shell_live/post_editor.ex:681 #, elixir-autogen, elixir-format msgid "Added %{name}" msgstr "Added %{name}" -#: lib/bds/desktop/shell_live/post_editor.ex:701 +#: lib/bds/desktop/shell_live/post_editor.ex:688 #, elixir-autogen, elixir-format msgid "Failed to import %{path}: %{reason}" msgstr "Failed to import %{path}: %{reason}" -#: lib/bds/desktop/shell_live/post_editor.ex:693 -#: lib/bds/desktop/shell_live/post_editor.ex:700 +#: lib/bds/desktop/shell_live/post_editor.ex:680 +#: lib/bds/desktop/shell_live/post_editor.ex:687 #, elixir-autogen, elixir-format msgid "Insert Image" msgstr "Insert Image" @@ -3569,7 +3569,7 @@ msgstr "" msgid "Copy Bookmarklet to Clipboard" msgstr "" -#: lib/bds/desktop/shell_live/overlay_manager.ex:300 +#: lib/bds/desktop/shell_live/overlay_manager.ex:301 #, elixir-autogen, elixir-format, fuzzy msgid "Delete Tag" msgstr "" @@ -3590,7 +3590,7 @@ msgid "Suggested tags" msgstr "" #: lib/bds/desktop/menu_bar.ex:257 -#: lib/bds/tui.ex:1796 +#: lib/bds/tui.ex:1805 #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "" @@ -3687,12 +3687,12 @@ msgstr "" msgid "The endpoint returned no models. You can still type a model name manually." msgstr "" -#: lib/bds/tui.ex:1754 +#: lib/bds/tui.ex:1763 #, elixir-autogen, elixir-format msgid "AI is unavailable in airplane mode without a local endpoint." msgstr "" -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1890 #, elixir-autogen, elixir-format, fuzzy msgid "Could not load this file." msgstr "" @@ -3702,68 +3702,68 @@ msgstr "" msgid "Editing this item is not available in the terminal UI yet." msgstr "" -#: lib/bds/tui.ex:940 +#: lib/bds/tui.ex:949 #, elixir-autogen, elixir-format msgid "No suggestions returned." msgstr "" -#: lib/bds/tui.ex:1878 +#: lib/bds/tui.ex:1887 #, elixir-autogen, elixir-format msgid "Only images can be previewed." msgstr "" -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1680 #, elixir-autogen, elixir-format msgid "Post not found." msgstr "" -#: lib/bds/tui.ex:1174 +#: lib/bds/tui.ex:1183 #, elixir-autogen, elixir-format msgid "Press enter to preview %{name}." msgstr "" -#: lib/bds/tui.ex:1724 +#: lib/bds/tui.ex:1733 #, elixir-autogen, elixir-format, fuzzy msgid "Published." msgstr "" -#: lib/bds/tui.ex:1740 +#: lib/bds/tui.ex:1749 #, elixir-autogen, elixir-format msgid "Save before switching languages." msgstr "" -#: lib/bds/tui.ex:524 -#: lib/bds/tui.ex:1725 +#: lib/bds/tui.ex:533 +#: lib/bds/tui.ex:1734 #, elixir-autogen, elixir-format, fuzzy msgid "Saved." msgstr "" -#: lib/bds/tui.ex:1177 +#: lib/bds/tui.ex:1186 #, elixir-autogen, elixir-format msgid "Select an entry and press enter to open it." msgstr "" -#: lib/bds/tui.ex:937 +#: lib/bds/tui.ex:946 #, elixir-autogen, elixir-format msgid "Suggestions applied." msgstr "" -#: lib/bds/tui.ex:1189 +#: lib/bds/tui.ex:1198 #, elixir-autogen, elixir-format msgid "Title (ctrl+t to edit)" msgstr "" -#: lib/bds/tui.ex:1188 +#: lib/bds/tui.ex:1197 #, elixir-autogen, elixir-format msgid "Title (editing — enter to confirm)" msgstr "" -#: lib/bds/tui.ex:1241 +#: lib/bds/tui.ex:1250 #, elixir-autogen, elixir-format, fuzzy msgid "Working…" msgstr "" -#: lib/bds/tui.ex:1263 +#: lib/bds/tui.ex:1272 #, elixir-autogen, elixir-format msgid "esc to close" msgstr "" @@ -3800,52 +3800,52 @@ msgstr "" msgid "Use the form user@host or user@host:port." msgstr "" -#: lib/bds/tui.ex:1208 +#: lib/bds/tui.ex:1217 #, elixir-autogen, elixir-format msgid "Preview (ctrl+e to edit)" msgstr "" -#: lib/bds/tui.ex:1520 +#: lib/bds/tui.ex:1529 #, elixir-autogen, elixir-format, fuzzy msgid "ctrl+s save · ctrl+p publish · ctrl+e preview · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" msgstr "" -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "All tasks finished." msgstr "" -#: lib/bds/tui.ex:1289 +#: lib/bds/tui.ex:1298 #, elixir-autogen, elixir-format msgid "Commands — esc to close" msgstr "" -#: lib/bds/tui.ex:814 +#: lib/bds/tui.ex:823 #, elixir-autogen, elixir-format, fuzzy msgid "Unknown command." msgstr "" -#: lib/bds/tui.ex:1279 +#: lib/bds/tui.ex:1288 #, elixir-autogen, elixir-format msgid "[report/apply in GUI]" msgstr "" -#: lib/bds/tui.ex:1428 +#: lib/bds/tui.ex:1437 #, elixir-autogen, elixir-format msgid "%{diffs} differences · %{orphans} orphan files" msgstr "" -#: lib/bds/tui.ex:1460 +#: lib/bds/tui.ex:1469 #, elixir-autogen, elixir-format msgid "Expected %{expected} · Existing %{existing}" msgstr "" -#: lib/bds/tui.ex:1477 +#: lib/bds/tui.ex:1486 #, elixir-autogen, elixir-format msgid "Extra files (will be removed):" msgstr "" -#: lib/bds/tui.ex:1473 +#: lib/bds/tui.ex:1482 #, elixir-autogen, elixir-format msgid "Missing pages (will be rendered):" msgstr "" @@ -3860,57 +3860,57 @@ msgstr "" msgid "Nothing to repair." msgstr "" -#: lib/bds/tui.ex:1451 +#: lib/bds/tui.ex:1460 #, elixir-autogen, elixir-format msgid "Orphan files (not in database):" msgstr "" -#: lib/bds/tui.ex:1467 +#: lib/bds/tui.ex:1476 #, elixir-autogen, elixir-format msgid "Sitemap changed." msgstr "" -#: lib/bds/tui.ex:1481 +#: lib/bds/tui.ex:1490 #, elixir-autogen, elixir-format msgid "Updated posts (will be re-rendered):" msgstr "" -#: lib/bds/tui.ex:1418 +#: lib/bds/tui.ex:1427 #, elixir-autogen, elixir-format msgid "enter apply changes · esc close" msgstr "" -#: lib/bds/tui.ex:1413 +#: lib/bds/tui.ex:1422 #, elixir-autogen, elixir-format msgid "enter repair all from files · esc close" msgstr "" -#: lib/bds/tui.ex:779 +#: lib/bds/tui.ex:788 #, elixir-autogen, elixir-format, fuzzy msgid "Imported Blog" msgstr "" -#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:807 #, elixir-autogen, elixir-format msgid "Not a folder: %{path}" msgstr "" -#: lib/bds/tui.ex:1355 +#: lib/bds/tui.ex:1364 #, elixir-autogen, elixir-format msgid "Projects — enter switch · o open existing · esc close" msgstr "" -#: lib/bds/tui.ex:706 +#: lib/bds/tui.ex:715 #, elixir-autogen, elixir-format msgid "Switched to %{name}." msgstr "" -#: lib/bds/tui.ex:1383 +#: lib/bds/tui.ex:1392 #, elixir-autogen, elixir-format msgid "Matching folders" msgstr "" -#: lib/bds/tui.ex:1321 +#: lib/bds/tui.ex:1330 #, elixir-autogen, elixir-format, fuzzy msgid "Open Existing Blog — type a folder path · tab complete · enter open · esc back" msgstr "" @@ -3925,12 +3925,12 @@ msgstr "" msgid "Committed." msgstr "" -#: lib/bds/tui.ex:1165 +#: lib/bds/tui.ex:1174 #, elixir-autogen, elixir-format msgid "Diff (pgup/pgdn scroll)" msgstr "" -#: lib/bds/tui.ex:1548 +#: lib/bds/tui.ex:1557 #, elixir-autogen, elixir-format, fuzzy msgid "No changes." msgstr "" @@ -3941,7 +3941,7 @@ msgid "No diff for this file." msgstr "" #: lib/bds/tui.ex:388 -#: lib/bds/tui.ex:1152 +#: lib/bds/tui.ex:1161 #, elixir-autogen, elixir-format msgid "Not a git repository." msgstr "" @@ -4031,32 +4031,71 @@ msgstr "" msgid "Show Title" msgstr "" -#: lib/bds/ui/settings_form.ex:242 +#: lib/bds/ui/settings_form.ex:247 #, elixir-autogen, elixir-format, fuzzy msgid "Theme" msgstr "" -#: lib/bds/tui.ex:1136 +#: lib/bds/tui.ex:1145 #, elixir-autogen, elixir-format msgid "enter edit · ctrl+s save · esc close" msgstr "" -#: lib/bds/tui.ex:1499 +#: lib/bds/tui.ex:1508 #, elixir-autogen, elixir-format msgid "enter edit/toggle/cycle · ctrl+s save · esc close · ctrl+q quit" msgstr "" -#: lib/bds/ui/settings_form.ex:230 +#: lib/bds/ui/settings_form.ex:235 #, elixir-autogen, elixir-format msgid "not supported yet" msgstr "" -#: lib/bds/tui.ex:1506 +#: lib/bds/tui.ex:1515 #, elixir-autogen, elixir-format, fuzzy msgid "c commit · u pull · s push · enter jump to file · pgup/pgdn scroll diff · 1-7 views · ctrl+q quit" msgstr "" -#: lib/bds/tui.ex:1513 +#: lib/bds/tui.ex:1522 #, elixir-autogen, elixir-format, fuzzy msgid "enter open · n new post · 1-7 views · p projects · / search · : commands (:? help) · r refresh · ctrl+q quit" msgstr "" + +#: lib/bds/ui/settings_form.ex:369 +#, elixir-autogen, elixir-format +msgid "CLI tool installed to %{path}" +msgstr "" + +#: lib/bds/desktop/shell_live/settings_editor.ex:75 +#: lib/bds/desktop/shell_live/settings_editor.ex:78 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:386 +#: lib/bds/ui/settings_form.ex:223 +#, elixir-autogen, elixir-format +msgid "Command Line Tool" +msgstr "" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:390 +#: lib/bds/ui/settings_form.ex:224 +#, elixir-autogen, elixir-format +msgid "Install CLI Tool" +msgstr "" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:387 +#, elixir-autogen, elixir-format +msgid "Install the bds-cli tool to ~/.local/bin; it uses the same settings and cache database as the app" +msgstr "" + +#: lib/bds/ui/settings_form.ex:375 +#, elixir-autogen, elixir-format +msgid "Installing the CLI tool failed: %{reason}" +msgstr "" + +#: lib/bds/ui/settings_form.ex:372 +#, elixir-autogen, elixir-format +msgid "The CLI tool can only be installed from the packaged application" +msgstr "" + +#: lib/bds/ui/settings_form.ex:379 +#, elixir-autogen, elixir-format +msgid "Unknown settings action" +msgstr "" diff --git a/priv/gettext/es/LC_MESSAGES/ui.po b/priv/gettext/es/LC_MESSAGES/ui.po index 53e6bff..73462e7 100644 --- a/priv/gettext/es/LC_MESSAGES/ui.po +++ b/priv/gettext/es/LC_MESSAGES/ui.po @@ -80,13 +80,13 @@ msgstr "Configuración de IA" #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:42 #: lib/bds/desktop/shell_live/overlay_manager.ex:72 -#: lib/bds/desktop/shell_live/post_editor.ex:920 +#: lib/bds/desktop/shell_live/post_editor.ex:906 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 -#: lib/bds/tui.ex:937 -#: lib/bds/tui.ex:940 -#: lib/bds/tui.ex:943 -#: lib/bds/tui.ex:951 -#: lib/bds/tui.ex:1753 +#: lib/bds/tui.ex:946 +#: lib/bds/tui.ex:949 +#: lib/bds/tui.ex:952 +#: lib/bds/tui.ex:960 +#: lib/bds/tui.ex:1762 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "Sugerencias de IA" @@ -349,8 +349,8 @@ msgstr "Automático" #: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:557 #: lib/bds/desktop/shell_live/overlay_manager.ex:73 -#: lib/bds/desktop/shell_live/post_editor.ex:765 -#: lib/bds/desktop/shell_live/post_editor.ex:814 +#: lib/bds/desktop/shell_live/post_editor.ex:752 +#: lib/bds/desktop/shell_live/post_editor.ex:801 #, elixir-autogen, elixir-format msgid "Automatic AI actions stay gated by airplane mode." msgstr "Las acciones automáticas de IA siguen bloqueadas por el modo avión." @@ -574,8 +574,8 @@ msgstr "Cerrar pestaña" msgid "Collapse unchanged diff hunks" msgstr "Contraer bloques de diff sin cambios" -#: lib/bds/desktop/shell_live/overlay_manager.ex:422 -#: lib/bds/tui.ex:1849 +#: lib/bds/desktop/shell_live/overlay_manager.ex:423 +#: lib/bds/tui.ex:1858 #, elixir-autogen, elixir-format msgid "Command completed" msgstr "Comando completado" @@ -593,7 +593,7 @@ msgstr "Confirmar" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 -#: lib/bds/tui.ex:1219 +#: lib/bds/tui.ex:1228 #: lib/bds/ui/settings_form.ex:137 #: lib/bds/ui/sidebar.ex:801 #, elixir-autogen, elixir-format @@ -751,7 +751,7 @@ msgstr "Modo de edición predeterminado y presentación de diff" msgid "Delete" msgstr "Eliminar" -#: lib/bds/desktop/shell_live/overlay_manager.ex:278 +#: lib/bds/desktop/shell_live/overlay_manager.ex:279 #, elixir-autogen, elixir-format msgid "Delete Media" msgstr "Eliminar medio" @@ -798,9 +798,9 @@ msgstr "Detectar" #: lib/bds/desktop/shell_live/media_editor.ex:214 #: lib/bds/desktop/shell_live/media_editor.ex:220 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:59 -#: lib/bds/desktop/shell_live/post_editor.ex:764 -#: lib/bds/desktop/shell_live/post_editor.ex:793 -#: lib/bds/desktop/shell_live/post_editor.ex:799 +#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:780 +#: lib/bds/desktop/shell_live/post_editor.ex:786 #, elixir-autogen, elixir-format msgid "Detect Language" msgstr "Detectar idioma" @@ -1040,7 +1040,7 @@ msgid "Filename" msgstr "Nombre de archivo" #: lib/bds/desktop/menu_bar.ex:254 -#: lib/bds/tui.ex:1813 +#: lib/bds/tui.ex:1822 #, elixir-autogen, elixir-format msgid "Fill Missing Translations" msgstr "Completar traducciones faltantes" @@ -1051,7 +1051,7 @@ msgid "Find" msgstr "Buscar" #: lib/bds/desktop/menu_bar.ex:255 -#: lib/bds/tui.ex:1816 +#: lib/bds/tui.ex:1825 #, elixir-autogen, elixir-format msgid "Find Duplicate Posts" msgstr "Buscar entradas duplicadas" @@ -1078,14 +1078,14 @@ msgid "Gallery" msgstr "Galeria" #: lib/bds/desktop/menu_bar.ex:256 -#: lib/bds/tui.ex:1797 +#: lib/bds/tui.ex:1806 #, elixir-autogen, elixir-format msgid "Generate Site" msgstr "Generar sitio" #: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live/socket_state.ex:109 -#: lib/bds/tui.ex:1662 +#: lib/bds/tui.ex:1671 #: lib/bds/ui/sidebar.ex:826 #, elixir-autogen, elixir-format msgid "Git" @@ -1128,7 +1128,7 @@ msgstr "Host" #: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 -#: lib/bds/desktop/shell_live/post_editor.ex:1038 +#: lib/bds/desktop/shell_live/post_editor.ex:1024 #, elixir-autogen, elixir-format msgid "Idle" msgstr "Inactivo" @@ -1299,7 +1299,7 @@ msgid "Language" msgstr "Idioma" #: lib/bds/desktop/shell_live/media_editor.ex:221 -#: lib/bds/desktop/shell_live/post_editor.ex:800 +#: lib/bds/desktop/shell_live/post_editor.ex:787 #, elixir-autogen, elixir-format msgid "Language detection failed." msgstr "La detección de idioma falló." @@ -1345,7 +1345,7 @@ msgstr "Cargar más" #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:50 #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:57 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:351 -#: lib/bds/ui/settings_form.ex:234 +#: lib/bds/ui/settings_form.ex:239 #: lib/bds/ui/sidebar.ex:816 #, elixir-autogen, elixir-format msgid "MCP" @@ -1386,7 +1386,7 @@ msgstr "Mapear a..." msgid "Mapped" msgstr "Mapeado" -#: lib/bds/desktop/shell_live/post_editor.ex:1041 +#: lib/bds/desktop/shell_live/post_editor.ex:1027 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:123 #, elixir-autogen, elixir-format msgid "Markdown" @@ -1404,9 +1404,9 @@ msgstr "Máximo de publicaciones por página" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 -#: lib/bds/tui.ex:1657 -#: lib/bds/tui.ex:1878 -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1666 +#: lib/bds/tui.ex:1887 +#: lib/bds/tui.ex:1890 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:578 @@ -1452,8 +1452,8 @@ msgstr "Metadatos" #: lib/bds/tui.ex:278 #: lib/bds/tui.ex:284 #: lib/bds/tui.ex:295 -#: lib/bds/tui.ex:1412 -#: lib/bds/tui.ex:1794 +#: lib/bds/tui.ex:1421 +#: lib/bds/tui.ex:1803 #: lib/bds/ui/registry.ex:111 #, elixir-autogen, elixir-format msgid "Metadata Diff" @@ -1787,8 +1787,8 @@ msgid "Open Data Folder" msgstr "Abrir carpeta de datos" #: lib/bds/desktop/shell_live/index.html.heex:644 -#: lib/bds/tui.ex:792 -#: lib/bds/tui.ex:797 +#: lib/bds/tui.ex:801 +#: lib/bds/tui.ex:806 #, elixir-autogen, elixir-format msgid "Open Existing Blog" msgstr "Abrir blog existente" @@ -1800,7 +1800,7 @@ msgid "Open Settings" msgstr "Abrir Ajustes" #: lib/bds/desktop/menu_bar.ex:217 -#: lib/bds/tui.ex:1818 +#: lib/bds/tui.ex:1827 #, elixir-autogen, elixir-format msgid "Open in Browser" msgstr "Abrir en el navegador" @@ -1896,16 +1896,16 @@ msgid "Persist the detected language for this media item" msgstr "Guardar el idioma detectado para este medio" #: lib/bds/desktop/shell_live/misc_editor.ex:733 -#: lib/bds/desktop/shell_live/post_editor.ex:539 -#: lib/bds/desktop/shell_live/post_editor.ex:543 -#: lib/bds/desktop/shell_live/post_editor.ex:582 -#: lib/bds/desktop/shell_live/post_editor.ex:586 -#: lib/bds/desktop/shell_live/post_editor.ex:624 -#: lib/bds/desktop/shell_live/post_editor.ex:639 -#: lib/bds/desktop/shell_live/post_editor.ex:668 -#: lib/bds/desktop/shell_live/post_editor.ex:671 -#: lib/bds/desktop/shell_live/post_editor.ex:751 -#: lib/bds/desktop/shell_live/post_editor.ex:754 +#: lib/bds/desktop/shell_live/post_editor.ex:526 +#: lib/bds/desktop/shell_live/post_editor.ex:530 +#: lib/bds/desktop/shell_live/post_editor.ex:569 +#: lib/bds/desktop/shell_live/post_editor.ex:573 +#: lib/bds/desktop/shell_live/post_editor.ex:611 +#: lib/bds/desktop/shell_live/post_editor.ex:626 +#: lib/bds/desktop/shell_live/post_editor.ex:655 +#: lib/bds/desktop/shell_live/post_editor.ex:658 +#: lib/bds/desktop/shell_live/post_editor.ex:738 +#: lib/bds/desktop/shell_live/post_editor.ex:741 #: lib/bds/desktop/shell_live/sidebar_components.ex:651 #: lib/bds/desktop/shell_live/sidebar_delete.ex:174 #: lib/bds/ui/registry.ex:99 @@ -1936,12 +1936,12 @@ msgstr "Plantilla de publicación" msgid "Post is marked as do-not-translate but has translations" msgstr "La entrada está marcada como no-traducir pero tiene traducciones" -#: lib/bds/desktop/shell_live/post_editor.ex:582 +#: lib/bds/desktop/shell_live/post_editor.ex:569 #, elixir-autogen, elixir-format msgid "Post published" msgstr "Artículo publicado" -#: lib/bds/desktop/shell_live/post_editor.ex:539 +#: lib/bds/desktop/shell_live/post_editor.ex:526 #, elixir-autogen, elixir-format msgid "Post saved" msgstr "Artículo guardado" @@ -1949,8 +1949,8 @@ msgstr "Artículo guardado" #: lib/bds/desktop/menu_bar.ex:233 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 -#: lib/bds/tui.ex:1656 -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1665 +#: lib/bds/tui.ex:1680 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1967,7 +1967,7 @@ msgstr "Publicaciones (%{count})" msgid "Preferences" msgstr "Preferencias" -#: lib/bds/desktop/shell_live/post_editor.ex:1042 +#: lib/bds/desktop/shell_live/post_editor.ex:1028 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:124 #, elixir-autogen, elixir-format msgid "Preview" @@ -2010,8 +2010,8 @@ msgstr "Proyecto y publicación" #: lib/bds/desktop/shell_live/chat_surface.ex:15 #: lib/bds/desktop/shell_live/index.html.heex:623 -#: lib/bds/tui.ex:705 -#: lib/bds/tui.ex:710 +#: lib/bds/tui.ex:714 +#: lib/bds/tui.ex:719 #, elixir-autogen, elixir-format msgid "Projects" msgstr "Proyectos" @@ -2040,7 +2040,7 @@ msgid "Publish Selected" msgstr "Publicar seleccionados" #: lib/bds/desktop/shell_data.ex:165 -#: lib/bds/desktop/shell_live/post_editor.ex:1036 +#: lib/bds/desktop/shell_live/post_editor.ex:1022 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 #, elixir-autogen, elixir-format @@ -2079,15 +2079,15 @@ msgid "Ready to import:" msgstr "Listo para importar:" #: lib/bds/desktop/menu_bar.ex:248 -#: lib/bds/tui.ex:789 -#: lib/bds/tui.ex:1798 +#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:1807 #, elixir-autogen, elixir-format msgid "Rebuild Database" msgstr "Reconstruir base de datos" #: lib/bds/desktop/menu_bar.ex:250 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 -#: lib/bds/tui.ex:1802 +#: lib/bds/tui.ex:1811 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "Reconstruir índice de embeddings" @@ -2145,7 +2145,7 @@ msgid "Refresh Translation" msgstr "Actualizar traducción" #: lib/bds/desktop/menu_bar.ex:252 -#: lib/bds/tui.ex:1805 +#: lib/bds/tui.ex:1814 #, elixir-autogen, elixir-format msgid "Regenerate Calendar" msgstr "Regenerar calendario" @@ -2156,7 +2156,7 @@ msgid "Regenerate Missing Thumbnails" msgstr "Regenerar miniaturas faltantes" #: lib/bds/desktop/menu_bar.ex:249 -#: lib/bds/tui.ex:1799 +#: lib/bds/tui.ex:1808 #, elixir-autogen, elixir-format msgid "Reindex Text" msgstr "Reindexar texto" @@ -2240,7 +2240,7 @@ msgstr "Resolución" msgid "Result" msgstr "Resultado" -#: lib/bds/desktop/shell_live/post_editor.ex:1037 +#: lib/bds/desktop/shell_live/post_editor.ex:1023 #, elixir-autogen, elixir-format msgid "Reverted" msgstr "Revertido" @@ -2293,7 +2293,7 @@ msgid "Save Translation" msgstr "Guardar traducción" #: lib/bds/desktop/shell_live/media_editor.ex:730 -#: lib/bds/desktop/shell_live/post_editor.ex:1035 +#: lib/bds/desktop/shell_live/post_editor.ex:1021 #, elixir-autogen, elixir-format msgid "Saved" msgstr "Guardado" @@ -2344,7 +2344,7 @@ msgstr "Las capacidades de scripts se configuran en la capa de aplicación en la #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 -#: lib/bds/tui.ex:1659 +#: lib/bds/tui.ex:1668 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2451,7 +2451,7 @@ msgstr "Similitud semántica" #: lib/bds/desktop/shell_live/settings_editor/project_settings.ex:60 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:11 -#: lib/bds/tui.ex:1661 +#: lib/bds/tui.ex:1670 #: lib/bds/ui/registry.ex:86 #: lib/bds/ui/registry.ex:101 #: lib/bds/ui/sidebar.ex:795 @@ -2477,7 +2477,7 @@ msgstr "Sitio" #: lib/bds/desktop/shell_live/misc_editor.ex:412 #: lib/bds/tui.ex:311 #: lib/bds/tui.ex:313 -#: lib/bds/tui.ex:1417 +#: lib/bds/tui.ex:1426 #: lib/bds/ui/registry.ex:125 #, elixir-autogen, elixir-format msgid "Site Validation" @@ -2541,7 +2541,7 @@ msgstr "Detener" #: lib/bds/desktop/shell_live/settings_editor/style_editor.ex:76 #: lib/bds/desktop/shell_live/settings_editor_html/style_editor.html.heex:3 #: lib/bds/ui/registry.ex:102 -#: lib/bds/ui/settings_form.ex:241 +#: lib/bds/ui/settings_form.ex:246 #: lib/bds/ui/sidebar.ex:817 #, elixir-autogen, elixir-format msgid "Style" @@ -2600,7 +2600,7 @@ msgstr "Nombre de la etiqueta" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 -#: lib/bds/tui.ex:1660 +#: lib/bds/tui.ex:1669 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2612,7 +2612,7 @@ msgstr "Etiquetas" #: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "Tasks" msgstr "Tareas" @@ -2658,7 +2658,7 @@ msgstr "La sintaxis de la plantilla es válida" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 -#: lib/bds/tui.ex:1658 +#: lib/bds/tui.ex:1667 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2772,7 +2772,7 @@ msgstr "Alternar barra lateral" #: lib/bds/desktop/shell_live/media_editor.ex:363 #: lib/bds/desktop/shell_live/media_editor.ex:556 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:76 -#: lib/bds/desktop/shell_live/post_editor.ex:813 +#: lib/bds/desktop/shell_live/post_editor.ex:800 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:60 #, elixir-autogen, elixir-format msgid "Translate" @@ -2849,7 +2849,7 @@ msgstr "Desvincular del artículo" #: lib/bds/desktop/shell_live/media_editor.ex:729 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:10 -#: lib/bds/desktop/shell_live/post_editor.ex:1034 +#: lib/bds/desktop/shell_live/post_editor.ex:1020 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:7 #, elixir-autogen, elixir-format msgid "Unsaved" @@ -2882,7 +2882,7 @@ msgid "Updated URLs" msgstr "URLs actualizadas" #: lib/bds/desktop/menu_bar.ex:259 -#: lib/bds/tui.ex:1817 +#: lib/bds/tui.ex:1826 #, elixir-autogen, elixir-format msgid "Upload Site" msgstr "Subir sitio" @@ -2910,13 +2910,13 @@ msgid "Validate" msgstr "Validar" #: lib/bds/desktop/menu_bar.ex:258 -#: lib/bds/tui.ex:1795 +#: lib/bds/tui.ex:1804 #, elixir-autogen, elixir-format msgid "Validate Site" msgstr "Validar sitio" #: lib/bds/desktop/menu_bar.ex:253 -#: lib/bds/tui.ex:1808 +#: lib/bds/tui.ex:1817 #, elixir-autogen, elixir-format msgid "Validate Translations" msgstr "Validar traducciones" @@ -3350,12 +3350,12 @@ msgstr "Archivar" msgid "Move this post to the archive" msgstr "Mover este artículo al archivo" -#: lib/bds/desktop/shell_live/post_editor.ex:668 +#: lib/bds/desktop/shell_live/post_editor.ex:655 #, elixir-autogen, elixir-format msgid "Post archived" msgstr "Artículo archivado" -#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:738 #, elixir-autogen, elixir-format msgid "Post unarchived" msgstr "Artículo restaurado" @@ -3397,7 +3397,7 @@ msgid "Commit" msgstr "Commit" #: lib/bds/desktop/shell_live/sidebar_components.ex:555 -#: lib/bds/tui.ex:1239 +#: lib/bds/tui.ex:1248 #, elixir-autogen, elixir-format msgid "Commit message" msgstr "Mensaje de commit" @@ -3421,7 +3421,7 @@ msgid "Fetch" msgstr "Obtener" #: lib/bds/desktop/shell_live/sidebar_components.ex:586 -#: lib/bds/tui.ex:1074 +#: lib/bds/tui.ex:1083 #, elixir-autogen, elixir-format msgid "History" msgstr "Historial" @@ -3469,7 +3469,7 @@ msgstr "Limpiar LFS" #: lib/bds/desktop/shell_live/git_handler.ex:17 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 -#: lib/bds/tui.ex:957 +#: lib/bds/tui.ex:966 #, elixir-autogen, elixir-format msgid "Pull" msgstr "Pull" @@ -3477,7 +3477,7 @@ msgstr "Pull" #: lib/bds/desktop/shell_live/git_handler.ex:18 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 -#: lib/bds/tui.ex:958 +#: lib/bds/tui.ex:967 #, elixir-autogen, elixir-format msgid "Push" msgstr "Push" @@ -3543,18 +3543,18 @@ msgstr "Blogmark" msgid "Open a project before importing a blogmark." msgstr "Abre un proyecto antes de importar un blogmark." -#: lib/bds/desktop/shell_live/post_editor.ex:694 +#: lib/bds/desktop/shell_live/post_editor.ex:681 #, elixir-autogen, elixir-format msgid "Added %{name}" msgstr "%{name} añadido" -#: lib/bds/desktop/shell_live/post_editor.ex:701 +#: lib/bds/desktop/shell_live/post_editor.ex:688 #, elixir-autogen, elixir-format msgid "Failed to import %{path}: %{reason}" msgstr "No se pudo importar %{path}: %{reason}" -#: lib/bds/desktop/shell_live/post_editor.ex:693 -#: lib/bds/desktop/shell_live/post_editor.ex:700 +#: lib/bds/desktop/shell_live/post_editor.ex:680 +#: lib/bds/desktop/shell_live/post_editor.ex:687 #, elixir-autogen, elixir-format msgid "Insert Image" msgstr "Insertar imagen" @@ -3569,7 +3569,7 @@ msgstr "Bookmarklet copiado al portapapeles" msgid "Copy Bookmarklet to Clipboard" msgstr "Copiar bookmarklet al portapapeles" -#: lib/bds/desktop/shell_live/overlay_manager.ex:300 +#: lib/bds/desktop/shell_live/overlay_manager.ex:301 #, elixir-autogen, elixir-format msgid "Delete Tag" msgstr "Eliminar etiqueta" @@ -3590,7 +3590,7 @@ msgid "Suggested tags" msgstr "Etiquetas sugeridas" #: lib/bds/desktop/menu_bar.ex:257 -#: lib/bds/tui.ex:1796 +#: lib/bds/tui.ex:1805 #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "Forzar la regeneración del sitio" @@ -3687,12 +3687,12 @@ msgstr "OK" 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." -#: lib/bds/tui.ex:1754 +#: lib/bds/tui.ex:1763 #, elixir-autogen, elixir-format msgid "AI is unavailable in airplane mode without a local endpoint." msgstr "La IA no está disponible en modo avión sin un punto de conexión local." -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1890 #, elixir-autogen, elixir-format msgid "Could not load this file." msgstr "No se pudo cargar este archivo." @@ -3702,68 +3702,68 @@ msgstr "No se pudo cargar este archivo." msgid "Editing this item is not available in the terminal UI yet." msgstr "La edición de este elemento aún no está disponible en la interfaz de terminal." -#: lib/bds/tui.ex:940 +#: lib/bds/tui.ex:949 #, elixir-autogen, elixir-format msgid "No suggestions returned." msgstr "No se recibieron sugerencias." -#: lib/bds/tui.ex:1878 +#: lib/bds/tui.ex:1887 #, elixir-autogen, elixir-format msgid "Only images can be previewed." msgstr "Solo se pueden previsualizar imágenes." -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1680 #, elixir-autogen, elixir-format msgid "Post not found." msgstr "Entrada no encontrada." -#: lib/bds/tui.ex:1174 +#: lib/bds/tui.ex:1183 #, elixir-autogen, elixir-format msgid "Press enter to preview %{name}." msgstr "Pulsa Intro para previsualizar %{name}." -#: lib/bds/tui.ex:1724 +#: lib/bds/tui.ex:1733 #, elixir-autogen, elixir-format msgid "Published." msgstr "Publicado." -#: lib/bds/tui.ex:1740 +#: lib/bds/tui.ex:1749 #, elixir-autogen, elixir-format msgid "Save before switching languages." msgstr "Guarda antes de cambiar de idioma." -#: lib/bds/tui.ex:524 -#: lib/bds/tui.ex:1725 +#: lib/bds/tui.ex:533 +#: lib/bds/tui.ex:1734 #, elixir-autogen, elixir-format msgid "Saved." msgstr "Guardado." -#: lib/bds/tui.ex:1177 +#: lib/bds/tui.ex:1186 #, elixir-autogen, elixir-format msgid "Select an entry and press enter to open it." msgstr "Selecciona una entrada y pulsa Intro para abrirla." -#: lib/bds/tui.ex:937 +#: lib/bds/tui.ex:946 #, elixir-autogen, elixir-format msgid "Suggestions applied." msgstr "Sugerencias aplicadas." -#: lib/bds/tui.ex:1189 +#: lib/bds/tui.ex:1198 #, elixir-autogen, elixir-format msgid "Title (ctrl+t to edit)" msgstr "Título (ctrl+t para editar)" -#: lib/bds/tui.ex:1188 +#: lib/bds/tui.ex:1197 #, elixir-autogen, elixir-format msgid "Title (editing — enter to confirm)" msgstr "Título (edición — Intro para confirmar)" -#: lib/bds/tui.ex:1241 +#: lib/bds/tui.ex:1250 #, elixir-autogen, elixir-format msgid "Working…" msgstr "Procesando…" -#: lib/bds/tui.ex:1263 +#: lib/bds/tui.ex:1272 #, elixir-autogen, elixir-format msgid "esc to close" msgstr "esc para cerrar" @@ -3800,52 +3800,52 @@ msgstr "Dirección del servidor (user@host o user@host:port), autenticación de msgid "Use the form user@host or user@host:port." msgstr "Usa el formato user@host o user@host:port." -#: lib/bds/tui.ex:1208 +#: lib/bds/tui.ex:1217 #, elixir-autogen, elixir-format msgid "Preview (ctrl+e to edit)" msgstr "Vista previa (ctrl+e para editar)" -#: lib/bds/tui.ex:1520 +#: lib/bds/tui.ex:1529 #, elixir-autogen, elixir-format msgid "ctrl+s save · ctrl+p publish · ctrl+e preview · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" msgstr "ctrl+s guardar · ctrl+p publicar · ctrl+e vista previa · ctrl+t título · ctrl+l idioma · ctrl+g IA · esc volver" -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "All tasks finished." msgstr "Todas las tareas han terminado." -#: lib/bds/tui.ex:1289 +#: lib/bds/tui.ex:1298 #, elixir-autogen, elixir-format msgid "Commands — esc to close" msgstr "Comandos — esc para cerrar" -#: lib/bds/tui.ex:814 +#: lib/bds/tui.ex:823 #, elixir-autogen, elixir-format msgid "Unknown command." msgstr "Comando desconocido." -#: lib/bds/tui.ex:1279 +#: lib/bds/tui.ex:1288 #, elixir-autogen, elixir-format msgid "[report/apply in GUI]" msgstr "[informe/aplicación en la GUI]" -#: lib/bds/tui.ex:1428 +#: lib/bds/tui.ex:1437 #, elixir-autogen, elixir-format msgid "%{diffs} differences · %{orphans} orphan files" msgstr "%{diffs} diferencias · %{orphans} archivos huérfanos" -#: lib/bds/tui.ex:1460 +#: lib/bds/tui.ex:1469 #, elixir-autogen, elixir-format msgid "Expected %{expected} · Existing %{existing}" msgstr "Esperados %{expected} · Existentes %{existing}" -#: lib/bds/tui.ex:1477 +#: lib/bds/tui.ex:1486 #, elixir-autogen, elixir-format msgid "Extra files (will be removed):" msgstr "Archivos sobrantes (se eliminarán):" -#: lib/bds/tui.ex:1473 +#: lib/bds/tui.ex:1482 #, elixir-autogen, elixir-format msgid "Missing pages (will be rendered):" msgstr "Páginas faltantes (se generarán):" @@ -3860,57 +3860,57 @@ msgstr "Nada que aplicar." msgid "Nothing to repair." msgstr "Nada que reparar." -#: lib/bds/tui.ex:1451 +#: lib/bds/tui.ex:1460 #, elixir-autogen, elixir-format msgid "Orphan files (not in database):" msgstr "Archivos huérfanos (no están en la base de datos):" -#: lib/bds/tui.ex:1467 +#: lib/bds/tui.ex:1476 #, elixir-autogen, elixir-format msgid "Sitemap changed." msgstr "Sitemap modificado." -#: lib/bds/tui.ex:1481 +#: lib/bds/tui.ex:1490 #, elixir-autogen, elixir-format msgid "Updated posts (will be re-rendered):" msgstr "Entradas actualizadas (se volverán a generar):" -#: lib/bds/tui.ex:1418 +#: lib/bds/tui.ex:1427 #, elixir-autogen, elixir-format msgid "enter apply changes · esc close" msgstr "intro aplicar cambios · esc cerrar" -#: lib/bds/tui.ex:1413 +#: lib/bds/tui.ex:1422 #, elixir-autogen, elixir-format msgid "enter repair all from files · esc close" msgstr "intro reparar todo desde archivos · esc cerrar" -#: lib/bds/tui.ex:779 +#: lib/bds/tui.ex:788 #, elixir-autogen, elixir-format msgid "Imported Blog" msgstr "Blog importado" -#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:807 #, elixir-autogen, elixir-format msgid "Not a folder: %{path}" msgstr "No es una carpeta: %{path}" -#: lib/bds/tui.ex:1355 +#: lib/bds/tui.ex:1364 #, elixir-autogen, elixir-format msgid "Projects — enter switch · o open existing · esc close" msgstr "Proyectos — intro cambiar · o abrir existente · esc cerrar" -#: lib/bds/tui.ex:706 +#: lib/bds/tui.ex:715 #, elixir-autogen, elixir-format msgid "Switched to %{name}." msgstr "Cambiado a %{name}." -#: lib/bds/tui.ex:1383 +#: lib/bds/tui.ex:1392 #, elixir-autogen, elixir-format msgid "Matching folders" msgstr "Carpetas coincidentes" -#: lib/bds/tui.ex:1321 +#: lib/bds/tui.ex:1330 #, elixir-autogen, elixir-format msgid "Open Existing Blog — type a folder path · tab complete · enter open · esc back" msgstr "Abrir blog existente — escribe la ruta de una carpeta · tab completar · intro abrir · esc volver" @@ -3925,12 +3925,12 @@ msgstr "Se requiere un mensaje de commit." msgid "Committed." msgstr "Commit realizado." -#: lib/bds/tui.ex:1165 +#: lib/bds/tui.ex:1174 #, elixir-autogen, elixir-format msgid "Diff (pgup/pgdn scroll)" msgstr "Diff (pgup/pgdn para desplazar)" -#: lib/bds/tui.ex:1548 +#: lib/bds/tui.ex:1557 #, elixir-autogen, elixir-format msgid "No changes." msgstr "Sin cambios." @@ -3941,7 +3941,7 @@ msgid "No diff for this file." msgstr "No hay diff para este archivo." #: lib/bds/tui.ex:388 -#: lib/bds/tui.ex:1152 +#: lib/bds/tui.ex:1161 #, elixir-autogen, elixir-format msgid "Not a git repository." msgstr "No es un repositorio git." @@ -4031,32 +4031,71 @@ msgstr "Usuario SSH" msgid "Show Title" msgstr "Mostrar título" -#: lib/bds/ui/settings_form.ex:242 +#: lib/bds/ui/settings_form.ex:247 #, elixir-autogen, elixir-format msgid "Theme" msgstr "Tema" -#: lib/bds/tui.ex:1136 +#: lib/bds/tui.ex:1145 #, elixir-autogen, elixir-format msgid "enter edit · ctrl+s save · esc close" msgstr "intro editar · ctrl+s guardar · esc cerrar" -#: lib/bds/tui.ex:1499 +#: lib/bds/tui.ex:1508 #, elixir-autogen, elixir-format msgid "enter edit/toggle/cycle · ctrl+s save · esc close · ctrl+q quit" msgstr "intro editar/alternar/ciclar · ctrl+s guardar · esc cerrar · ctrl+q salir" -#: lib/bds/ui/settings_form.ex:230 +#: lib/bds/ui/settings_form.ex:235 #, elixir-autogen, elixir-format msgid "not supported yet" msgstr "aún no compatible" -#: lib/bds/tui.ex:1506 +#: lib/bds/tui.ex:1515 #, elixir-autogen, elixir-format msgid "c commit · u pull · s push · enter jump to file · pgup/pgdn scroll diff · 1-7 views · ctrl+q quit" msgstr "c commit · u pull · s push · intro ir al archivo · pgup/pgdn desplazar el diff · 1-7 vistas · ctrl+q salir" -#: lib/bds/tui.ex:1513 +#: lib/bds/tui.ex:1522 #, elixir-autogen, elixir-format msgid "enter open · n new post · 1-7 views · p projects · / search · : commands (:? help) · r refresh · ctrl+q quit" msgstr "intro abrir · n nueva entrada · 1-7 vistas · p proyectos · / buscar · : comandos (:? ayuda) · r actualizar · ctrl+q salir" + +#: lib/bds/ui/settings_form.ex:369 +#, elixir-autogen, elixir-format +msgid "CLI tool installed to %{path}" +msgstr "Herramienta CLI instalada en %{path}" + +#: lib/bds/desktop/shell_live/settings_editor.ex:75 +#: lib/bds/desktop/shell_live/settings_editor.ex:78 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:386 +#: lib/bds/ui/settings_form.ex:223 +#, elixir-autogen, elixir-format +msgid "Command Line Tool" +msgstr "Herramienta de línea de comandos" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:390 +#: lib/bds/ui/settings_form.ex:224 +#, elixir-autogen, elixir-format +msgid "Install CLI Tool" +msgstr "Instalar la herramienta CLI" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:387 +#, elixir-autogen, elixir-format +msgid "Install the bds-cli tool to ~/.local/bin; it uses the same settings and cache database as the app" +msgstr "Instala la herramienta bds-cli en ~/.local/bin; utiliza la misma configuración y la misma base de datos de caché que la aplicación" + +#: lib/bds/ui/settings_form.ex:375 +#, elixir-autogen, elixir-format +msgid "Installing the CLI tool failed: %{reason}" +msgstr "Error al instalar la herramienta CLI: %{reason}" + +#: lib/bds/ui/settings_form.ex:372 +#, elixir-autogen, elixir-format +msgid "The CLI tool can only be installed from the packaged application" +msgstr "La herramienta CLI solo puede instalarse desde la aplicación empaquetada" + +#: lib/bds/ui/settings_form.ex:379 +#, elixir-autogen, elixir-format +msgid "Unknown settings action" +msgstr "Acción de configuración desconocida" diff --git a/priv/gettext/fr/LC_MESSAGES/ui.po b/priv/gettext/fr/LC_MESSAGES/ui.po index 664944e..7cf0ad1 100644 --- a/priv/gettext/fr/LC_MESSAGES/ui.po +++ b/priv/gettext/fr/LC_MESSAGES/ui.po @@ -80,13 +80,13 @@ msgstr "Paramètres IA" #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:42 #: lib/bds/desktop/shell_live/overlay_manager.ex:72 -#: lib/bds/desktop/shell_live/post_editor.ex:920 +#: lib/bds/desktop/shell_live/post_editor.ex:906 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 -#: lib/bds/tui.ex:937 -#: lib/bds/tui.ex:940 -#: lib/bds/tui.ex:943 -#: lib/bds/tui.ex:951 -#: lib/bds/tui.ex:1753 +#: lib/bds/tui.ex:946 +#: lib/bds/tui.ex:949 +#: lib/bds/tui.ex:952 +#: lib/bds/tui.ex:960 +#: lib/bds/tui.ex:1762 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "Suggestions IA" @@ -349,8 +349,8 @@ msgstr "Automatique" #: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:557 #: lib/bds/desktop/shell_live/overlay_manager.ex:73 -#: lib/bds/desktop/shell_live/post_editor.ex:765 -#: lib/bds/desktop/shell_live/post_editor.ex:814 +#: lib/bds/desktop/shell_live/post_editor.ex:752 +#: lib/bds/desktop/shell_live/post_editor.ex:801 #, elixir-autogen, elixir-format msgid "Automatic AI actions stay gated by airplane mode." msgstr "Les actions IA automatiques restent bloquées par le mode avion." @@ -574,8 +574,8 @@ msgstr "Fermer l’onglet" msgid "Collapse unchanged diff hunks" msgstr "Réduire les blocs de diff inchangés" -#: lib/bds/desktop/shell_live/overlay_manager.ex:422 -#: lib/bds/tui.ex:1849 +#: lib/bds/desktop/shell_live/overlay_manager.ex:423 +#: lib/bds/tui.ex:1858 #, elixir-autogen, elixir-format msgid "Command completed" msgstr "Commande terminée" @@ -593,7 +593,7 @@ msgstr "Confirmer" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 -#: lib/bds/tui.ex:1219 +#: lib/bds/tui.ex:1228 #: lib/bds/ui/settings_form.ex:137 #: lib/bds/ui/sidebar.ex:801 #, elixir-autogen, elixir-format @@ -751,7 +751,7 @@ msgstr "Mode d’édition par défaut et présentation des diffs" msgid "Delete" msgstr "Supprimer" -#: lib/bds/desktop/shell_live/overlay_manager.ex:278 +#: lib/bds/desktop/shell_live/overlay_manager.ex:279 #, elixir-autogen, elixir-format msgid "Delete Media" msgstr "Supprimer le media" @@ -798,9 +798,9 @@ msgstr "Détecter" #: lib/bds/desktop/shell_live/media_editor.ex:214 #: lib/bds/desktop/shell_live/media_editor.ex:220 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:59 -#: lib/bds/desktop/shell_live/post_editor.ex:764 -#: lib/bds/desktop/shell_live/post_editor.ex:793 -#: lib/bds/desktop/shell_live/post_editor.ex:799 +#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:780 +#: lib/bds/desktop/shell_live/post_editor.ex:786 #, elixir-autogen, elixir-format msgid "Detect Language" msgstr "Détecter la langue" @@ -1040,7 +1040,7 @@ msgid "Filename" msgstr "Nom de fichier" #: lib/bds/desktop/menu_bar.ex:254 -#: lib/bds/tui.ex:1813 +#: lib/bds/tui.ex:1822 #, elixir-autogen, elixir-format msgid "Fill Missing Translations" msgstr "Compléter les traductions manquantes" @@ -1051,7 +1051,7 @@ msgid "Find" msgstr "Rechercher" #: lib/bds/desktop/menu_bar.ex:255 -#: lib/bds/tui.ex:1816 +#: lib/bds/tui.ex:1825 #, elixir-autogen, elixir-format msgid "Find Duplicate Posts" msgstr "Trouver les doublons" @@ -1078,14 +1078,14 @@ msgid "Gallery" msgstr "Galerie" #: lib/bds/desktop/menu_bar.ex:256 -#: lib/bds/tui.ex:1797 +#: lib/bds/tui.ex:1806 #, elixir-autogen, elixir-format msgid "Generate Site" msgstr "Générer le site" #: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live/socket_state.ex:109 -#: lib/bds/tui.ex:1662 +#: lib/bds/tui.ex:1671 #: lib/bds/ui/sidebar.ex:826 #, elixir-autogen, elixir-format msgid "Git" @@ -1128,7 +1128,7 @@ msgstr "Hôte" #: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 -#: lib/bds/desktop/shell_live/post_editor.ex:1038 +#: lib/bds/desktop/shell_live/post_editor.ex:1024 #, elixir-autogen, elixir-format msgid "Idle" msgstr "Inactif" @@ -1299,7 +1299,7 @@ msgid "Language" msgstr "Langue" #: lib/bds/desktop/shell_live/media_editor.ex:221 -#: lib/bds/desktop/shell_live/post_editor.ex:800 +#: lib/bds/desktop/shell_live/post_editor.ex:787 #, elixir-autogen, elixir-format msgid "Language detection failed." msgstr "La détection de la langue a échoué." @@ -1345,7 +1345,7 @@ msgstr "Charger plus" #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:50 #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:57 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:351 -#: lib/bds/ui/settings_form.ex:234 +#: lib/bds/ui/settings_form.ex:239 #: lib/bds/ui/sidebar.ex:816 #, elixir-autogen, elixir-format msgid "MCP" @@ -1386,7 +1386,7 @@ msgstr "Mapper vers..." msgid "Mapped" msgstr "Mappé" -#: lib/bds/desktop/shell_live/post_editor.ex:1041 +#: lib/bds/desktop/shell_live/post_editor.ex:1027 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:123 #, elixir-autogen, elixir-format msgid "Markdown" @@ -1404,9 +1404,9 @@ msgstr "Nombre maximal d’articles par page" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 -#: lib/bds/tui.ex:1657 -#: lib/bds/tui.ex:1878 -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1666 +#: lib/bds/tui.ex:1887 +#: lib/bds/tui.ex:1890 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:578 @@ -1452,8 +1452,8 @@ msgstr "Métadonnées" #: lib/bds/tui.ex:278 #: lib/bds/tui.ex:284 #: lib/bds/tui.ex:295 -#: lib/bds/tui.ex:1412 -#: lib/bds/tui.ex:1794 +#: lib/bds/tui.ex:1421 +#: lib/bds/tui.ex:1803 #: lib/bds/ui/registry.ex:111 #, elixir-autogen, elixir-format msgid "Metadata Diff" @@ -1787,8 +1787,8 @@ msgid "Open Data Folder" msgstr "Ouvrir le dossier de données" #: lib/bds/desktop/shell_live/index.html.heex:644 -#: lib/bds/tui.ex:792 -#: lib/bds/tui.ex:797 +#: lib/bds/tui.ex:801 +#: lib/bds/tui.ex:806 #, elixir-autogen, elixir-format msgid "Open Existing Blog" msgstr "Ouvrir un blog existant" @@ -1800,7 +1800,7 @@ msgid "Open Settings" msgstr "Ouvrir les Réglages" #: lib/bds/desktop/menu_bar.ex:217 -#: lib/bds/tui.ex:1818 +#: lib/bds/tui.ex:1827 #, elixir-autogen, elixir-format msgid "Open in Browser" msgstr "Ouvrir dans le navigateur" @@ -1896,16 +1896,16 @@ msgid "Persist the detected language for this media item" msgstr "Enregistrer la langue détectée pour ce média" #: lib/bds/desktop/shell_live/misc_editor.ex:733 -#: lib/bds/desktop/shell_live/post_editor.ex:539 -#: lib/bds/desktop/shell_live/post_editor.ex:543 -#: lib/bds/desktop/shell_live/post_editor.ex:582 -#: lib/bds/desktop/shell_live/post_editor.ex:586 -#: lib/bds/desktop/shell_live/post_editor.ex:624 -#: lib/bds/desktop/shell_live/post_editor.ex:639 -#: lib/bds/desktop/shell_live/post_editor.ex:668 -#: lib/bds/desktop/shell_live/post_editor.ex:671 -#: lib/bds/desktop/shell_live/post_editor.ex:751 -#: lib/bds/desktop/shell_live/post_editor.ex:754 +#: lib/bds/desktop/shell_live/post_editor.ex:526 +#: lib/bds/desktop/shell_live/post_editor.ex:530 +#: lib/bds/desktop/shell_live/post_editor.ex:569 +#: lib/bds/desktop/shell_live/post_editor.ex:573 +#: lib/bds/desktop/shell_live/post_editor.ex:611 +#: lib/bds/desktop/shell_live/post_editor.ex:626 +#: lib/bds/desktop/shell_live/post_editor.ex:655 +#: lib/bds/desktop/shell_live/post_editor.ex:658 +#: lib/bds/desktop/shell_live/post_editor.ex:738 +#: lib/bds/desktop/shell_live/post_editor.ex:741 #: lib/bds/desktop/shell_live/sidebar_components.ex:651 #: lib/bds/desktop/shell_live/sidebar_delete.ex:174 #: lib/bds/ui/registry.ex:99 @@ -1936,12 +1936,12 @@ msgstr "Modèle d’article" msgid "Post is marked as do-not-translate but has translations" msgstr "L'article est marqué ne-pas-traduire mais a des traductions" -#: lib/bds/desktop/shell_live/post_editor.ex:582 +#: lib/bds/desktop/shell_live/post_editor.ex:569 #, elixir-autogen, elixir-format msgid "Post published" msgstr "Article publié" -#: lib/bds/desktop/shell_live/post_editor.ex:539 +#: lib/bds/desktop/shell_live/post_editor.ex:526 #, elixir-autogen, elixir-format msgid "Post saved" msgstr "Article enregistré" @@ -1949,8 +1949,8 @@ msgstr "Article enregistré" #: lib/bds/desktop/menu_bar.ex:233 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 -#: lib/bds/tui.ex:1656 -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1665 +#: lib/bds/tui.ex:1680 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1967,7 +1967,7 @@ msgstr "Articles (%{count})" msgid "Preferences" msgstr "Préférences" -#: lib/bds/desktop/shell_live/post_editor.ex:1042 +#: lib/bds/desktop/shell_live/post_editor.ex:1028 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:124 #, elixir-autogen, elixir-format msgid "Preview" @@ -2010,8 +2010,8 @@ msgstr "Projet et publication" #: lib/bds/desktop/shell_live/chat_surface.ex:15 #: lib/bds/desktop/shell_live/index.html.heex:623 -#: lib/bds/tui.ex:705 -#: lib/bds/tui.ex:710 +#: lib/bds/tui.ex:714 +#: lib/bds/tui.ex:719 #, elixir-autogen, elixir-format msgid "Projects" msgstr "Projets" @@ -2040,7 +2040,7 @@ msgid "Publish Selected" msgstr "Publier la sélection" #: lib/bds/desktop/shell_data.ex:165 -#: lib/bds/desktop/shell_live/post_editor.ex:1036 +#: lib/bds/desktop/shell_live/post_editor.ex:1022 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 #, elixir-autogen, elixir-format @@ -2079,15 +2079,15 @@ msgid "Ready to import:" msgstr "Prêt à importer :" #: lib/bds/desktop/menu_bar.ex:248 -#: lib/bds/tui.ex:789 -#: lib/bds/tui.ex:1798 +#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:1807 #, elixir-autogen, elixir-format msgid "Rebuild Database" msgstr "Reconstruire la base de données" #: lib/bds/desktop/menu_bar.ex:250 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 -#: lib/bds/tui.ex:1802 +#: lib/bds/tui.ex:1811 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "Reconstruire l’index d’embeddings" @@ -2145,7 +2145,7 @@ msgid "Refresh Translation" msgstr "Actualiser la traduction" #: lib/bds/desktop/menu_bar.ex:252 -#: lib/bds/tui.ex:1805 +#: lib/bds/tui.ex:1814 #, elixir-autogen, elixir-format msgid "Regenerate Calendar" msgstr "Régénérer le calendrier" @@ -2156,7 +2156,7 @@ msgid "Regenerate Missing Thumbnails" msgstr "Régénérer les vignettes manquantes" #: lib/bds/desktop/menu_bar.ex:249 -#: lib/bds/tui.ex:1799 +#: lib/bds/tui.ex:1808 #, elixir-autogen, elixir-format msgid "Reindex Text" msgstr "Réindexer le texte" @@ -2240,7 +2240,7 @@ msgstr "Résolution" msgid "Result" msgstr "Résultat" -#: lib/bds/desktop/shell_live/post_editor.ex:1037 +#: lib/bds/desktop/shell_live/post_editor.ex:1023 #, elixir-autogen, elixir-format msgid "Reverted" msgstr "Restauré" @@ -2293,7 +2293,7 @@ msgid "Save Translation" msgstr "Enregistrer la traduction" #: lib/bds/desktop/shell_live/media_editor.ex:730 -#: lib/bds/desktop/shell_live/post_editor.ex:1035 +#: lib/bds/desktop/shell_live/post_editor.ex:1021 #, elixir-autogen, elixir-format msgid "Saved" msgstr "Enregistré" @@ -2344,7 +2344,7 @@ msgstr "Les capacités de script sont configurées au niveau de l’application #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 -#: lib/bds/tui.ex:1659 +#: lib/bds/tui.ex:1668 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2451,7 +2451,7 @@ msgstr "Similarité sémantique" #: lib/bds/desktop/shell_live/settings_editor/project_settings.ex:60 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:11 -#: lib/bds/tui.ex:1661 +#: lib/bds/tui.ex:1670 #: lib/bds/ui/registry.ex:86 #: lib/bds/ui/registry.ex:101 #: lib/bds/ui/sidebar.ex:795 @@ -2477,7 +2477,7 @@ msgstr "Site" #: lib/bds/desktop/shell_live/misc_editor.ex:412 #: lib/bds/tui.ex:311 #: lib/bds/tui.ex:313 -#: lib/bds/tui.ex:1417 +#: lib/bds/tui.ex:1426 #: lib/bds/ui/registry.ex:125 #, elixir-autogen, elixir-format msgid "Site Validation" @@ -2541,7 +2541,7 @@ msgstr "Arrêter" #: lib/bds/desktop/shell_live/settings_editor/style_editor.ex:76 #: lib/bds/desktop/shell_live/settings_editor_html/style_editor.html.heex:3 #: lib/bds/ui/registry.ex:102 -#: lib/bds/ui/settings_form.ex:241 +#: lib/bds/ui/settings_form.ex:246 #: lib/bds/ui/sidebar.ex:817 #, elixir-autogen, elixir-format msgid "Style" @@ -2600,7 +2600,7 @@ msgstr "Nom du mot-clé" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 -#: lib/bds/tui.ex:1660 +#: lib/bds/tui.ex:1669 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2612,7 +2612,7 @@ msgstr "Tags" #: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "Tasks" msgstr "Tâches" @@ -2658,7 +2658,7 @@ msgstr "La syntaxe du template est valide" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 -#: lib/bds/tui.ex:1658 +#: lib/bds/tui.ex:1667 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2772,7 +2772,7 @@ msgstr "Afficher ou masquer la barre latérale" #: lib/bds/desktop/shell_live/media_editor.ex:363 #: lib/bds/desktop/shell_live/media_editor.ex:556 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:76 -#: lib/bds/desktop/shell_live/post_editor.ex:813 +#: lib/bds/desktop/shell_live/post_editor.ex:800 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:60 #, elixir-autogen, elixir-format msgid "Translate" @@ -2849,7 +2849,7 @@ msgstr "Dissocier de l'article" #: lib/bds/desktop/shell_live/media_editor.ex:729 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:10 -#: lib/bds/desktop/shell_live/post_editor.ex:1034 +#: lib/bds/desktop/shell_live/post_editor.ex:1020 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:7 #, elixir-autogen, elixir-format msgid "Unsaved" @@ -2882,7 +2882,7 @@ msgid "Updated URLs" msgstr "URLs mises à jour" #: lib/bds/desktop/menu_bar.ex:259 -#: lib/bds/tui.ex:1817 +#: lib/bds/tui.ex:1826 #, elixir-autogen, elixir-format msgid "Upload Site" msgstr "Téléverser le site" @@ -2910,13 +2910,13 @@ msgid "Validate" msgstr "Valider" #: lib/bds/desktop/menu_bar.ex:258 -#: lib/bds/tui.ex:1795 +#: lib/bds/tui.ex:1804 #, elixir-autogen, elixir-format msgid "Validate Site" msgstr "Valider le site" #: lib/bds/desktop/menu_bar.ex:253 -#: lib/bds/tui.ex:1808 +#: lib/bds/tui.ex:1817 #, elixir-autogen, elixir-format msgid "Validate Translations" msgstr "Valider les traductions" @@ -3350,12 +3350,12 @@ msgstr "Archiver" msgid "Move this post to the archive" msgstr "Déplacer cet article dans les archives" -#: lib/bds/desktop/shell_live/post_editor.ex:668 +#: lib/bds/desktop/shell_live/post_editor.ex:655 #, elixir-autogen, elixir-format msgid "Post archived" msgstr "Article archivé" -#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:738 #, elixir-autogen, elixir-format msgid "Post unarchived" msgstr "Article désarchivé" @@ -3397,7 +3397,7 @@ msgid "Commit" msgstr "Commit" #: lib/bds/desktop/shell_live/sidebar_components.ex:555 -#: lib/bds/tui.ex:1239 +#: lib/bds/tui.ex:1248 #, elixir-autogen, elixir-format msgid "Commit message" msgstr "Message de commit" @@ -3421,7 +3421,7 @@ msgid "Fetch" msgstr "Récupérer" #: lib/bds/desktop/shell_live/sidebar_components.ex:586 -#: lib/bds/tui.ex:1074 +#: lib/bds/tui.ex:1083 #, elixir-autogen, elixir-format msgid "History" msgstr "Historique" @@ -3469,7 +3469,7 @@ msgstr "Nettoyer LFS" #: lib/bds/desktop/shell_live/git_handler.ex:17 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 -#: lib/bds/tui.ex:957 +#: lib/bds/tui.ex:966 #, elixir-autogen, elixir-format msgid "Pull" msgstr "Pull" @@ -3477,7 +3477,7 @@ msgstr "Pull" #: lib/bds/desktop/shell_live/git_handler.ex:18 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 -#: lib/bds/tui.ex:958 +#: lib/bds/tui.ex:967 #, elixir-autogen, elixir-format msgid "Push" msgstr "Push" @@ -3543,18 +3543,18 @@ msgstr "Blogmark" msgid "Open a project before importing a blogmark." msgstr "Ouvrez un projet avant d’importer un blogmark." -#: lib/bds/desktop/shell_live/post_editor.ex:694 +#: lib/bds/desktop/shell_live/post_editor.ex:681 #, elixir-autogen, elixir-format msgid "Added %{name}" msgstr "%{name} ajouté" -#: lib/bds/desktop/shell_live/post_editor.ex:701 +#: lib/bds/desktop/shell_live/post_editor.ex:688 #, elixir-autogen, elixir-format msgid "Failed to import %{path}: %{reason}" msgstr "Échec de l'import de %{path} : %{reason}" -#: lib/bds/desktop/shell_live/post_editor.ex:693 -#: lib/bds/desktop/shell_live/post_editor.ex:700 +#: lib/bds/desktop/shell_live/post_editor.ex:680 +#: lib/bds/desktop/shell_live/post_editor.ex:687 #, elixir-autogen, elixir-format msgid "Insert Image" msgstr "Insérer une image" @@ -3569,7 +3569,7 @@ msgstr "Bookmarklet copié dans le presse-papiers" msgid "Copy Bookmarklet to Clipboard" msgstr "Copier le bookmarklet dans le presse-papiers" -#: lib/bds/desktop/shell_live/overlay_manager.ex:300 +#: lib/bds/desktop/shell_live/overlay_manager.ex:301 #, elixir-autogen, elixir-format msgid "Delete Tag" msgstr "Supprimer le tag" @@ -3590,7 +3590,7 @@ msgid "Suggested tags" msgstr "Tags suggérés" #: lib/bds/desktop/menu_bar.ex:257 -#: lib/bds/tui.ex:1796 +#: lib/bds/tui.ex:1805 #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "Forcer la régénération du site" @@ -3687,12 +3687,12 @@ msgstr "OK" 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." -#: lib/bds/tui.ex:1754 +#: lib/bds/tui.ex:1763 #, elixir-autogen, elixir-format msgid "AI is unavailable in airplane mode without a local endpoint." msgstr "L'IA n'est pas disponible en mode avion sans point de terminaison local." -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1890 #, elixir-autogen, elixir-format msgid "Could not load this file." msgstr "Impossible de charger ce fichier." @@ -3702,68 +3702,68 @@ msgstr "Impossible de charger ce fichier." msgid "Editing this item is not available in the terminal UI yet." msgstr "La modification de cet élément n'est pas encore disponible dans l'interface du terminal." -#: lib/bds/tui.ex:940 +#: lib/bds/tui.ex:949 #, elixir-autogen, elixir-format msgid "No suggestions returned." msgstr "Aucune suggestion reçue." -#: lib/bds/tui.ex:1878 +#: lib/bds/tui.ex:1887 #, elixir-autogen, elixir-format msgid "Only images can be previewed." msgstr "Seules les images peuvent être prévisualisées." -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1680 #, elixir-autogen, elixir-format msgid "Post not found." msgstr "Article introuvable." -#: lib/bds/tui.ex:1174 +#: lib/bds/tui.ex:1183 #, elixir-autogen, elixir-format msgid "Press enter to preview %{name}." msgstr "Appuyez sur Entrée pour prévisualiser %{name}." -#: lib/bds/tui.ex:1724 +#: lib/bds/tui.ex:1733 #, elixir-autogen, elixir-format msgid "Published." msgstr "Publié." -#: lib/bds/tui.ex:1740 +#: lib/bds/tui.ex:1749 #, elixir-autogen, elixir-format msgid "Save before switching languages." msgstr "Enregistrez avant de changer de langue." -#: lib/bds/tui.ex:524 -#: lib/bds/tui.ex:1725 +#: lib/bds/tui.ex:533 +#: lib/bds/tui.ex:1734 #, elixir-autogen, elixir-format msgid "Saved." msgstr "Enregistré." -#: lib/bds/tui.ex:1177 +#: lib/bds/tui.ex:1186 #, elixir-autogen, elixir-format msgid "Select an entry and press enter to open it." msgstr "Sélectionnez une entrée et appuyez sur Entrée pour l'ouvrir." -#: lib/bds/tui.ex:937 +#: lib/bds/tui.ex:946 #, elixir-autogen, elixir-format msgid "Suggestions applied." msgstr "Suggestions appliquées." -#: lib/bds/tui.ex:1189 +#: lib/bds/tui.ex:1198 #, elixir-autogen, elixir-format msgid "Title (ctrl+t to edit)" msgstr "Titre (ctrl+t pour modifier)" -#: lib/bds/tui.ex:1188 +#: lib/bds/tui.ex:1197 #, elixir-autogen, elixir-format msgid "Title (editing — enter to confirm)" msgstr "Titre (édition — Entrée pour confirmer)" -#: lib/bds/tui.ex:1241 +#: lib/bds/tui.ex:1250 #, elixir-autogen, elixir-format msgid "Working…" msgstr "Traitement en cours…" -#: lib/bds/tui.ex:1263 +#: lib/bds/tui.ex:1272 #, elixir-autogen, elixir-format msgid "esc to close" msgstr "échap pour fermer" @@ -3800,52 +3800,52 @@ msgstr "Adresse du serveur (user@host ou user@host:port), authentification par c msgid "Use the form user@host or user@host:port." msgstr "Utilisez le format user@host ou user@host:port." -#: lib/bds/tui.ex:1208 +#: lib/bds/tui.ex:1217 #, elixir-autogen, elixir-format msgid "Preview (ctrl+e to edit)" msgstr "Aperçu (ctrl+e pour modifier)" -#: lib/bds/tui.ex:1520 +#: lib/bds/tui.ex:1529 #, elixir-autogen, elixir-format msgid "ctrl+s save · ctrl+p publish · ctrl+e preview · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" msgstr "ctrl+s enregistrer · ctrl+p publier · ctrl+e aperçu · ctrl+t titre · ctrl+l langue · ctrl+g IA · échap retour" -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "All tasks finished." msgstr "Toutes les tâches sont terminées." -#: lib/bds/tui.ex:1289 +#: lib/bds/tui.ex:1298 #, elixir-autogen, elixir-format msgid "Commands — esc to close" msgstr "Commandes — échap pour fermer" -#: lib/bds/tui.ex:814 +#: lib/bds/tui.ex:823 #, elixir-autogen, elixir-format msgid "Unknown command." msgstr "Commande inconnue." -#: lib/bds/tui.ex:1279 +#: lib/bds/tui.ex:1288 #, elixir-autogen, elixir-format msgid "[report/apply in GUI]" msgstr "[rapport/application dans la GUI]" -#: lib/bds/tui.ex:1428 +#: lib/bds/tui.ex:1437 #, elixir-autogen, elixir-format msgid "%{diffs} differences · %{orphans} orphan files" msgstr "%{diffs} différences · %{orphans} fichiers orphelins" -#: lib/bds/tui.ex:1460 +#: lib/bds/tui.ex:1469 #, elixir-autogen, elixir-format msgid "Expected %{expected} · Existing %{existing}" msgstr "Attendu %{expected} · Existant %{existing}" -#: lib/bds/tui.ex:1477 +#: lib/bds/tui.ex:1486 #, elixir-autogen, elixir-format msgid "Extra files (will be removed):" msgstr "Fichiers en trop (seront supprimés) :" -#: lib/bds/tui.ex:1473 +#: lib/bds/tui.ex:1482 #, elixir-autogen, elixir-format msgid "Missing pages (will be rendered):" msgstr "Pages manquantes (seront rendues) :" @@ -3860,57 +3860,57 @@ msgstr "Rien à appliquer." msgid "Nothing to repair." msgstr "Rien à réparer." -#: lib/bds/tui.ex:1451 +#: lib/bds/tui.ex:1460 #, elixir-autogen, elixir-format msgid "Orphan files (not in database):" msgstr "Fichiers orphelins (absents de la base de données) :" -#: lib/bds/tui.ex:1467 +#: lib/bds/tui.ex:1476 #, elixir-autogen, elixir-format msgid "Sitemap changed." msgstr "Sitemap modifié." -#: lib/bds/tui.ex:1481 +#: lib/bds/tui.ex:1490 #, elixir-autogen, elixir-format msgid "Updated posts (will be re-rendered):" msgstr "Articles mis à jour (seront rendus à nouveau) :" -#: lib/bds/tui.ex:1418 +#: lib/bds/tui.ex:1427 #, elixir-autogen, elixir-format msgid "enter apply changes · esc close" msgstr "entrée appliquer les modifications · échap fermer" -#: lib/bds/tui.ex:1413 +#: lib/bds/tui.ex:1422 #, elixir-autogen, elixir-format msgid "enter repair all from files · esc close" msgstr "entrée tout réparer depuis les fichiers · échap fermer" -#: lib/bds/tui.ex:779 +#: lib/bds/tui.ex:788 #, elixir-autogen, elixir-format msgid "Imported Blog" msgstr "Blog importé" -#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:807 #, elixir-autogen, elixir-format msgid "Not a folder: %{path}" msgstr "Pas un dossier : %{path}" -#: lib/bds/tui.ex:1355 +#: lib/bds/tui.ex:1364 #, elixir-autogen, elixir-format msgid "Projects — enter switch · o open existing · esc close" msgstr "Projets — entrée changer · o ouvrir existant · échap fermer" -#: lib/bds/tui.ex:706 +#: lib/bds/tui.ex:715 #, elixir-autogen, elixir-format msgid "Switched to %{name}." msgstr "Passage à %{name}." -#: lib/bds/tui.ex:1383 +#: lib/bds/tui.ex:1392 #, elixir-autogen, elixir-format msgid "Matching folders" msgstr "Dossiers correspondants" -#: lib/bds/tui.ex:1321 +#: lib/bds/tui.ex:1330 #, elixir-autogen, elixir-format msgid "Open Existing Blog — type a folder path · tab complete · enter open · esc back" msgstr "Ouvrir un blog existant — saisir le chemin d'un dossier · tab compléter · entrée ouvrir · échap retour" @@ -3925,12 +3925,12 @@ msgstr "Message de commit requis." msgid "Committed." msgstr "Commit effectué." -#: lib/bds/tui.ex:1165 +#: lib/bds/tui.ex:1174 #, elixir-autogen, elixir-format msgid "Diff (pgup/pgdn scroll)" msgstr "Diff (pgup/pgdn pour défiler)" -#: lib/bds/tui.ex:1548 +#: lib/bds/tui.ex:1557 #, elixir-autogen, elixir-format msgid "No changes." msgstr "Aucune modification." @@ -3941,7 +3941,7 @@ msgid "No diff for this file." msgstr "Pas de diff pour ce fichier." #: lib/bds/tui.ex:388 -#: lib/bds/tui.ex:1152 +#: lib/bds/tui.ex:1161 #, elixir-autogen, elixir-format msgid "Not a git repository." msgstr "Pas un dépôt git." @@ -4031,32 +4031,71 @@ msgstr "Utilisateur SSH" msgid "Show Title" msgstr "Afficher le titre" -#: lib/bds/ui/settings_form.ex:242 +#: lib/bds/ui/settings_form.ex:247 #, elixir-autogen, elixir-format msgid "Theme" msgstr "Thème" -#: lib/bds/tui.ex:1136 +#: lib/bds/tui.ex:1145 #, elixir-autogen, elixir-format msgid "enter edit · ctrl+s save · esc close" msgstr "entrée modifier · ctrl+s enregistrer · échap fermer" -#: lib/bds/tui.ex:1499 +#: lib/bds/tui.ex:1508 #, elixir-autogen, elixir-format msgid "enter edit/toggle/cycle · ctrl+s save · esc close · ctrl+q quit" msgstr "entrée modifier/basculer/parcourir · ctrl+s enregistrer · échap fermer · ctrl+q quitter" -#: lib/bds/ui/settings_form.ex:230 +#: lib/bds/ui/settings_form.ex:235 #, elixir-autogen, elixir-format msgid "not supported yet" msgstr "pas encore pris en charge" -#: lib/bds/tui.ex:1506 +#: lib/bds/tui.ex:1515 #, elixir-autogen, elixir-format msgid "c commit · u pull · s push · enter jump to file · pgup/pgdn scroll diff · 1-7 views · ctrl+q quit" msgstr "c commit · u pull · s push · entrée aller au fichier · pgup/pgdn défiler le diff · 1-7 vues · ctrl+q quitter" -#: lib/bds/tui.ex:1513 +#: lib/bds/tui.ex:1522 #, elixir-autogen, elixir-format msgid "enter open · n new post · 1-7 views · p projects · / search · : commands (:? help) · r refresh · ctrl+q quit" msgstr "entrée ouvrir · n nouvel article · 1-7 vues · p projets · / rechercher · : commandes (:? aide) · r actualiser · ctrl+q quitter" + +#: lib/bds/ui/settings_form.ex:369 +#, elixir-autogen, elixir-format +msgid "CLI tool installed to %{path}" +msgstr "Outil CLI installé dans %{path}" + +#: lib/bds/desktop/shell_live/settings_editor.ex:75 +#: lib/bds/desktop/shell_live/settings_editor.ex:78 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:386 +#: lib/bds/ui/settings_form.ex:223 +#, elixir-autogen, elixir-format +msgid "Command Line Tool" +msgstr "Outil en ligne de commande" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:390 +#: lib/bds/ui/settings_form.ex:224 +#, elixir-autogen, elixir-format +msgid "Install CLI Tool" +msgstr "Installer l'outil CLI" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:387 +#, elixir-autogen, elixir-format +msgid "Install the bds-cli tool to ~/.local/bin; it uses the same settings and cache database as the app" +msgstr "Installe l'outil bds-cli dans ~/.local/bin ; il utilise les mêmes paramètres et la même base de données de cache que l'application" + +#: lib/bds/ui/settings_form.ex:375 +#, elixir-autogen, elixir-format +msgid "Installing the CLI tool failed: %{reason}" +msgstr "L'installation de l'outil CLI a échoué : %{reason}" + +#: lib/bds/ui/settings_form.ex:372 +#, elixir-autogen, elixir-format +msgid "The CLI tool can only be installed from the packaged application" +msgstr "L'outil CLI ne peut être installé que depuis l'application empaquetée" + +#: lib/bds/ui/settings_form.ex:379 +#, elixir-autogen, elixir-format +msgid "Unknown settings action" +msgstr "Action de paramètres inconnue" diff --git a/priv/gettext/it/LC_MESSAGES/ui.po b/priv/gettext/it/LC_MESSAGES/ui.po index 77bd480..08ec7e8 100644 --- a/priv/gettext/it/LC_MESSAGES/ui.po +++ b/priv/gettext/it/LC_MESSAGES/ui.po @@ -80,13 +80,13 @@ msgstr "Impostazioni IA" #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:42 #: lib/bds/desktop/shell_live/overlay_manager.ex:72 -#: lib/bds/desktop/shell_live/post_editor.ex:920 +#: lib/bds/desktop/shell_live/post_editor.ex:906 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 -#: lib/bds/tui.ex:937 -#: lib/bds/tui.ex:940 -#: lib/bds/tui.ex:943 -#: lib/bds/tui.ex:951 -#: lib/bds/tui.ex:1753 +#: lib/bds/tui.ex:946 +#: lib/bds/tui.ex:949 +#: lib/bds/tui.ex:952 +#: lib/bds/tui.ex:960 +#: lib/bds/tui.ex:1762 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "Suggerimenti IA" @@ -349,8 +349,8 @@ msgstr "Automatico" #: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:557 #: lib/bds/desktop/shell_live/overlay_manager.ex:73 -#: lib/bds/desktop/shell_live/post_editor.ex:765 -#: lib/bds/desktop/shell_live/post_editor.ex:814 +#: lib/bds/desktop/shell_live/post_editor.ex:752 +#: lib/bds/desktop/shell_live/post_editor.ex:801 #, elixir-autogen, elixir-format msgid "Automatic AI actions stay gated by airplane mode." msgstr "Le azioni IA automatiche restano bloccate dalla modalità aereo." @@ -574,8 +574,8 @@ msgstr "Chiudi scheda" msgid "Collapse unchanged diff hunks" msgstr "Comprimi i blocchi diff invariati" -#: lib/bds/desktop/shell_live/overlay_manager.ex:422 -#: lib/bds/tui.ex:1849 +#: lib/bds/desktop/shell_live/overlay_manager.ex:423 +#: lib/bds/tui.ex:1858 #, elixir-autogen, elixir-format msgid "Command completed" msgstr "Comando completato" @@ -593,7 +593,7 @@ msgstr "Conferma" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 -#: lib/bds/tui.ex:1219 +#: lib/bds/tui.ex:1228 #: lib/bds/ui/settings_form.ex:137 #: lib/bds/ui/sidebar.ex:801 #, elixir-autogen, elixir-format @@ -751,7 +751,7 @@ msgstr "Modalità di modifica predefinita e presentazione dei diff" msgid "Delete" msgstr "Elimina" -#: lib/bds/desktop/shell_live/overlay_manager.ex:278 +#: lib/bds/desktop/shell_live/overlay_manager.ex:279 #, elixir-autogen, elixir-format msgid "Delete Media" msgstr "Elimina media" @@ -798,9 +798,9 @@ msgstr "Rileva" #: lib/bds/desktop/shell_live/media_editor.ex:214 #: lib/bds/desktop/shell_live/media_editor.ex:220 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:59 -#: lib/bds/desktop/shell_live/post_editor.ex:764 -#: lib/bds/desktop/shell_live/post_editor.ex:793 -#: lib/bds/desktop/shell_live/post_editor.ex:799 +#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:780 +#: lib/bds/desktop/shell_live/post_editor.ex:786 #, elixir-autogen, elixir-format msgid "Detect Language" msgstr "Rileva lingua" @@ -1040,7 +1040,7 @@ msgid "Filename" msgstr "Nome file" #: lib/bds/desktop/menu_bar.ex:254 -#: lib/bds/tui.ex:1813 +#: lib/bds/tui.ex:1822 #, elixir-autogen, elixir-format msgid "Fill Missing Translations" msgstr "Completa traduzioni mancanti" @@ -1051,7 +1051,7 @@ msgid "Find" msgstr "Trova" #: lib/bds/desktop/menu_bar.ex:255 -#: lib/bds/tui.ex:1816 +#: lib/bds/tui.ex:1825 #, elixir-autogen, elixir-format msgid "Find Duplicate Posts" msgstr "Trova post duplicati" @@ -1078,14 +1078,14 @@ msgid "Gallery" msgstr "Galleria" #: lib/bds/desktop/menu_bar.ex:256 -#: lib/bds/tui.ex:1797 +#: lib/bds/tui.ex:1806 #, elixir-autogen, elixir-format msgid "Generate Site" msgstr "Genera sito" #: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live/socket_state.ex:109 -#: lib/bds/tui.ex:1662 +#: lib/bds/tui.ex:1671 #: lib/bds/ui/sidebar.ex:826 #, elixir-autogen, elixir-format msgid "Git" @@ -1128,7 +1128,7 @@ msgstr "Host" #: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 -#: lib/bds/desktop/shell_live/post_editor.ex:1038 +#: lib/bds/desktop/shell_live/post_editor.ex:1024 #, elixir-autogen, elixir-format msgid "Idle" msgstr "Inattivo" @@ -1299,7 +1299,7 @@ msgid "Language" msgstr "Lingua" #: lib/bds/desktop/shell_live/media_editor.ex:221 -#: lib/bds/desktop/shell_live/post_editor.ex:800 +#: lib/bds/desktop/shell_live/post_editor.ex:787 #, elixir-autogen, elixir-format msgid "Language detection failed." msgstr "Rilevamento della lingua non riuscito." @@ -1345,7 +1345,7 @@ msgstr "Carica altri" #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:50 #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:57 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:351 -#: lib/bds/ui/settings_form.ex:234 +#: lib/bds/ui/settings_form.ex:239 #: lib/bds/ui/sidebar.ex:816 #, elixir-autogen, elixir-format msgid "MCP" @@ -1386,7 +1386,7 @@ msgstr "Mappa a..." msgid "Mapped" msgstr "Mappato" -#: lib/bds/desktop/shell_live/post_editor.ex:1041 +#: lib/bds/desktop/shell_live/post_editor.ex:1027 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:123 #, elixir-autogen, elixir-format msgid "Markdown" @@ -1404,9 +1404,9 @@ msgstr "Numero massimo di post per pagina" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 -#: lib/bds/tui.ex:1657 -#: lib/bds/tui.ex:1878 -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1666 +#: lib/bds/tui.ex:1887 +#: lib/bds/tui.ex:1890 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:578 @@ -1452,8 +1452,8 @@ msgstr "Metadati" #: lib/bds/tui.ex:278 #: lib/bds/tui.ex:284 #: lib/bds/tui.ex:295 -#: lib/bds/tui.ex:1412 -#: lib/bds/tui.ex:1794 +#: lib/bds/tui.ex:1421 +#: lib/bds/tui.ex:1803 #: lib/bds/ui/registry.ex:111 #, elixir-autogen, elixir-format msgid "Metadata Diff" @@ -1787,8 +1787,8 @@ msgid "Open Data Folder" msgstr "Apri cartella dati" #: lib/bds/desktop/shell_live/index.html.heex:644 -#: lib/bds/tui.ex:792 -#: lib/bds/tui.ex:797 +#: lib/bds/tui.ex:801 +#: lib/bds/tui.ex:806 #, elixir-autogen, elixir-format msgid "Open Existing Blog" msgstr "Apri blog esistente" @@ -1800,7 +1800,7 @@ msgid "Open Settings" msgstr "Apri Impostazioni" #: lib/bds/desktop/menu_bar.ex:217 -#: lib/bds/tui.ex:1818 +#: lib/bds/tui.ex:1827 #, elixir-autogen, elixir-format msgid "Open in Browser" msgstr "Apri nel browser" @@ -1896,16 +1896,16 @@ msgid "Persist the detected language for this media item" msgstr "Salva la lingua rilevata per questo media" #: lib/bds/desktop/shell_live/misc_editor.ex:733 -#: lib/bds/desktop/shell_live/post_editor.ex:539 -#: lib/bds/desktop/shell_live/post_editor.ex:543 -#: lib/bds/desktop/shell_live/post_editor.ex:582 -#: lib/bds/desktop/shell_live/post_editor.ex:586 -#: lib/bds/desktop/shell_live/post_editor.ex:624 -#: lib/bds/desktop/shell_live/post_editor.ex:639 -#: lib/bds/desktop/shell_live/post_editor.ex:668 -#: lib/bds/desktop/shell_live/post_editor.ex:671 -#: lib/bds/desktop/shell_live/post_editor.ex:751 -#: lib/bds/desktop/shell_live/post_editor.ex:754 +#: lib/bds/desktop/shell_live/post_editor.ex:526 +#: lib/bds/desktop/shell_live/post_editor.ex:530 +#: lib/bds/desktop/shell_live/post_editor.ex:569 +#: lib/bds/desktop/shell_live/post_editor.ex:573 +#: lib/bds/desktop/shell_live/post_editor.ex:611 +#: lib/bds/desktop/shell_live/post_editor.ex:626 +#: lib/bds/desktop/shell_live/post_editor.ex:655 +#: lib/bds/desktop/shell_live/post_editor.ex:658 +#: lib/bds/desktop/shell_live/post_editor.ex:738 +#: lib/bds/desktop/shell_live/post_editor.ex:741 #: lib/bds/desktop/shell_live/sidebar_components.ex:651 #: lib/bds/desktop/shell_live/sidebar_delete.ex:174 #: lib/bds/ui/registry.ex:99 @@ -1936,12 +1936,12 @@ msgstr "Template del post" msgid "Post is marked as do-not-translate but has translations" msgstr "Il post è contrassegnato come non-tradurre ma ha traduzioni" -#: lib/bds/desktop/shell_live/post_editor.ex:582 +#: lib/bds/desktop/shell_live/post_editor.ex:569 #, elixir-autogen, elixir-format msgid "Post published" msgstr "Articolo pubblicato" -#: lib/bds/desktop/shell_live/post_editor.ex:539 +#: lib/bds/desktop/shell_live/post_editor.ex:526 #, elixir-autogen, elixir-format msgid "Post saved" msgstr "Articolo salvato" @@ -1949,8 +1949,8 @@ msgstr "Articolo salvato" #: lib/bds/desktop/menu_bar.ex:233 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 -#: lib/bds/tui.ex:1656 -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1665 +#: lib/bds/tui.ex:1680 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1967,7 +1967,7 @@ msgstr "Articoli (%{count})" msgid "Preferences" msgstr "Preferenze" -#: lib/bds/desktop/shell_live/post_editor.ex:1042 +#: lib/bds/desktop/shell_live/post_editor.ex:1028 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:124 #, elixir-autogen, elixir-format msgid "Preview" @@ -2010,8 +2010,8 @@ msgstr "Progetto e pubblicazione" #: lib/bds/desktop/shell_live/chat_surface.ex:15 #: lib/bds/desktop/shell_live/index.html.heex:623 -#: lib/bds/tui.ex:705 -#: lib/bds/tui.ex:710 +#: lib/bds/tui.ex:714 +#: lib/bds/tui.ex:719 #, elixir-autogen, elixir-format msgid "Projects" msgstr "Progetti" @@ -2040,7 +2040,7 @@ msgid "Publish Selected" msgstr "Pubblica selezionati" #: lib/bds/desktop/shell_data.ex:165 -#: lib/bds/desktop/shell_live/post_editor.ex:1036 +#: lib/bds/desktop/shell_live/post_editor.ex:1022 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 #, elixir-autogen, elixir-format @@ -2079,15 +2079,15 @@ msgid "Ready to import:" msgstr "Pronto per importare:" #: lib/bds/desktop/menu_bar.ex:248 -#: lib/bds/tui.ex:789 -#: lib/bds/tui.ex:1798 +#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:1807 #, elixir-autogen, elixir-format msgid "Rebuild Database" msgstr "Ricostruisci database" #: lib/bds/desktop/menu_bar.ex:250 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 -#: lib/bds/tui.ex:1802 +#: lib/bds/tui.ex:1811 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "Ricostruisci indice embeddings" @@ -2145,7 +2145,7 @@ msgid "Refresh Translation" msgstr "Aggiorna traduzione" #: lib/bds/desktop/menu_bar.ex:252 -#: lib/bds/tui.ex:1805 +#: lib/bds/tui.ex:1814 #, elixir-autogen, elixir-format msgid "Regenerate Calendar" msgstr "Rigenera calendario" @@ -2156,7 +2156,7 @@ msgid "Regenerate Missing Thumbnails" msgstr "Rigenera miniature mancanti" #: lib/bds/desktop/menu_bar.ex:249 -#: lib/bds/tui.ex:1799 +#: lib/bds/tui.ex:1808 #, elixir-autogen, elixir-format msgid "Reindex Text" msgstr "Reindicizza testo" @@ -2240,7 +2240,7 @@ msgstr "Risoluzione" msgid "Result" msgstr "Risultato" -#: lib/bds/desktop/shell_live/post_editor.ex:1037 +#: lib/bds/desktop/shell_live/post_editor.ex:1023 #, elixir-autogen, elixir-format msgid "Reverted" msgstr "Ripristinato" @@ -2293,7 +2293,7 @@ msgid "Save Translation" msgstr "Salva traduzione" #: lib/bds/desktop/shell_live/media_editor.ex:730 -#: lib/bds/desktop/shell_live/post_editor.ex:1035 +#: lib/bds/desktop/shell_live/post_editor.ex:1021 #, elixir-autogen, elixir-format msgid "Saved" msgstr "Salvato" @@ -2344,7 +2344,7 @@ msgstr "Le capacità di scripting sono configurate a livello applicativo nella r #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 -#: lib/bds/tui.ex:1659 +#: lib/bds/tui.ex:1668 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2451,7 +2451,7 @@ msgstr "Somiglianza semantica" #: lib/bds/desktop/shell_live/settings_editor/project_settings.ex:60 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:11 -#: lib/bds/tui.ex:1661 +#: lib/bds/tui.ex:1670 #: lib/bds/ui/registry.ex:86 #: lib/bds/ui/registry.ex:101 #: lib/bds/ui/sidebar.ex:795 @@ -2477,7 +2477,7 @@ msgstr "Sito" #: lib/bds/desktop/shell_live/misc_editor.ex:412 #: lib/bds/tui.ex:311 #: lib/bds/tui.ex:313 -#: lib/bds/tui.ex:1417 +#: lib/bds/tui.ex:1426 #: lib/bds/ui/registry.ex:125 #, elixir-autogen, elixir-format msgid "Site Validation" @@ -2541,7 +2541,7 @@ msgstr "Ferma" #: lib/bds/desktop/shell_live/settings_editor/style_editor.ex:76 #: lib/bds/desktop/shell_live/settings_editor_html/style_editor.html.heex:3 #: lib/bds/ui/registry.ex:102 -#: lib/bds/ui/settings_form.ex:241 +#: lib/bds/ui/settings_form.ex:246 #: lib/bds/ui/sidebar.ex:817 #, elixir-autogen, elixir-format msgid "Style" @@ -2600,7 +2600,7 @@ msgstr "Nome del tag" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 -#: lib/bds/tui.ex:1660 +#: lib/bds/tui.ex:1669 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2612,7 +2612,7 @@ msgstr "Tag" #: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "Tasks" msgstr "Attività" @@ -2658,7 +2658,7 @@ msgstr "La sintassi del template è valida" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 -#: lib/bds/tui.ex:1658 +#: lib/bds/tui.ex:1667 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2772,7 +2772,7 @@ msgstr "Attiva/disattiva barra laterale" #: lib/bds/desktop/shell_live/media_editor.ex:363 #: lib/bds/desktop/shell_live/media_editor.ex:556 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:76 -#: lib/bds/desktop/shell_live/post_editor.ex:813 +#: lib/bds/desktop/shell_live/post_editor.ex:800 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:60 #, elixir-autogen, elixir-format msgid "Translate" @@ -2849,7 +2849,7 @@ msgstr "Scollega dall'articolo" #: lib/bds/desktop/shell_live/media_editor.ex:729 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:10 -#: lib/bds/desktop/shell_live/post_editor.ex:1034 +#: lib/bds/desktop/shell_live/post_editor.ex:1020 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:7 #, elixir-autogen, elixir-format msgid "Unsaved" @@ -2882,7 +2882,7 @@ msgid "Updated URLs" msgstr "URL aggiornati" #: lib/bds/desktop/menu_bar.ex:259 -#: lib/bds/tui.ex:1817 +#: lib/bds/tui.ex:1826 #, elixir-autogen, elixir-format msgid "Upload Site" msgstr "Carica sito" @@ -2910,13 +2910,13 @@ msgid "Validate" msgstr "Valida" #: lib/bds/desktop/menu_bar.ex:258 -#: lib/bds/tui.ex:1795 +#: lib/bds/tui.ex:1804 #, elixir-autogen, elixir-format msgid "Validate Site" msgstr "Valida sito" #: lib/bds/desktop/menu_bar.ex:253 -#: lib/bds/tui.ex:1808 +#: lib/bds/tui.ex:1817 #, elixir-autogen, elixir-format msgid "Validate Translations" msgstr "Valida traduzioni" @@ -3350,12 +3350,12 @@ msgstr "Archivia" msgid "Move this post to the archive" msgstr "Sposta questo articolo nell'archivio" -#: lib/bds/desktop/shell_live/post_editor.ex:668 +#: lib/bds/desktop/shell_live/post_editor.ex:655 #, elixir-autogen, elixir-format msgid "Post archived" msgstr "Articolo archiviato" -#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:738 #, elixir-autogen, elixir-format msgid "Post unarchived" msgstr "Articolo ripristinato" @@ -3397,7 +3397,7 @@ msgid "Commit" msgstr "Commit" #: lib/bds/desktop/shell_live/sidebar_components.ex:555 -#: lib/bds/tui.ex:1239 +#: lib/bds/tui.ex:1248 #, elixir-autogen, elixir-format msgid "Commit message" msgstr "Messaggio di commit" @@ -3421,7 +3421,7 @@ msgid "Fetch" msgstr "Recupera" #: lib/bds/desktop/shell_live/sidebar_components.ex:586 -#: lib/bds/tui.ex:1074 +#: lib/bds/tui.ex:1083 #, elixir-autogen, elixir-format msgid "History" msgstr "Cronologia" @@ -3469,7 +3469,7 @@ msgstr "Pulisci LFS" #: lib/bds/desktop/shell_live/git_handler.ex:17 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 -#: lib/bds/tui.ex:957 +#: lib/bds/tui.ex:966 #, elixir-autogen, elixir-format msgid "Pull" msgstr "Pull" @@ -3477,7 +3477,7 @@ msgstr "Pull" #: lib/bds/desktop/shell_live/git_handler.ex:18 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 -#: lib/bds/tui.ex:958 +#: lib/bds/tui.ex:967 #, elixir-autogen, elixir-format msgid "Push" msgstr "Push" @@ -3543,18 +3543,18 @@ msgstr "Blogmark" msgid "Open a project before importing a blogmark." msgstr "Apri un progetto prima di importare un blogmark." -#: lib/bds/desktop/shell_live/post_editor.ex:694 +#: lib/bds/desktop/shell_live/post_editor.ex:681 #, elixir-autogen, elixir-format msgid "Added %{name}" msgstr "%{name} aggiunto" -#: lib/bds/desktop/shell_live/post_editor.ex:701 +#: lib/bds/desktop/shell_live/post_editor.ex:688 #, elixir-autogen, elixir-format msgid "Failed to import %{path}: %{reason}" msgstr "Impossibile importare %{path}: %{reason}" -#: lib/bds/desktop/shell_live/post_editor.ex:693 -#: lib/bds/desktop/shell_live/post_editor.ex:700 +#: lib/bds/desktop/shell_live/post_editor.ex:680 +#: lib/bds/desktop/shell_live/post_editor.ex:687 #, elixir-autogen, elixir-format msgid "Insert Image" msgstr "Inserisci immagine" @@ -3569,7 +3569,7 @@ msgstr "Bookmarklet copiato negli appunti" msgid "Copy Bookmarklet to Clipboard" msgstr "Copia bookmarklet negli appunti" -#: lib/bds/desktop/shell_live/overlay_manager.ex:300 +#: lib/bds/desktop/shell_live/overlay_manager.ex:301 #, elixir-autogen, elixir-format msgid "Delete Tag" msgstr "Elimina tag" @@ -3590,7 +3590,7 @@ msgid "Suggested tags" msgstr "Tag suggeriti" #: lib/bds/desktop/menu_bar.ex:257 -#: lib/bds/tui.ex:1796 +#: lib/bds/tui.ex:1805 #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "Forza la rigenerazione del sito" @@ -3687,12 +3687,12 @@ msgstr "OK" 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." -#: lib/bds/tui.ex:1754 +#: lib/bds/tui.ex:1763 #, elixir-autogen, elixir-format msgid "AI is unavailable in airplane mode without a local endpoint." msgstr "L'IA non è disponibile in modalità aereo senza un endpoint locale." -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1890 #, elixir-autogen, elixir-format msgid "Could not load this file." msgstr "Impossibile caricare questo file." @@ -3702,68 +3702,68 @@ msgstr "Impossibile caricare questo file." msgid "Editing this item is not available in the terminal UI yet." msgstr "La modifica di questo elemento non è ancora disponibile nell'interfaccia del terminale." -#: lib/bds/tui.ex:940 +#: lib/bds/tui.ex:949 #, elixir-autogen, elixir-format msgid "No suggestions returned." msgstr "Nessun suggerimento ricevuto." -#: lib/bds/tui.ex:1878 +#: lib/bds/tui.ex:1887 #, elixir-autogen, elixir-format msgid "Only images can be previewed." msgstr "Solo le immagini possono essere visualizzate in anteprima." -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1680 #, elixir-autogen, elixir-format msgid "Post not found." msgstr "Post non trovato." -#: lib/bds/tui.ex:1174 +#: lib/bds/tui.ex:1183 #, elixir-autogen, elixir-format msgid "Press enter to preview %{name}." msgstr "Premi Invio per l'anteprima di %{name}." -#: lib/bds/tui.ex:1724 +#: lib/bds/tui.ex:1733 #, elixir-autogen, elixir-format msgid "Published." msgstr "Pubblicato." -#: lib/bds/tui.ex:1740 +#: lib/bds/tui.ex:1749 #, elixir-autogen, elixir-format msgid "Save before switching languages." msgstr "Salva prima di cambiare lingua." -#: lib/bds/tui.ex:524 -#: lib/bds/tui.ex:1725 +#: lib/bds/tui.ex:533 +#: lib/bds/tui.ex:1734 #, elixir-autogen, elixir-format msgid "Saved." msgstr "Salvato." -#: lib/bds/tui.ex:1177 +#: lib/bds/tui.ex:1186 #, elixir-autogen, elixir-format msgid "Select an entry and press enter to open it." msgstr "Seleziona una voce e premi Invio per aprirla." -#: lib/bds/tui.ex:937 +#: lib/bds/tui.ex:946 #, elixir-autogen, elixir-format msgid "Suggestions applied." msgstr "Suggerimenti applicati." -#: lib/bds/tui.ex:1189 +#: lib/bds/tui.ex:1198 #, elixir-autogen, elixir-format msgid "Title (ctrl+t to edit)" msgstr "Titolo (ctrl+t per modificare)" -#: lib/bds/tui.ex:1188 +#: lib/bds/tui.ex:1197 #, elixir-autogen, elixir-format msgid "Title (editing — enter to confirm)" msgstr "Titolo (modifica — Invio per confermare)" -#: lib/bds/tui.ex:1241 +#: lib/bds/tui.ex:1250 #, elixir-autogen, elixir-format msgid "Working…" msgstr "Elaborazione…" -#: lib/bds/tui.ex:1263 +#: lib/bds/tui.ex:1272 #, elixir-autogen, elixir-format msgid "esc to close" msgstr "esc per chiudere" @@ -3800,52 +3800,52 @@ msgstr "Indirizzo del server (user@host o user@host:port), autenticazione a chia msgid "Use the form user@host or user@host:port." msgstr "Usa il formato user@host o user@host:port." -#: lib/bds/tui.ex:1208 +#: lib/bds/tui.ex:1217 #, elixir-autogen, elixir-format msgid "Preview (ctrl+e to edit)" msgstr "Anteprima (ctrl+e per modificare)" -#: lib/bds/tui.ex:1520 +#: lib/bds/tui.ex:1529 #, elixir-autogen, elixir-format msgid "ctrl+s save · ctrl+p publish · ctrl+e preview · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" msgstr "ctrl+s salva · ctrl+p pubblica · ctrl+e anteprima · ctrl+t titolo · ctrl+l lingua · ctrl+g IA · esc indietro" -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "All tasks finished." msgstr "Tutte le attività sono terminate." -#: lib/bds/tui.ex:1289 +#: lib/bds/tui.ex:1298 #, elixir-autogen, elixir-format msgid "Commands — esc to close" msgstr "Comandi — esc per chiudere" -#: lib/bds/tui.ex:814 +#: lib/bds/tui.ex:823 #, elixir-autogen, elixir-format msgid "Unknown command." msgstr "Comando sconosciuto." -#: lib/bds/tui.ex:1279 +#: lib/bds/tui.ex:1288 #, elixir-autogen, elixir-format msgid "[report/apply in GUI]" msgstr "[report/applicazione nella GUI]" -#: lib/bds/tui.ex:1428 +#: lib/bds/tui.ex:1437 #, elixir-autogen, elixir-format msgid "%{diffs} differences · %{orphans} orphan files" msgstr "%{diffs} differenze · %{orphans} file orfani" -#: lib/bds/tui.ex:1460 +#: lib/bds/tui.ex:1469 #, elixir-autogen, elixir-format msgid "Expected %{expected} · Existing %{existing}" msgstr "Attesi %{expected} · Esistenti %{existing}" -#: lib/bds/tui.ex:1477 +#: lib/bds/tui.ex:1486 #, elixir-autogen, elixir-format msgid "Extra files (will be removed):" msgstr "File in eccesso (verranno rimossi):" -#: lib/bds/tui.ex:1473 +#: lib/bds/tui.ex:1482 #, elixir-autogen, elixir-format msgid "Missing pages (will be rendered):" msgstr "Pagine mancanti (verranno generate):" @@ -3860,57 +3860,57 @@ msgstr "Niente da applicare." msgid "Nothing to repair." msgstr "Niente da riparare." -#: lib/bds/tui.ex:1451 +#: lib/bds/tui.ex:1460 #, elixir-autogen, elixir-format msgid "Orphan files (not in database):" msgstr "File orfani (non presenti nel database):" -#: lib/bds/tui.ex:1467 +#: lib/bds/tui.ex:1476 #, elixir-autogen, elixir-format msgid "Sitemap changed." msgstr "Sitemap modificata." -#: lib/bds/tui.ex:1481 +#: lib/bds/tui.ex:1490 #, elixir-autogen, elixir-format msgid "Updated posts (will be re-rendered):" msgstr "Post aggiornati (verranno rigenerati):" -#: lib/bds/tui.ex:1418 +#: lib/bds/tui.ex:1427 #, elixir-autogen, elixir-format msgid "enter apply changes · esc close" msgstr "invio applica le modifiche · esc chiudi" -#: lib/bds/tui.ex:1413 +#: lib/bds/tui.ex:1422 #, elixir-autogen, elixir-format msgid "enter repair all from files · esc close" msgstr "invio ripara tutto dai file · esc chiudi" -#: lib/bds/tui.ex:779 +#: lib/bds/tui.ex:788 #, elixir-autogen, elixir-format msgid "Imported Blog" msgstr "Blog importato" -#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:807 #, elixir-autogen, elixir-format msgid "Not a folder: %{path}" msgstr "Non è una cartella: %{path}" -#: lib/bds/tui.ex:1355 +#: lib/bds/tui.ex:1364 #, elixir-autogen, elixir-format msgid "Projects — enter switch · o open existing · esc close" msgstr "Progetti — invio cambia · o apri esistente · esc chiudi" -#: lib/bds/tui.ex:706 +#: lib/bds/tui.ex:715 #, elixir-autogen, elixir-format msgid "Switched to %{name}." msgstr "Passato a %{name}." -#: lib/bds/tui.ex:1383 +#: lib/bds/tui.ex:1392 #, elixir-autogen, elixir-format msgid "Matching folders" msgstr "Cartelle corrispondenti" -#: lib/bds/tui.ex:1321 +#: lib/bds/tui.ex:1330 #, elixir-autogen, elixir-format msgid "Open Existing Blog — type a folder path · tab complete · enter open · esc back" msgstr "Apri blog esistente — digita il percorso di una cartella · tab completa · invio apri · esc indietro" @@ -3925,12 +3925,12 @@ msgstr "Messaggio di commit obbligatorio." msgid "Committed." msgstr "Commit eseguito." -#: lib/bds/tui.ex:1165 +#: lib/bds/tui.ex:1174 #, elixir-autogen, elixir-format msgid "Diff (pgup/pgdn scroll)" msgstr "Diff (pgup/pgdn per scorrere)" -#: lib/bds/tui.ex:1548 +#: lib/bds/tui.ex:1557 #, elixir-autogen, elixir-format msgid "No changes." msgstr "Nessuna modifica." @@ -3941,7 +3941,7 @@ msgid "No diff for this file." msgstr "Nessun diff per questo file." #: lib/bds/tui.ex:388 -#: lib/bds/tui.ex:1152 +#: lib/bds/tui.ex:1161 #, elixir-autogen, elixir-format msgid "Not a git repository." msgstr "Non è un repository git." @@ -4031,32 +4031,71 @@ msgstr "Utente SSH" msgid "Show Title" msgstr "Mostra titolo" -#: lib/bds/ui/settings_form.ex:242 +#: lib/bds/ui/settings_form.ex:247 #, elixir-autogen, elixir-format msgid "Theme" msgstr "Tema" -#: lib/bds/tui.ex:1136 +#: lib/bds/tui.ex:1145 #, elixir-autogen, elixir-format msgid "enter edit · ctrl+s save · esc close" msgstr "invio modifica · ctrl+s salva · esc chiudi" -#: lib/bds/tui.ex:1499 +#: lib/bds/tui.ex:1508 #, elixir-autogen, elixir-format msgid "enter edit/toggle/cycle · ctrl+s save · esc close · ctrl+q quit" msgstr "invio modifica/attiva/cicla · ctrl+s salva · esc chiudi · ctrl+q esci" -#: lib/bds/ui/settings_form.ex:230 +#: lib/bds/ui/settings_form.ex:235 #, elixir-autogen, elixir-format msgid "not supported yet" msgstr "non ancora supportato" -#: lib/bds/tui.ex:1506 +#: lib/bds/tui.ex:1515 #, elixir-autogen, elixir-format msgid "c commit · u pull · s push · enter jump to file · pgup/pgdn scroll diff · 1-7 views · ctrl+q quit" msgstr "c commit · u pull · s push · invio vai al file · pgup/pgdn scorri il diff · 1-7 viste · ctrl+q esci" -#: lib/bds/tui.ex:1513 +#: lib/bds/tui.ex:1522 #, elixir-autogen, elixir-format msgid "enter open · n new post · 1-7 views · p projects · / search · : commands (:? help) · r refresh · ctrl+q quit" msgstr "invio apri · n nuovo post · 1-7 viste · p progetti · / cerca · : comandi (:? aiuto) · r aggiorna · ctrl+q esci" + +#: lib/bds/ui/settings_form.ex:369 +#, elixir-autogen, elixir-format +msgid "CLI tool installed to %{path}" +msgstr "Strumento CLI installato in %{path}" + +#: lib/bds/desktop/shell_live/settings_editor.ex:75 +#: lib/bds/desktop/shell_live/settings_editor.ex:78 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:386 +#: lib/bds/ui/settings_form.ex:223 +#, elixir-autogen, elixir-format +msgid "Command Line Tool" +msgstr "Strumento a riga di comando" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:390 +#: lib/bds/ui/settings_form.ex:224 +#, elixir-autogen, elixir-format +msgid "Install CLI Tool" +msgstr "Installa lo strumento CLI" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:387 +#, elixir-autogen, elixir-format +msgid "Install the bds-cli tool to ~/.local/bin; it uses the same settings and cache database as the app" +msgstr "Installa lo strumento bds-cli in ~/.local/bin; utilizza le stesse impostazioni e lo stesso database di cache dell'app" + +#: lib/bds/ui/settings_form.ex:375 +#, elixir-autogen, elixir-format +msgid "Installing the CLI tool failed: %{reason}" +msgstr "Installazione dello strumento CLI non riuscita: %{reason}" + +#: lib/bds/ui/settings_form.ex:372 +#, elixir-autogen, elixir-format +msgid "The CLI tool can only be installed from the packaged application" +msgstr "Lo strumento CLI può essere installato solo dall'applicazione pacchettizzata" + +#: lib/bds/ui/settings_form.ex:379 +#, elixir-autogen, elixir-format +msgid "Unknown settings action" +msgstr "Azione delle impostazioni sconosciuta" diff --git a/priv/gettext/ui.pot b/priv/gettext/ui.pot index 2fc87c1..bc039e2 100644 --- a/priv/gettext/ui.pot +++ b/priv/gettext/ui.pot @@ -93,13 +93,13 @@ msgstr "" #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:42 #: lib/bds/desktop/shell_live/overlay_manager.ex:72 -#: lib/bds/desktop/shell_live/post_editor.ex:920 +#: lib/bds/desktop/shell_live/post_editor.ex:906 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:43 -#: lib/bds/tui.ex:937 -#: lib/bds/tui.ex:940 -#: lib/bds/tui.ex:943 -#: lib/bds/tui.ex:951 -#: lib/bds/tui.ex:1753 +#: lib/bds/tui.ex:946 +#: lib/bds/tui.ex:949 +#: lib/bds/tui.ex:952 +#: lib/bds/tui.ex:960 +#: lib/bds/tui.ex:1762 #, elixir-autogen, elixir-format msgid "AI Suggestions" msgstr "" @@ -362,8 +362,8 @@ msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:364 #: lib/bds/desktop/shell_live/media_editor.ex:557 #: lib/bds/desktop/shell_live/overlay_manager.ex:73 -#: lib/bds/desktop/shell_live/post_editor.ex:765 -#: lib/bds/desktop/shell_live/post_editor.ex:814 +#: lib/bds/desktop/shell_live/post_editor.ex:752 +#: lib/bds/desktop/shell_live/post_editor.ex:801 #, elixir-autogen, elixir-format msgid "Automatic AI actions stay gated by airplane mode." msgstr "" @@ -587,8 +587,8 @@ msgstr "" msgid "Collapse unchanged diff hunks" msgstr "" -#: lib/bds/desktop/shell_live/overlay_manager.ex:422 -#: lib/bds/tui.ex:1849 +#: lib/bds/desktop/shell_live/overlay_manager.ex:423 +#: lib/bds/tui.ex:1858 #, elixir-autogen, elixir-format msgid "Command completed" msgstr "" @@ -606,7 +606,7 @@ msgstr "" #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:375 #: lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex:34 #: lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex:32 -#: lib/bds/tui.ex:1219 +#: lib/bds/tui.ex:1228 #: lib/bds/ui/settings_form.ex:137 #: lib/bds/ui/sidebar.ex:801 #, elixir-autogen, elixir-format @@ -764,7 +764,7 @@ msgstr "" msgid "Delete" msgstr "" -#: lib/bds/desktop/shell_live/overlay_manager.ex:278 +#: lib/bds/desktop/shell_live/overlay_manager.ex:279 #, elixir-autogen, elixir-format msgid "Delete Media" msgstr "" @@ -811,9 +811,9 @@ msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:214 #: lib/bds/desktop/shell_live/media_editor.ex:220 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:59 -#: lib/bds/desktop/shell_live/post_editor.ex:764 -#: lib/bds/desktop/shell_live/post_editor.ex:793 -#: lib/bds/desktop/shell_live/post_editor.ex:799 +#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:780 +#: lib/bds/desktop/shell_live/post_editor.ex:786 #, elixir-autogen, elixir-format msgid "Detect Language" msgstr "" @@ -1053,7 +1053,7 @@ msgid "Filename" msgstr "" #: lib/bds/desktop/menu_bar.ex:254 -#: lib/bds/tui.ex:1813 +#: lib/bds/tui.ex:1822 #, elixir-autogen, elixir-format msgid "Fill Missing Translations" msgstr "" @@ -1064,7 +1064,7 @@ msgid "Find" msgstr "" #: lib/bds/desktop/menu_bar.ex:255 -#: lib/bds/tui.ex:1816 +#: lib/bds/tui.ex:1825 #, elixir-autogen, elixir-format msgid "Find Duplicate Posts" msgstr "" @@ -1091,14 +1091,14 @@ msgid "Gallery" msgstr "" #: lib/bds/desktop/menu_bar.ex:256 -#: lib/bds/tui.ex:1797 +#: lib/bds/tui.ex:1806 #, elixir-autogen, elixir-format msgid "Generate Site" msgstr "" #: lib/bds/desktop/shell_live.ex:792 #: lib/bds/desktop/shell_live/socket_state.ex:109 -#: lib/bds/tui.ex:1662 +#: lib/bds/tui.ex:1671 #: lib/bds/ui/sidebar.ex:826 #, elixir-autogen, elixir-format msgid "Git" @@ -1141,7 +1141,7 @@ msgstr "" #: lib/bds/desktop/shell_data.ex:100 #: lib/bds/desktop/shell_live/index.html.heex:657 #: lib/bds/desktop/shell_live/media_editor.ex:731 -#: lib/bds/desktop/shell_live/post_editor.ex:1038 +#: lib/bds/desktop/shell_live/post_editor.ex:1024 #, elixir-autogen, elixir-format msgid "Idle" msgstr "" @@ -1312,7 +1312,7 @@ msgid "Language" msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:221 -#: lib/bds/desktop/shell_live/post_editor.ex:800 +#: lib/bds/desktop/shell_live/post_editor.ex:787 #, elixir-autogen, elixir-format msgid "Language detection failed." msgstr "" @@ -1358,7 +1358,7 @@ msgstr "" #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:50 #: lib/bds/desktop/shell_live/settings_editor/mcp_config.ex:57 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:351 -#: lib/bds/ui/settings_form.ex:234 +#: lib/bds/ui/settings_form.ex:239 #: lib/bds/ui/sidebar.ex:816 #, elixir-autogen, elixir-format msgid "MCP" @@ -1399,7 +1399,7 @@ msgstr "" msgid "Mapped" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:1041 +#: lib/bds/desktop/shell_live/post_editor.ex:1027 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:123 #, elixir-autogen, elixir-format msgid "Markdown" @@ -1417,9 +1417,9 @@ msgstr "" #: lib/bds/desktop/shell_live/misc_editor.ex:762 #: lib/bds/desktop/shell_live/sidebar_components.ex:654 #: lib/bds/desktop/shell_live/sidebar_delete.ex:175 -#: lib/bds/tui.ex:1657 -#: lib/bds/tui.ex:1878 -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1666 +#: lib/bds/tui.ex:1887 +#: lib/bds/tui.ex:1890 #: lib/bds/ui/registry.ex:30 #: lib/bds/ui/registry.ex:100 #: lib/bds/ui/sidebar.ex:578 @@ -1465,8 +1465,8 @@ msgstr "" #: lib/bds/tui.ex:278 #: lib/bds/tui.ex:284 #: lib/bds/tui.ex:295 -#: lib/bds/tui.ex:1412 -#: lib/bds/tui.ex:1794 +#: lib/bds/tui.ex:1421 +#: lib/bds/tui.ex:1803 #: lib/bds/ui/registry.ex:111 #, elixir-autogen, elixir-format msgid "Metadata Diff" @@ -1800,8 +1800,8 @@ msgid "Open Data Folder" msgstr "" #: lib/bds/desktop/shell_live/index.html.heex:644 -#: lib/bds/tui.ex:792 -#: lib/bds/tui.ex:797 +#: lib/bds/tui.ex:801 +#: lib/bds/tui.ex:806 #, elixir-autogen, elixir-format msgid "Open Existing Blog" msgstr "" @@ -1813,7 +1813,7 @@ msgid "Open Settings" msgstr "" #: lib/bds/desktop/menu_bar.ex:217 -#: lib/bds/tui.ex:1818 +#: lib/bds/tui.ex:1827 #, elixir-autogen, elixir-format msgid "Open in Browser" msgstr "" @@ -1909,16 +1909,16 @@ msgid "Persist the detected language for this media item" msgstr "" #: lib/bds/desktop/shell_live/misc_editor.ex:733 -#: lib/bds/desktop/shell_live/post_editor.ex:539 -#: lib/bds/desktop/shell_live/post_editor.ex:543 -#: lib/bds/desktop/shell_live/post_editor.ex:582 -#: lib/bds/desktop/shell_live/post_editor.ex:586 -#: lib/bds/desktop/shell_live/post_editor.ex:624 -#: lib/bds/desktop/shell_live/post_editor.ex:639 -#: lib/bds/desktop/shell_live/post_editor.ex:668 -#: lib/bds/desktop/shell_live/post_editor.ex:671 -#: lib/bds/desktop/shell_live/post_editor.ex:751 -#: lib/bds/desktop/shell_live/post_editor.ex:754 +#: lib/bds/desktop/shell_live/post_editor.ex:526 +#: lib/bds/desktop/shell_live/post_editor.ex:530 +#: lib/bds/desktop/shell_live/post_editor.ex:569 +#: lib/bds/desktop/shell_live/post_editor.ex:573 +#: lib/bds/desktop/shell_live/post_editor.ex:611 +#: lib/bds/desktop/shell_live/post_editor.ex:626 +#: lib/bds/desktop/shell_live/post_editor.ex:655 +#: lib/bds/desktop/shell_live/post_editor.ex:658 +#: lib/bds/desktop/shell_live/post_editor.ex:738 +#: lib/bds/desktop/shell_live/post_editor.ex:741 #: lib/bds/desktop/shell_live/sidebar_components.ex:651 #: lib/bds/desktop/shell_live/sidebar_delete.ex:174 #: lib/bds/ui/registry.ex:99 @@ -1949,12 +1949,12 @@ msgstr "" msgid "Post is marked as do-not-translate but has translations" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:582 +#: lib/bds/desktop/shell_live/post_editor.ex:569 #, elixir-autogen, elixir-format msgid "Post published" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:539 +#: lib/bds/desktop/shell_live/post_editor.ex:526 #, elixir-autogen, elixir-format msgid "Post saved" msgstr "" @@ -1962,8 +1962,8 @@ msgstr "" #: lib/bds/desktop/menu_bar.ex:233 #: lib/bds/desktop/shell_live/misc_editor.ex:664 #: lib/bds/desktop/shell_live/misc_editor.ex:761 -#: lib/bds/tui.ex:1656 -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1665 +#: lib/bds/tui.ex:1680 #: lib/bds/ui/registry.ex:14 #: lib/bds/ui/sidebar.ex:271 #, elixir-autogen, elixir-format @@ -1980,7 +1980,7 @@ msgstr "" msgid "Preferences" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:1042 +#: lib/bds/desktop/shell_live/post_editor.ex:1028 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:124 #, elixir-autogen, elixir-format msgid "Preview" @@ -2023,8 +2023,8 @@ msgstr "" #: lib/bds/desktop/shell_live/chat_surface.ex:15 #: lib/bds/desktop/shell_live/index.html.heex:623 -#: lib/bds/tui.ex:705 -#: lib/bds/tui.ex:710 +#: lib/bds/tui.ex:714 +#: lib/bds/tui.ex:719 #, elixir-autogen, elixir-format msgid "Projects" msgstr "" @@ -2053,7 +2053,7 @@ msgid "Publish Selected" msgstr "" #: lib/bds/desktop/shell_data.ex:165 -#: lib/bds/desktop/shell_live/post_editor.ex:1036 +#: lib/bds/desktop/shell_live/post_editor.ex:1022 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:472 #: lib/bds/ui/sidebar.ex:324 #, elixir-autogen, elixir-format @@ -2092,15 +2092,15 @@ msgid "Ready to import:" msgstr "" #: lib/bds/desktop/menu_bar.ex:248 -#: lib/bds/tui.ex:789 -#: lib/bds/tui.ex:1798 +#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:1807 #, elixir-autogen, elixir-format msgid "Rebuild Database" msgstr "" #: lib/bds/desktop/menu_bar.ex:250 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:381 -#: lib/bds/tui.ex:1802 +#: lib/bds/tui.ex:1811 #, elixir-autogen, elixir-format msgid "Rebuild Embedding Index" msgstr "" @@ -2158,7 +2158,7 @@ msgid "Refresh Translation" msgstr "" #: lib/bds/desktop/menu_bar.ex:252 -#: lib/bds/tui.ex:1805 +#: lib/bds/tui.ex:1814 #, elixir-autogen, elixir-format msgid "Regenerate Calendar" msgstr "" @@ -2169,7 +2169,7 @@ msgid "Regenerate Missing Thumbnails" msgstr "" #: lib/bds/desktop/menu_bar.ex:249 -#: lib/bds/tui.ex:1799 +#: lib/bds/tui.ex:1808 #, elixir-autogen, elixir-format msgid "Reindex Text" msgstr "" @@ -2253,7 +2253,7 @@ msgstr "" msgid "Result" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:1037 +#: lib/bds/desktop/shell_live/post_editor.ex:1023 #, elixir-autogen, elixir-format msgid "Reverted" msgstr "" @@ -2306,7 +2306,7 @@ msgid "Save Translation" msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:730 -#: lib/bds/desktop/shell_live/post_editor.ex:1035 +#: lib/bds/desktop/shell_live/post_editor.ex:1021 #, elixir-autogen, elixir-format msgid "Saved" msgstr "" @@ -2357,7 +2357,7 @@ msgstr "" #: lib/bds/desktop/shell_live/script_editor.ex:216 #: lib/bds/desktop/shell_live/script_editor.ex:219 #: lib/bds/desktop/shell_live/script_editor.ex:234 -#: lib/bds/tui.ex:1659 +#: lib/bds/tui.ex:1668 #: lib/bds/ui/registry.ex:38 #: lib/bds/ui/sidebar.ex:63 #: lib/bds/ui/sidebar.ex:115 @@ -2464,7 +2464,7 @@ msgstr "" #: lib/bds/desktop/shell_live/settings_editor/project_settings.ex:60 #: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:11 -#: lib/bds/tui.ex:1661 +#: lib/bds/tui.ex:1670 #: lib/bds/ui/registry.ex:86 #: lib/bds/ui/registry.ex:101 #: lib/bds/ui/sidebar.ex:795 @@ -2490,7 +2490,7 @@ msgstr "" #: lib/bds/desktop/shell_live/misc_editor.ex:412 #: lib/bds/tui.ex:311 #: lib/bds/tui.ex:313 -#: lib/bds/tui.ex:1417 +#: lib/bds/tui.ex:1426 #: lib/bds/ui/registry.ex:125 #, elixir-autogen, elixir-format msgid "Site Validation" @@ -2554,7 +2554,7 @@ msgstr "" #: lib/bds/desktop/shell_live/settings_editor/style_editor.ex:76 #: lib/bds/desktop/shell_live/settings_editor_html/style_editor.html.heex:3 #: lib/bds/ui/registry.ex:102 -#: lib/bds/ui/settings_form.ex:241 +#: lib/bds/ui/settings_form.ex:246 #: lib/bds/ui/sidebar.ex:817 #, elixir-autogen, elixir-format msgid "Style" @@ -2613,7 +2613,7 @@ msgstr "" #: lib/bds/desktop/shell_live/tags_editor.ex:222 #: lib/bds/desktop/shell_live/tags_editor.ex:253 #: lib/bds/desktop/shell_live/tags_editor_html/tags_editor.html.heex:11 -#: lib/bds/tui.ex:1660 +#: lib/bds/tui.ex:1669 #: lib/bds/ui/registry.ex:54 #: lib/bds/ui/registry.ex:103 #: lib/bds/ui/sidebar.ex:289 @@ -2625,7 +2625,7 @@ msgstr "" #: lib/bds/desktop/shell_live.ex:786 #: lib/bds/desktop/shell_live/panel_renderer.ex:54 -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "Tasks" msgstr "" @@ -2671,7 +2671,7 @@ msgstr "" #: lib/bds/desktop/shell_live/template_editor.ex:171 #: lib/bds/desktop/shell_live/template_editor.ex:176 #: lib/bds/desktop/shell_live/template_editor.ex:191 -#: lib/bds/tui.ex:1658 +#: lib/bds/tui.ex:1667 #: lib/bds/ui/registry.ex:46 #: lib/bds/ui/sidebar.ex:71 #: lib/bds/ui/sidebar.ex:122 @@ -2785,7 +2785,7 @@ msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:363 #: lib/bds/desktop/shell_live/media_editor.ex:556 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:76 -#: lib/bds/desktop/shell_live/post_editor.ex:813 +#: lib/bds/desktop/shell_live/post_editor.ex:800 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:60 #, elixir-autogen, elixir-format msgid "Translate" @@ -2862,7 +2862,7 @@ msgstr "" #: lib/bds/desktop/shell_live/media_editor.ex:729 #: lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex:10 -#: lib/bds/desktop/shell_live/post_editor.ex:1034 +#: lib/bds/desktop/shell_live/post_editor.ex:1020 #: lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex:7 #, elixir-autogen, elixir-format msgid "Unsaved" @@ -2895,7 +2895,7 @@ msgid "Updated URLs" msgstr "" #: lib/bds/desktop/menu_bar.ex:259 -#: lib/bds/tui.ex:1817 +#: lib/bds/tui.ex:1826 #, elixir-autogen, elixir-format msgid "Upload Site" msgstr "" @@ -2923,13 +2923,13 @@ msgid "Validate" msgstr "" #: lib/bds/desktop/menu_bar.ex:258 -#: lib/bds/tui.ex:1795 +#: lib/bds/tui.ex:1804 #, elixir-autogen, elixir-format msgid "Validate Site" msgstr "" #: lib/bds/desktop/menu_bar.ex:253 -#: lib/bds/tui.ex:1808 +#: lib/bds/tui.ex:1817 #, elixir-autogen, elixir-format msgid "Validate Translations" msgstr "" @@ -3363,12 +3363,12 @@ msgstr "" msgid "Move this post to the archive" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:668 +#: lib/bds/desktop/shell_live/post_editor.ex:655 #, elixir-autogen, elixir-format msgid "Post archived" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:751 +#: lib/bds/desktop/shell_live/post_editor.ex:738 #, elixir-autogen, elixir-format msgid "Post unarchived" msgstr "" @@ -3410,7 +3410,7 @@ msgid "Commit" msgstr "" #: lib/bds/desktop/shell_live/sidebar_components.ex:555 -#: lib/bds/tui.ex:1239 +#: lib/bds/tui.ex:1248 #, elixir-autogen, elixir-format msgid "Commit message" msgstr "" @@ -3434,7 +3434,7 @@ msgid "Fetch" msgstr "" #: lib/bds/desktop/shell_live/sidebar_components.ex:586 -#: lib/bds/tui.ex:1074 +#: lib/bds/tui.ex:1083 #, elixir-autogen, elixir-format msgid "History" msgstr "" @@ -3482,7 +3482,7 @@ msgstr "" #: lib/bds/desktop/shell_live/git_handler.ex:17 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 #: lib/bds/desktop/shell_live/sidebar_components.ex:543 -#: lib/bds/tui.ex:957 +#: lib/bds/tui.ex:966 #, elixir-autogen, elixir-format msgid "Pull" msgstr "" @@ -3490,7 +3490,7 @@ msgstr "" #: lib/bds/desktop/shell_live/git_handler.ex:18 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 #: lib/bds/desktop/shell_live/sidebar_components.ex:544 -#: lib/bds/tui.ex:958 +#: lib/bds/tui.ex:967 #, elixir-autogen, elixir-format msgid "Push" msgstr "" @@ -3556,18 +3556,18 @@ msgstr "" msgid "Open a project before importing a blogmark." msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:694 +#: lib/bds/desktop/shell_live/post_editor.ex:681 #, elixir-autogen, elixir-format msgid "Added %{name}" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:701 +#: lib/bds/desktop/shell_live/post_editor.ex:688 #, elixir-autogen, elixir-format msgid "Failed to import %{path}: %{reason}" msgstr "" -#: lib/bds/desktop/shell_live/post_editor.ex:693 -#: lib/bds/desktop/shell_live/post_editor.ex:700 +#: lib/bds/desktop/shell_live/post_editor.ex:680 +#: lib/bds/desktop/shell_live/post_editor.ex:687 #, elixir-autogen, elixir-format msgid "Insert Image" msgstr "" @@ -3582,7 +3582,7 @@ msgstr "" msgid "Copy Bookmarklet to Clipboard" msgstr "" -#: lib/bds/desktop/shell_live/overlay_manager.ex:300 +#: lib/bds/desktop/shell_live/overlay_manager.ex:301 #, elixir-autogen, elixir-format msgid "Delete Tag" msgstr "" @@ -3603,7 +3603,7 @@ msgid "Suggested tags" msgstr "" #: lib/bds/desktop/menu_bar.ex:257 -#: lib/bds/tui.ex:1796 +#: lib/bds/tui.ex:1805 #, elixir-autogen, elixir-format msgid "Force Render Site" msgstr "" @@ -3700,12 +3700,12 @@ msgstr "" msgid "The endpoint returned no models. You can still type a model name manually." msgstr "" -#: lib/bds/tui.ex:1754 +#: lib/bds/tui.ex:1763 #, elixir-autogen, elixir-format msgid "AI is unavailable in airplane mode without a local endpoint." msgstr "" -#: lib/bds/tui.ex:1881 +#: lib/bds/tui.ex:1890 #, elixir-autogen, elixir-format msgid "Could not load this file." msgstr "" @@ -3715,68 +3715,68 @@ msgstr "" msgid "Editing this item is not available in the terminal UI yet." msgstr "" -#: lib/bds/tui.ex:940 +#: lib/bds/tui.ex:949 #, elixir-autogen, elixir-format msgid "No suggestions returned." msgstr "" -#: lib/bds/tui.ex:1878 +#: lib/bds/tui.ex:1887 #, elixir-autogen, elixir-format msgid "Only images can be previewed." msgstr "" -#: lib/bds/tui.ex:1671 +#: lib/bds/tui.ex:1680 #, elixir-autogen, elixir-format msgid "Post not found." msgstr "" -#: lib/bds/tui.ex:1174 +#: lib/bds/tui.ex:1183 #, elixir-autogen, elixir-format msgid "Press enter to preview %{name}." msgstr "" -#: lib/bds/tui.ex:1724 +#: lib/bds/tui.ex:1733 #, elixir-autogen, elixir-format msgid "Published." msgstr "" -#: lib/bds/tui.ex:1740 +#: lib/bds/tui.ex:1749 #, elixir-autogen, elixir-format msgid "Save before switching languages." msgstr "" -#: lib/bds/tui.ex:524 -#: lib/bds/tui.ex:1725 +#: lib/bds/tui.ex:533 +#: lib/bds/tui.ex:1734 #, elixir-autogen, elixir-format msgid "Saved." msgstr "" -#: lib/bds/tui.ex:1177 +#: lib/bds/tui.ex:1186 #, elixir-autogen, elixir-format msgid "Select an entry and press enter to open it." msgstr "" -#: lib/bds/tui.ex:937 +#: lib/bds/tui.ex:946 #, elixir-autogen, elixir-format msgid "Suggestions applied." msgstr "" -#: lib/bds/tui.ex:1189 +#: lib/bds/tui.ex:1198 #, elixir-autogen, elixir-format msgid "Title (ctrl+t to edit)" msgstr "" -#: lib/bds/tui.ex:1188 +#: lib/bds/tui.ex:1197 #, elixir-autogen, elixir-format msgid "Title (editing — enter to confirm)" msgstr "" -#: lib/bds/tui.ex:1241 +#: lib/bds/tui.ex:1250 #, elixir-autogen, elixir-format msgid "Working…" msgstr "" -#: lib/bds/tui.ex:1263 +#: lib/bds/tui.ex:1272 #, elixir-autogen, elixir-format msgid "esc to close" msgstr "" @@ -3813,52 +3813,52 @@ msgstr "" msgid "Use the form user@host or user@host:port." msgstr "" -#: lib/bds/tui.ex:1208 +#: lib/bds/tui.ex:1217 #, elixir-autogen, elixir-format msgid "Preview (ctrl+e to edit)" msgstr "" -#: lib/bds/tui.ex:1520 +#: lib/bds/tui.ex:1529 #, elixir-autogen, elixir-format msgid "ctrl+s save · ctrl+p publish · ctrl+e preview · ctrl+t title · ctrl+l language · ctrl+g AI · esc back" msgstr "" -#: lib/bds/tui.ex:985 +#: lib/bds/tui.ex:994 #, elixir-autogen, elixir-format msgid "All tasks finished." msgstr "" -#: lib/bds/tui.ex:1289 +#: lib/bds/tui.ex:1298 #, elixir-autogen, elixir-format msgid "Commands — esc to close" msgstr "" -#: lib/bds/tui.ex:814 +#: lib/bds/tui.ex:823 #, elixir-autogen, elixir-format msgid "Unknown command." msgstr "" -#: lib/bds/tui.ex:1279 +#: lib/bds/tui.ex:1288 #, elixir-autogen, elixir-format msgid "[report/apply in GUI]" msgstr "" -#: lib/bds/tui.ex:1428 +#: lib/bds/tui.ex:1437 #, elixir-autogen, elixir-format msgid "%{diffs} differences · %{orphans} orphan files" msgstr "" -#: lib/bds/tui.ex:1460 +#: lib/bds/tui.ex:1469 #, elixir-autogen, elixir-format msgid "Expected %{expected} · Existing %{existing}" msgstr "" -#: lib/bds/tui.ex:1477 +#: lib/bds/tui.ex:1486 #, elixir-autogen, elixir-format msgid "Extra files (will be removed):" msgstr "" -#: lib/bds/tui.ex:1473 +#: lib/bds/tui.ex:1482 #, elixir-autogen, elixir-format msgid "Missing pages (will be rendered):" msgstr "" @@ -3873,57 +3873,57 @@ msgstr "" msgid "Nothing to repair." msgstr "" -#: lib/bds/tui.ex:1451 +#: lib/bds/tui.ex:1460 #, elixir-autogen, elixir-format msgid "Orphan files (not in database):" msgstr "" -#: lib/bds/tui.ex:1467 +#: lib/bds/tui.ex:1476 #, elixir-autogen, elixir-format msgid "Sitemap changed." msgstr "" -#: lib/bds/tui.ex:1481 +#: lib/bds/tui.ex:1490 #, elixir-autogen, elixir-format msgid "Updated posts (will be re-rendered):" msgstr "" -#: lib/bds/tui.ex:1418 +#: lib/bds/tui.ex:1427 #, elixir-autogen, elixir-format msgid "enter apply changes · esc close" msgstr "" -#: lib/bds/tui.ex:1413 +#: lib/bds/tui.ex:1422 #, elixir-autogen, elixir-format msgid "enter repair all from files · esc close" msgstr "" -#: lib/bds/tui.ex:779 +#: lib/bds/tui.ex:788 #, elixir-autogen, elixir-format msgid "Imported Blog" msgstr "" -#: lib/bds/tui.ex:798 +#: lib/bds/tui.ex:807 #, elixir-autogen, elixir-format msgid "Not a folder: %{path}" msgstr "" -#: lib/bds/tui.ex:1355 +#: lib/bds/tui.ex:1364 #, elixir-autogen, elixir-format msgid "Projects — enter switch · o open existing · esc close" msgstr "" -#: lib/bds/tui.ex:706 +#: lib/bds/tui.ex:715 #, elixir-autogen, elixir-format msgid "Switched to %{name}." msgstr "" -#: lib/bds/tui.ex:1383 +#: lib/bds/tui.ex:1392 #, elixir-autogen, elixir-format msgid "Matching folders" msgstr "" -#: lib/bds/tui.ex:1321 +#: lib/bds/tui.ex:1330 #, elixir-autogen, elixir-format msgid "Open Existing Blog — type a folder path · tab complete · enter open · esc back" msgstr "" @@ -3938,12 +3938,12 @@ msgstr "" msgid "Committed." msgstr "" -#: lib/bds/tui.ex:1165 +#: lib/bds/tui.ex:1174 #, elixir-autogen, elixir-format msgid "Diff (pgup/pgdn scroll)" msgstr "" -#: lib/bds/tui.ex:1548 +#: lib/bds/tui.ex:1557 #, elixir-autogen, elixir-format msgid "No changes." msgstr "" @@ -3954,7 +3954,7 @@ msgid "No diff for this file." msgstr "" #: lib/bds/tui.ex:388 -#: lib/bds/tui.ex:1152 +#: lib/bds/tui.ex:1161 #, elixir-autogen, elixir-format msgid "Not a git repository." msgstr "" @@ -4044,32 +4044,71 @@ msgstr "" msgid "Show Title" msgstr "" -#: lib/bds/ui/settings_form.ex:242 +#: lib/bds/ui/settings_form.ex:247 #, elixir-autogen, elixir-format msgid "Theme" msgstr "" -#: lib/bds/tui.ex:1136 +#: lib/bds/tui.ex:1145 #, elixir-autogen, elixir-format msgid "enter edit · ctrl+s save · esc close" msgstr "" -#: lib/bds/tui.ex:1499 +#: lib/bds/tui.ex:1508 #, elixir-autogen, elixir-format msgid "enter edit/toggle/cycle · ctrl+s save · esc close · ctrl+q quit" msgstr "" -#: lib/bds/ui/settings_form.ex:230 +#: lib/bds/ui/settings_form.ex:235 #, elixir-autogen, elixir-format msgid "not supported yet" msgstr "" -#: lib/bds/tui.ex:1506 +#: lib/bds/tui.ex:1515 #, elixir-autogen, elixir-format msgid "c commit · u pull · s push · enter jump to file · pgup/pgdn scroll diff · 1-7 views · ctrl+q quit" msgstr "" -#: lib/bds/tui.ex:1513 +#: lib/bds/tui.ex:1522 #, elixir-autogen, elixir-format msgid "enter open · n new post · 1-7 views · p projects · / search · : commands (:? help) · r refresh · ctrl+q quit" msgstr "" + +#: lib/bds/ui/settings_form.ex:369 +#, elixir-autogen, elixir-format +msgid "CLI tool installed to %{path}" +msgstr "" + +#: lib/bds/desktop/shell_live/settings_editor.ex:75 +#: lib/bds/desktop/shell_live/settings_editor.ex:78 +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:386 +#: lib/bds/ui/settings_form.ex:223 +#, elixir-autogen, elixir-format +msgid "Command Line Tool" +msgstr "" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:390 +#: lib/bds/ui/settings_form.ex:224 +#, elixir-autogen, elixir-format +msgid "Install CLI Tool" +msgstr "" + +#: lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex:387 +#, elixir-autogen, elixir-format +msgid "Install the bds-cli tool to ~/.local/bin; it uses the same settings and cache database as the app" +msgstr "" + +#: lib/bds/ui/settings_form.ex:375 +#, elixir-autogen, elixir-format +msgid "Installing the CLI tool failed: %{reason}" +msgstr "" + +#: lib/bds/ui/settings_form.ex:372 +#, elixir-autogen, elixir-format +msgid "The CLI tool can only be installed from the packaged application" +msgstr "" + +#: lib/bds/ui/settings_form.ex:379 +#, elixir-autogen, elixir-format +msgid "Unknown settings action" +msgstr "" diff --git a/rel/overlays/cli/bin/bds-cli b/rel/overlays/cli/bin/bds-cli new file mode 100755 index 0000000..e31be43 --- /dev/null +++ b/rel/overlays/cli/bin/bds-cli @@ -0,0 +1,23 @@ +#!/bin/sh +# bds-cli: workspace CLI for bDS2 (issue #25). Runs commands inside the +# release VM against the same settings and cache database as the app. +set -e + +SELF_DIR="$(cd "$(dirname "$0")" && pwd)" +RELEASE_ROOT="$(cd "$SELF_DIR/../.." && pwd)" +export RELEASE_ROOT + +# The TUI owns the terminal, so it boots the app instead of the one-shot CLI. +if [ "$1" = "tui" ]; then + BDS_MODE=tui exec "$RELEASE_ROOT/bin/bds" start +fi + +# `bin/bds eval` cannot forward arguments, so the argv travels in an +# environment variable, joined with the ASCII unit separator (0x1f). +US="$(printf '\037')" +ARGV="" +for arg in "$@"; do + ARGV="${ARGV}${arg}${US}" +done + +BDS_CLI_ARGV="$ARGV" BDS_MODE=cli exec "$RELEASE_ROOT/bin/bds" eval "BDS.CLI.main()" diff --git a/specs/cli.allium b/specs/cli.allium new file mode 100644 index 0000000..07672e8 --- /dev/null +++ b/specs/cli.allium @@ -0,0 +1,175 @@ +-- allium: 1 +-- Workspace CLI tool (issue #25) +-- Scope: extension (Bucket G — MCP + Automation) +-- Distilled from: lib/bds/cli.ex, lib/bds/cli/commands.ex, lib/bds/cli/install.ex, +-- rel/overlays/cli/bin/bds-cli + +entity CliInvocation { + command: rebuild | repair | render | upload | push | pull | post | media + | gallery | config | project | tui | lua + incremental: Boolean + force: Boolean + exit_code: Integer + -- Parsed by Optimus: subcommands, options, flags, and auto-generated + -- help/version output. Unknown commands and invalid options exit 1 + -- with formatted errors on stderr. +} + +surface CliSurface { + facing _: CliRuntime + + provides: + CliCommandExecuted(command) + CliInstallRequested() +} + +invariant SharedDatabase { + -- The CLI boots the application in BDS_MODE=cli: the same repo, + -- settings, and cache database as the GUI/TUI app — but with no + -- HTTP listener, no SSH daemon, no window, and no sync watcher. + -- Console logging is redirected to the rotating log file so stdout + -- carries only command output. +} + +invariant CliWritesNotify { + -- Every CLI mutation (post/media/gallery creation, config set, + -- project add/switch, bulk rebuild/repair) inserts DbNotification + -- rows (from_cli: true) so a concurrently running app picks the + -- change up through its sync watcher (see cli_sync.allium). Bulk + -- maintenance uses wildcard entity ids per entity type. +} + +rule ExitCode { + when: CliCommandExecuted(command) + -- Success prints the result (stdout) and exits 0; any error prints + -- to stderr and exits 1. + requires: CliInvocation.command = command + ensures: CliInvocation.exit_code.updated() +} + +rule RebuildFull { + when: CliCommandExecuted(command: rebuild) + requires: not CliInvocation.incremental + -- The same step sequence as the GUI "Rebuild Database" + -- (BDS.Maintenance.full_rebuild_steps — posts, media, scripts, + -- templates, post links, thumbnails, embedding index), run + -- synchronously with progress on stdout. + ensures: CacheDatabaseRebuilt() +} + +rule RebuildIncremental { + when: CliCommandExecuted(command: rebuild) + requires: CliInvocation.incremental + -- Metadata diff, then auto-apply file→db for every difference and + -- import every orphan file. + ensures: CacheDatabaseRebuilt() +} + +rule Repair { + when: CliCommandExecuted(command: repair) + -- Subcommand argument selects the repair part: post-links, + -- media-links, thumbnails, embeddings, or search — the standard + -- rebuild tasks outside the full rebuild. + ensures: RepairTaskCompleted() +} + +rule Render { + when: CliCommandExecuted(command: render) + -- Default: render all site sections plus the search index. + -- --incremental: validate the generated output and apply only the + -- differences (targeted render + extra-file deletion + calendar). + -- --force: full re-render ignoring (but updating) content hashes. + requires: not (CliInvocation.incremental and CliInvocation.force) + ensures: SiteRendered() +} + +rule Upload { + when: CliCommandExecuted(command: upload) + -- Uses the project publishing preferences (ssh host/user/path/mode) + -- exactly like the app's upload, and waits for the publish job. + ensures: SiteUploaded() +} + +rule GitSync { + when: CliCommandExecuted(command: push | pull) + -- push: git push of the project repository to origin. + -- pull: git pull --ff-only, then the incremental cache update + -- (metadata diff auto-apply + orphan import) so the database + -- reflects the pulled files. + ensures: RepositorySynchronized() +} + +rule CreatePost { + when: CliCommandExecuted(command: post) + -- Post data from parameters or JSON on stdin (LLM-friendly). + -- Language is auto-detected when missing: the configured AI + -- endpoint (airplane mode routes to the local model), falling back + -- to the offline heuristic with a printed notice — the CLI + -- equivalent of the airplane-mode toast. After creation the same + -- auto-translation as the GUI is scheduled and awaited; when + -- nothing can be scheduled the user is told. + ensures: PostCreated() +} + +rule CreateMedia { + when: CliCommandExecuted(command: media) + -- Imports the image, then best-effort AI enrichment: generated + -- title, alt text, caption, and translations to all configured + -- blog languages (the gallery pipeline without post linking). + ensures: MediaImported() +} + +rule CreateGallery { + when: CliCommandExecuted(command: gallery) + -- Creates the post, then runs the shared gallery import pipeline + -- for every referenced image: import, mandatory post link, AI + -- enrichment, translations; finally the post auto-translation. + ensures: PostCreated() + ensures: MediaImported() +} + +rule Preferences { + when: CliCommandExecuted(command: config) + -- get/set/list of the global settings store shared with the app. + ensures: PreferencesAccessed() +} + +rule Projects { + when: CliCommandExecuted(command: project) + -- list, add (register a folder in the cache database), and + -- switch (change the active project). + ensures: ProjectRegistryUpdated() +} + +rule Tui { + when: CliCommandExecuted(command: tui) + -- Handled by the launcher script: exec the release in BDS_MODE=tui + -- so the interactive terminal UI owns the terminal. + ensures: TuiStarted() +} + +rule RunLuaTask { + when: CliCommandExecuted(command: lua) + -- Runs a utility ("task", long-running) script from the database by + -- slug in the active project, with the managed-job execution budget + -- (unlimited time/reductions) but synchronously. Macro and + -- transform scripts are rejected. + ensures: ScriptExecuted() +} + +rule InstallLauncher { + when: CliInstallRequested() + -- Install buttons in the GUI settings (Data section) and the TUI + -- settings (data form action field) both call + -- BDS.UI.SettingsForm.run_action("install_cli"): a shim exec'ing + -- the release's cli/bin/bds-cli launcher is written to + -- ~/.local/bin/bds-cli. Outside a packaged release the action + -- reports that installing requires the packaged application. + ensures: LauncherInstalled() +} + +invariant LauncherArgv { + -- bin/bds eval cannot forward arguments, so the launcher passes the + -- argv in BDS_CLI_ARGV joined with the ASCII unit separator (0x1f) + -- and the release evaluates BDS.CLI.main(). +} diff --git a/test/bds/cli_test.exs b/test/bds/cli_test.exs new file mode 100644 index 0000000..5979563 --- /dev/null +++ b/test/bds/cli_test.exs @@ -0,0 +1,401 @@ +defmodule BDS.CLITest do + use ExUnit.Case, async: false + + import Ecto.Query + import ExUnit.CaptureIO + + alias BDS.CLI + alias BDS.CLI.Commands + alias BDS.CliSync.Notification + alias BDS.Repo + + setup do + :ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo) + Repo.delete_all(Notification) + + temp_dir = Path.join(System.tmp_dir!(), "bds-cli-test-#{System.unique_integer([:positive])}") + File.mkdir_p!(temp_dir) + on_exit(fn -> File.rm_rf(temp_dir) end) + + {:ok, temp_dir: temp_dir} + end + + defp create_active_project(temp_dir, name \\ "CLI Test") do + {:ok, project} = BDS.Projects.create_project(%{name: name, data_path: temp_dir}) + {:ok, project} = BDS.Projects.set_active_project(project.id) + project + end + + defp notifications do + Repo.all(Notification) + end + + describe "argv decoding" do + test "splits on the unit separator and drops empties" do + assert CLI.env_argv("post" <> <<0x1F>> <> "--title" <> <<0x1F>> <> "Hello" <> <<0x1F>>) == + ["post", "--title", "Hello"] + + assert CLI.env_argv(nil) == [] + assert CLI.env_argv("") == [] + end + end + + describe "parser" do + test "parses every subcommand" do + optimus = CLI.optimus() + + assert {:ok, [:rebuild], %{flags: %{incremental: true}}} = + Optimus.parse(optimus, ["rebuild", "--incremental"]) + + assert {:ok, [:repair], %{args: %{part: "post-links"}}} = + Optimus.parse(optimus, ["repair", "post-links"]) + + assert {:error, [:repair], _errors} = Optimus.parse(optimus, ["repair", "nonsense"]) + + assert {:ok, [:render], %{flags: %{force: true, incremental: false}}} = + Optimus.parse(optimus, ["render", "--force"]) + + assert {:ok, [:upload], _result} = Optimus.parse(optimus, ["upload"]) + assert {:ok, [:push], _result} = Optimus.parse(optimus, ["push"]) + assert {:ok, [:pull], _result} = Optimus.parse(optimus, ["pull"]) + + assert {:ok, [:post], %{options: %{title: "Hi", tags: "a,b"}}} = + Optimus.parse(optimus, ["post", "--title", "Hi", "--tags", "a,b"]) + + assert {:ok, [:media], %{args: %{file: "img.jpg"}, options: %{language: "de"}}} = + Optimus.parse(optimus, ["media", "img.jpg", "--language", "de"]) + + assert {:ok, [:gallery], %{options: %{title: "Trip"}, unknown: ["a.jpg", "b.jpg"]}} = + Optimus.parse(optimus, ["gallery", "--title", "Trip", "a.jpg", "b.jpg"]) + + assert {:ok, [:config, :get], %{args: %{key: "ui.theme"}}} = + Optimus.parse(optimus, ["config", "get", "ui.theme"]) + + assert {:ok, [:config, :set], %{args: %{key: "k", value: "v"}}} = + Optimus.parse(optimus, ["config", "set", "k", "v"]) + + assert {:ok, [:config, :list], _result} = Optimus.parse(optimus, ["config", "list"]) + assert {:ok, [:project, :list], _result} = Optimus.parse(optimus, ["project", "list"]) + + assert {:ok, [:project, :add], %{args: %{path: "/tmp/x"}, options: %{name: "X"}}} = + Optimus.parse(optimus, ["project", "add", "/tmp/x", "--name", "X"]) + + assert {:ok, [:project, :switch], %{args: %{project: "blog"}}} = + Optimus.parse(optimus, ["project", "switch", "blog"]) + + assert {:ok, [:tui], _result} = Optimus.parse(optimus, ["tui"]) + + assert {:ok, [:lua], %{args: %{script: "cleanup"}, unknown: ["arg1"]}} = + Optimus.parse(optimus, ["lua", "cleanup", "arg1"]) + end + + test "help exits 0 and unknown commands exit 1" do + {help_code, help_output} = with_io(fn -> CLI.run(["--help"]) end) + assert help_code == 0 + assert help_output =~ "rebuild" + assert help_output =~ "gallery" + + {code, _output} = with_io(:stderr, fn -> CLI.run(["frobnicate"]) end) + assert code == 1 + end + end + + describe "config commands" do + test "set, get, and list operate on the global settings store", %{temp_dir: _temp_dir} do + assert {:ok, "cli.test.key = hello"} = + Commands.dispatch([:config, :set], %{args: %{key: "cli.test.key", value: "hello"}}) + + assert {:ok, "hello"} = + Commands.dispatch([:config, :get], %{args: %{key: "cli.test.key"}}) + + {:ok, output} = with_io(fn -> Commands.dispatch([:config, :list], %{}) end) + assert output =~ "cli.test.key=hello" + + assert {:error, message} = + Commands.dispatch([:config, :get], %{args: %{key: "cli.test.missing"}}) + + assert message =~ "not set" + + assert Enum.any?( + notifications(), + &(&1.entity_type == "setting" and &1.entity_id == "cli.test.key" and + &1.from_cli == true) + ) + end + end + + describe "project commands" do + test "add, switch, and list manage the project registry", %{temp_dir: temp_dir} do + assert {:ok, added_message} = + Commands.dispatch([:project, :add], %{ + args: %{path: temp_dir}, + options: %{name: "CLI Added"} + }) + + assert added_message =~ "Added project" + + project = + Enum.find(BDS.Projects.list_projects(), &(&1.name == "CLI Added")) + + assert project + + assert {:ok, "Active project: CLI Added"} = + Commands.dispatch([:project, :switch], %{args: %{project: project.slug}}) + + {:ok, output} = with_io(fn -> Commands.dispatch([:project, :list], %{}) end) + assert output =~ "CLI Added" + assert output =~ "* " <> project.id + + assert {:error, message} = + Commands.dispatch([:project, :switch], %{args: %{project: "no-such-project"}}) + + assert message =~ "No project matches" + + entity_types = Enum.map(notifications(), & &1.entity_type) + assert "project" in entity_types + end + + test "add rejects a missing directory" do + assert {:error, message} = + Commands.dispatch([:project, :add], %{ + args: %{path: "/nonexistent/bds-cli"}, + options: %{} + }) + + assert message =~ "not a directory" + end + end + + describe "post command" do + test "creates a post from options and writes a cli notification", %{temp_dir: temp_dir} do + create_active_project(temp_dir) + + {{:ok, message}, output} = + with_io(fn -> + Commands.dispatch([:post], %{ + flags: %{no_translate: true}, + options: %{ + title: "From the CLI", + content: "Hello from the command line", + language: "en", + tags: "cli,test", + categories: "notes" + } + }) + end) + + assert message =~ "Created post" + assert output == "" or output =~ "translation" + + post = + Repo.one!( + from post in BDS.Posts.Post, where: post.title == "From the CLI" + ) + + assert post.tags == ["cli", "test"] + assert post.categories == ["notes"] + assert post.language == "en" + assert post.status == :draft + + assert Enum.any?( + notifications(), + &(&1.entity_type == "post" and &1.entity_id == post.id and &1.action == :created) + ) + end + + test "creates a post from JSON on stdin", %{temp_dir: temp_dir} do + create_active_project(temp_dir) + + json = + Jason.encode!(%{ + "title" => "Stdin Post", + "content" => "Body from stdin", + "language" => "en", + "tags" => ["json", "cli"] + }) + + {{:ok, message}, _output} = + with_io([input: json], fn -> + Commands.dispatch([:post], %{flags: %{stdin: true, no_translate: true}, options: %{}}) + end) + + assert message =~ "Created post" + + post = Repo.one!(from post in BDS.Posts.Post, where: post.title == "Stdin Post") + assert post.tags == ["json", "cli"] + end + + test "requires a title", %{temp_dir: temp_dir} do + create_active_project(temp_dir) + + assert {:error, message} = Commands.dispatch([:post], %{flags: %{}, options: %{}}) + assert message =~ "--title is required" + end + end + + describe "media command" do + test "imports a file and reports enrichment best-effort", %{temp_dir: temp_dir} do + create_active_project(temp_dir) + + source = Path.join(temp_dir, "cli-image.png") + File.write!(source, "fake png bytes") + + {result, _output} = + with_io(fn -> + Commands.dispatch([:media], %{args: %{file: source}, options: %{}}) + end) + + assert {:ok, message} = result + assert message =~ "Imported media" + + assert Enum.any?(notifications(), &(&1.entity_type == "media" and &1.action == :created)) + end + + test "rejects a missing file", %{temp_dir: temp_dir} do + create_active_project(temp_dir) + + assert {:error, message} = + Commands.dispatch([:media], %{ + args: %{file: Path.join(temp_dir, "missing.png")}, + options: %{} + }) + + assert message =~ "No such file" + end + end + + describe "rebuild command" do + test "incremental rebuild diffs and applies from the filesystem", %{temp_dir: temp_dir} do + create_active_project(temp_dir) + + {result, _output} = + with_io(fn -> + Commands.dispatch([:rebuild], %{flags: %{incremental: true}}) + end) + + assert {:ok, message} = result + assert message =~ "differences" + end + end + + describe "gallery and tui guards" do + test "gallery without images errors", %{temp_dir: temp_dir} do + create_active_project(temp_dir) + + assert {:error, message} = + Commands.dispatch([:gallery], %{ + flags: %{}, + options: %{title: "Trip"}, + unknown: [] + }) + + assert message =~ "image files" + end + + test "tui is delegated to the launcher script" do + assert {:error, message} = Commands.dispatch([:tui], %{}) + assert message =~ "launcher" + end + end + + describe "lua command" do + test "rejects unknown and non-utility scripts", %{temp_dir: temp_dir} do + project = create_active_project(temp_dir) + + assert {:error, message} = + Commands.dispatch([:lua], %{args: %{script: "missing"}, unknown: []}) + + assert message =~ "No script" + + {:ok, script} = + BDS.Scripts.create_script(%{ + project_id: project.id, + title: "Macro Script", + kind: :macro, + content: "function render() return \"x\" end", + entrypoint: "render" + }) + + assert {:error, macro_message} = + Commands.dispatch([:lua], %{args: %{script: script.slug}, unknown: []}) + + assert macro_message =~ "macro" + end + + test "runs a utility script synchronously", %{temp_dir: temp_dir} do + project = create_active_project(temp_dir) + + {:ok, script} = + BDS.Scripts.create_script(%{ + project_id: project.id, + title: "Task Script", + kind: :utility, + content: "function main() return \"done-42\" end", + entrypoint: "main" + }) + + assert {:ok, message} = + Commands.dispatch([:lua], %{args: %{script: script.slug}, unknown: []}) + + assert message =~ "done-42" + end + end + + describe "full rebuild step sharing" do + test "the CLI and GUI share the canonical full-rebuild sequence" do + names = Enum.map(BDS.Maintenance.full_rebuild_steps("project-id"), & &1.name) + + assert names == [ + "Rebuild Posts From Files", + "Rebuild Media From Files", + "Rebuild Scripts From Files", + "Rebuild Templates From Files", + "Rebuild Post Links", + "Regenerate Missing Thumbnails", + "Rebuild Embedding Index" + ] + end + end + + describe "installer" do + test "installs a shim pointing at the release launcher", %{temp_dir: temp_dir} do + release_root = Path.join(temp_dir, "release") + launcher = Path.join([release_root, "cli", "bin", "bds-cli"]) + File.mkdir_p!(Path.dirname(launcher)) + File.write!(launcher, "#!/bin/sh\n") + + bin_dir = Path.join(temp_dir, "local-bin") + + assert {:ok, installed} = + BDS.CLI.Install.install(bin_dir: bin_dir, release_root: release_root) + + assert installed == Path.join(bin_dir, "bds-cli") + content = File.read!(installed) + assert content =~ launcher + assert content =~ ~s(exec ") + assert Bitwise.band(File.stat!(installed).mode, 0o111) != 0 + end + + test "reports no_release outside a packaged release", %{temp_dir: temp_dir} do + assert {:error, :no_release} = + BDS.CLI.Install.install( + bin_dir: Path.join(temp_dir, "bin"), + release_root: Path.join(temp_dir, "not-a-release") + ) + end + end + + describe "settings form action" do + test "the data section exposes the install action and run_action handles it" do + fields = BDS.UI.SettingsForm.load("data", "nonexistent-project").fields + action = Enum.find(fields, &(&1.type == :action)) + assert action.key == "install_cli" + + # Outside a packaged release the action reports the error message. + assert {:error, message} = BDS.UI.SettingsForm.run_action("install_cli") + assert message =~ "packaged application" + + assert {:error, _unknown} = BDS.UI.SettingsForm.run_action("nope") + end + end +end