diff --git a/.github/PULL_REQUEST_TEMPLATE/template-submission.md b/.github/PULL_REQUEST_TEMPLATE/template-submission.md new file mode 100644 index 0000000..03ffa3e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/template-submission.md @@ -0,0 +1,42 @@ + + +## What's in this PR + +- [ ] New template: `templates///` +- [ ] Update to existing template: `templates///` (which one and why) + +## One-line pitch + +_What does this template do for its installers? Two sentences max._ + +## Checklist + +- [ ] I wrote this template, or have the author's explicit permission to submit it. +- [ ] `AGENTS.md` is present and tells any cross-agent what the project does and how to run it. +- [ ] `README.md` includes install, customize, and uninstall instructions. +- [ ] The bundle's `template.json` `contents` claim matches what's actually in the zip. +- [ ] Cron jobs (if any) ship paused and use self-contained prompts. +- [ ] No secrets in any file (API keys, tokens, hostnames, IPs, credentials). +- [ ] No writes to `config.yaml`, `auth.json`, or credential paths — v1 installer will refuse. +- [ ] `python3 tools/build-catalog.py --check` passes locally. +- [ ] I installed + uninstalled this template on my machine and verified the `AGENTS.md` contract works end-to-end. +- [ ] I did **not** edit `templates/catalog.json` — the maintainer regenerates it post-merge. + +## Testing notes + +_What did you run, what did you see? Paste the log output of the cron job +firing once, or the chat transcript of asking the agent to do the main +thing. Reviewers don't have your machine — show, don't tell._ + +## Screenshots (optional) + +_Drop screenshots of the installed dashboard, or the catalog detail page +rendered locally (`./scripts/catalog.sh preview && open /tmp/scarf-catalog-preview/templates//index.html`)._ diff --git a/.github/workflows/validate-template-pr.yml b/.github/workflows/validate-template-pr.yml new file mode 100644 index 0000000..05d4d5a --- /dev/null +++ b/.github/workflows/validate-template-pr.yml @@ -0,0 +1,74 @@ +# Validates `.scarftemplate` bundles on PRs that touch templates/. +# +# Mirrors the invariants `ProjectTemplateService.verifyClaims` enforces at +# install time. Runs the same Python script the maintainer uses locally +# (tools/build-catalog.py --check) so a bundle can't reach main unless the +# validator is happy. +# +# Also runs tools/test_build_catalog.py so drift between the validator and +# its own test suite is caught on the same PR. + +name: Validate template submissions + +on: + pull_request: + paths: + - 'templates/**' + - 'tools/build-catalog.py' + - 'tools/test_build_catalog.py' + - '.github/workflows/validate-template-pr.yml' + +permissions: + contents: read + pull-requests: write + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Full clone so we can diff against the PR base and scope + # --only to just the changed templates if we want to later. + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + # The validator is stdlib-only and tested against 3.9+ (the + # system Python on current macOS, what most maintainers run + # locally). CI uses 3.11 for faster cold-cache times on + # GitHub Actions runners — same stdlib APIs, same code paths. + python-version: '3.11' + + - name: Run validator unit tests + run: python3 tools/test_build_catalog.py -v + + - name: Validate every template + id: validate + run: | + set -o pipefail + python3 tools/build-catalog.py --check 2>&1 | tee /tmp/validator.log + + - name: Post failure comment + if: failure() && steps.validate.outcome == 'failure' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + let body = '## Template validation failed\n\n'; + try { + const log = fs.readFileSync('/tmp/validator.log', 'utf8'); + body += '```\n' + log.slice(-3000) + '\n```\n'; + } catch (e) { + body += 'See the failed job log for details.\n'; + } + body += '\nFix the issues above and push again — the check reruns automatically.\n'; + body += '\nLocal reproduction: `python3 tools/build-catalog.py --check`\n'; + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body, + }); diff --git a/CLAUDE.md b/CLAUDE.md index 4414b98..81e5c18 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,3 +85,82 @@ Public documentation lives in the GitHub wiki at https://github.com/awizemann/sc ## Hermes Version Targets Hermes v0.9.0 (v2026.4.13). Log lines may carry an optional `[session_id]` tag between the level and logger name — `HermesLogService.parseLine` treats the session tag as an optional capture group, so older untagged lines still parse. + +## Project Templates + +Scarf ships a `.scarftemplate` format (v1 as of 2.2.0) for sharing pre-packaged projects across users and machines. A bundle is a zip containing: + +- `template.json` — manifest (id, name, version, `contents` claim) +- `README.md` — shown in the install preview sheet +- `AGENTS.md` — required; the [Linux Foundation cross-agent instructions standard](https://agents.md/) — every template is agent-portable out of the box +- `dashboard.json` — copied to `/.scarf/dashboard.json` +- `instructions/…` — optional per-agent shims (`CLAUDE.md`, `GEMINI.md`, `.cursorrules`, `.github/copilot-instructions.md`) +- `skills//…` — optional; installed to `~/.hermes/skills/templates//` (namespaced so uninstall is `rm -rf` on one folder) +- `cron/jobs.json` — optional; registered via `hermes cron create` with a `[tmpl:] …` name prefix and immediately paused +- `memory/append.md` — optional; appended to `~/.hermes/memories/MEMORY.md` between `` markers + +Key services: [ProjectTemplateService.swift](scarf/scarf/Core/Services/ProjectTemplateService.swift) (inspect + validate + plan), [ProjectTemplateInstaller.swift](scarf/scarf/Core/Services/ProjectTemplateInstaller.swift) (execute a plan), [ProjectTemplateExporter.swift](scarf/scarf/Core/Services/ProjectTemplateExporter.swift) (build a bundle from a project), [ProjectTemplateUninstaller.swift](scarf/scarf/Core/Services/ProjectTemplateUninstaller.swift) (reverse an install using the lock file). UI in [Features/Templates/](scarf/scarf/Features/Templates/). The `scarf://install?url=` deep link + `file://` URLs for `.scarftemplate` files are handled by [TemplateURLRouter.swift](scarf/scarf/Core/Services/TemplateURLRouter.swift) and `onOpenURL` in `scarfApp.swift`. A `/.scarf/template.lock.json` uninstall manifest is written after every install and drives the uninstall flow. + +**Uninstall semantics:** driven by the lock file. Only files listed in `lock.projectFiles` are removed from the project dir; user-added files (e.g. a `sites.txt` created on first run) are preserved. If every file in the dir was installed by the template, the dir is removed too; otherwise the dir stays with just the user's files. Skills namespace is always removed wholesale (it's isolated). Cron jobs are removed via `hermes cron remove ` after resolving each lock-recorded name. Memory block is stripped between the `begin`/`end` markers, leaving the rest of MEMORY.md intact. No "undo" — uninstall is destructive; to re-install, run the install flow again. Uninstall UI lives on the project-list context menu and the dashboard header (only shown when the selected project has a lock file). + +**Never** let a template write to `config.yaml`, `auth.json`, sessions, or any credential path — the v1 installer refuses. If you extend the format, treat the preview sheet as load-bearing: the user's only trust boundary is that the sheet is honest about everything that's about to be written. + +### Template configuration (v2.3, schemaVersion 2) + +Templates can declare a typed configuration schema in `template.json`'s new `config` block. The installer renders a **Configure** step between the parent-directory pick and the preview sheet; values land at `/.scarf/config.json` (non-secret) and in the login Keychain (secret). A post-install **Configuration** button on the dashboard header (shown when `/.scarf/manifest.json` exists) opens the same form pre-filled for editing. + +Manifest shape: + +```json +{ + "schemaVersion": 2, + "contents": { "dashboard": true, "agentsMd": true, "config": 2 }, + "config": { + "schema": [ + {"key": "site_url", "type": "string", "label": "Site URL", "required": true}, + {"key": "api_token", "type": "secret", "label": "API Token", "required": true} + ], + "modelRecommendation": { + "preferred": "claude-sonnet-4.5", + "rationale": "Tool-heavy workload — reasoning helps." + } + } +} +``` + +Supported field types: `string`, `text`, `number`, `bool`, `enum` (with `options: [{value, label}]`), `list` (itemType `"string"` only in v1), `secret`. Type-specific constraints (`pattern`, `min`/`max`, `minLength`/`maxLength`, `minItems`/`maxItems`) are optional. `secret` fields **must not** declare a `default` — the validator refuses. + +Key services: [TemplateConfig.swift](scarf/scarf/Core/Models/TemplateConfig.swift) (schema + value models + Keychain ref helpers), [ProjectConfigKeychain.swift](scarf/scarf/Core/Services/ProjectConfigKeychain.swift) (thin `SecItemAdd`/`Copy`/`Delete` wrapper; the only Keychain user in Scarf today), [ProjectConfigService.swift](scarf/scarf/Core/Services/ProjectConfigService.swift) (load/save config.json, resolve secrets, cache manifest, validate schema + values). UI in [Features/Templates/ViewModels/TemplateConfigViewModel.swift](scarf/scarf/Features/Templates/ViewModels/TemplateConfigViewModel.swift) + [Features/Templates/Views/TemplateConfigSheet.swift](scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift). + +**Secret storage.** Keychain service name is `com.scarf.template.`, account is `:`. The path-hash suffix means two installs of the same template in different dirs don't collide on Keychain entries. Values in `config.json` are `"keychain://service/account"` URIs — never plaintext. The bytes hit the Keychain only on form commit, so cancelling never leaves orphan entries. + +**Uninstall.** `TemplateLock` v2 gains `config_keychain_items` and `config_fields` arrays. The uninstaller iterates each URI through `SecItemDelete` before removing the lock file. Absent items (user hand-cleaned) are no-ops. + +**Exporter.** Carries the *schema* from `/.scarf/manifest.json` through into exported bundles, never values. Exporting never leaks anyone's secrets. `schemaVersion` bumps to 2 only when a schema is forwarded; schema-less exports stay at 1. + +**Catalog site.** [tools/build-catalog.py](tools/build-catalog.py) mirrors the Swift schema validator. Each v2 template's `template.json` is copied into `.gh-pages-worktree/templates//manifest.json` and the site's `widgets.js` calls `ScarfWidgets.renderConfigSchema` to display the schema on the detail page (display-only — the form lives in-app). + +**Schema is Swift-primary.** If `TemplateConfigField.FieldType` gains a new case, update in order: `TemplateConfig.swift` (model + validation), `tools/build-catalog.py` (`SUPPORTED_CONFIG_FIELD_TYPES` + type-specific rules), `widgets.js` (`summariseConstraint`), `TemplateConfigSheet.swift` (new control subview), tests on both sides. Schema drift between validator + installer is the kind of bug users only notice after shipping. + +## Template Catalog + +Shipped community templates live at `templates///` (one level down — `templates/CONTRIBUTING.md` explains the submission flow for authors). The catalog site is generated from this directory and served at `awizemann.github.io/scarf/templates/` alongside the Sparkle appcast — the two coexist on the `gh-pages` branch but touch completely disjoint paths. + +Pipeline: + +- **Validator + regenerator:** [tools/build-catalog.py](tools/build-catalog.py) is stdlib-only Python (3.9+). It walks `templates/*/*/`, validates every `.scarftemplate` against its manifest claim (mirrors the Swift `ProjectTemplateService.verifyClaims` invariants), enforces a 5 MB bundle-size cap, scans for high-confidence secret patterns, checks `staging/` matches the built bundle byte-for-byte, and emits `templates/catalog.json`. Tested by [tools/test_build_catalog.py](tools/test_build_catalog.py) — 16 tests covering every validation path. +- **Wrapper:** [scripts/catalog.sh](scripts/catalog.sh) mirrors the `scripts/wiki.sh` shape with `check / build / preview / serve / publish` subcommands. `publish` runs a second-pass secret-scan against the rendered site before committing + pushing `gh-pages`. +- **Site source:** `site/index.html.tmpl` + `site/template.html.tmpl` are `{{TOKEN}}`-substitution templates. `site/widgets.js` (~300 lines of vanilla JS) is the dogfood — renders a `ProjectDashboard` JSON into HTML using the same widget vocabulary the Swift app uses, so each template's detail page shows a live preview of its post-install dashboard. +- **Install-URL hosting:** raw-served from `main` at `https://raw.githubusercontent.com/awizemann/scarf/main/templates///.scarftemplate`. No per-template Releases ceremony. +- **CI gate:** [.github/workflows/validate-template-pr.yml](.github/workflows/validate-template-pr.yml) runs the Python validator + its own test suite on every PR that touches `templates/`, the validator, or its tests. Failures post a comment on the PR with the last 3 KB of the validator log. + +Maintainer workflow on merge to main: + +```bash +./scripts/catalog.sh build # regenerate templates/catalog.json + .gh-pages-worktree/templates/ +./scripts/catalog.sh publish # secret-scan rendered output + commit + push gh-pages +``` + +Same cadence as `scripts/release.sh` (manual, auditable, no auto-deploy). Runs stay isolated: release.sh only touches `appcast.xml` on gh-pages; catalog.sh only touches `templates/` on gh-pages. Never push catalog output on a release cadence or vice versa. + +**Schema is Swift-primary.** When `ProjectDashboardWidget.type` gains a new case or `ProjectTemplateManifest` adds a field, update Swift first, then mirror into `tools/build-catalog.py` (`SUPPORTED_WIDGET_TYPES`, `_validate_manifest`, `_validate_contents_claim`) so the web validator stays honest. The Python test suite's real-bundle test catches drift on the example template but not on the full widget vocabulary — add a synthetic fixture to `test_build_catalog.py` for any new widget type. diff --git a/README.md b/README.md index 00ab008..2afe17c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,18 @@ Buy Me a Coffee

-## What's New in 2.1 +## What's New in 2.2 + +- **Project Templates** — Scarf projects can now travel. Package a project's dashboard, agent instructions, skills, cron jobs, and a typed configuration schema into a `.scarftemplate` bundle, hand it to anyone, and they install it in one click. Every bundle ships with a cross-agent `AGENTS.md` ([agents.md](https://agents.md/) standard) so the instructions work in Claude Code, Cursor, Codex, Aider, and the 20+ other agents that read it natively. Browser-based one-click install via `scarf://install?url=…` deep links. Export / Install from File / Install from URL live under the new **Templates** menu in the Projects toolbar. +- **Typed configuration with Keychain-backed secrets** — Templates declare a schema with seven field types (`string`, `text`, `number`, `bool`, `enum`, `list`, `secret`). A **Configure** step in the install flow renders the form, routes secrets to the macOS Keychain, and drops non-secret values into `/.scarf/config.json`. A slider icon in the dashboard header opens the same form post-install for edits — rotate a token, change a site, toggle a feature, and the next cron run picks it up. +- **Public template catalog** — [awizemann.github.io/scarf/templates/](https://awizemann.github.io/scarf/templates/) is a static catalog site generated from `templates///` in this repo. Each template has a detail page with a live dashboard preview, the schema rendered with constraint summaries, and a one-click install button. Community submissions go through a CI-enforced Python validator that mirrors the Swift-side invariants. +- **Preview-before-apply** — Every install shows a preview sheet listing the exact project directory that will be created, every file inside it, every skill that will be namespaced, every cron job that will be registered (paused by default), every Keychain secret that will be written, and a live diff of any memory appendix. Markdown fields render inline. Nothing writes until you click Install. +- **Site tab** — A dashboard with at least one `webview` widget gets a second tab next to Dashboard. The example `awizemann/site-status-checker` template uses this to render whatever URL you configured as your first watched site, updating on every cron run. +- **Safe-by-design** — Skills install into `~/.hermes/skills/templates//` so they never collide with your own. Cron jobs carry a `[tmpl:]` tag and start paused. A `template.lock.json` records every file, cron job, Keychain ref, and memory block for one-click uninstall. Exports carry the configuration schema but never the user's values — safe on projects with live config. Templates **never** touch `config.yaml`, `auth.json`, sessions, or credentials. + +See the full [v2.2.0 release notes](https://github.com/awizemann/scarf/releases/tag/v2.2.0) and the [Project Templates wiki page](https://github.com/awizemann/scarf/wiki/Project-Templates). + +### Previously, in 2.1 - **Seven languages** — Full UI translations for Simplified Chinese, German, French, Spanish, Japanese, and Brazilian Portuguese on top of English. Scarf respects the system language by default; override per-app via **System Settings → Language & Region → Apps → Scarf**. Contributor workflow for adding more locales is documented in [CONTRIBUTING.md → Adding a Language](CONTRIBUTING.md#adding-a-language). - **Locale-aware number formatting** — Currency, byte sizes, compact token counts (`15K`, `1.5M`), and day-of-week charts now follow the user's locale instead of POSIX / English defaults. @@ -387,6 +398,16 @@ Signing prerequisites (one-time): - `scarf-notary` keychain profile registered via `xcrun notarytool store-credentials` - Sparkle EdDSA private key in Keychain item `https://sparkle-project.org` (back this up — without it, shipped apps can never receive updates) +## Template Catalog + +Community-contributed Scarf project templates live under [`templates/`](templates/) in this repo and are browsable at **[awizemann.github.io/scarf/templates/](https://awizemann.github.io/scarf/templates/)** with live dashboard previews and one-click `scarf://install?url=…` links. + +- **Install from the web** — click "Install with Scarf" on any template's detail page; the app takes over from there. +- **Install from a local file** — Scarf → Projects → Templates → Install from File…, or double-click any `.scarftemplate` in Finder. +- **Author a template** — see [`templates/CONTRIBUTING.md`](templates/CONTRIBUTING.md) for the full walkthrough. Fork, drop a template under `templates///`, open a PR; CI validates the bundle automatically. + +The catalog's site is a static HTML + vanilla JS build generated by [`tools/build-catalog.py`](tools/build-catalog.py) and driven by [`scripts/catalog.sh`](scripts/catalog.sh) (check / build / preview / publish). Appcast and main landing page are independent — updating the catalog never disturbs Sparkle. + ## Contributing Contributions are welcome. Please open an issue to discuss what you'd like to change before submitting a PR. @@ -397,6 +418,8 @@ Contributions are welcome. Please open an issue to discuss what you'd like to ch 4. Push to the branch (`git push origin feature/my-feature`) 5. Open a Pull Request +For template submissions, see [`templates/CONTRIBUTING.md`](templates/CONTRIBUTING.md) — same flow, with a catalog-specific checklist + automated CI validation. + ## Support If you find Scarf useful, consider buying me a coffee. diff --git a/releases/v2.2.0/RELEASE_NOTES.md b/releases/v2.2.0/RELEASE_NOTES.md new file mode 100644 index 0000000..4d618a3 --- /dev/null +++ b/releases/v2.2.0/RELEASE_NOTES.md @@ -0,0 +1,94 @@ +## What's New in 2.2.0 + +Scarf projects can now travel. This release introduces **Project Templates** — a shareable `.scarftemplate` bundle format that packages a project's dashboard, agent instructions, skills, cron jobs, and a typed configuration schema into a single file anyone can install with one click. Bundles are agent-portable by design: every template ships with a cross-agent [`AGENTS.md`](https://agents.md/) so the instructions work natively in Claude Code, Cursor, Codex, Aider, Jules, Copilot, Zed, and every other agent that reads the Linux Foundation standard. + +This is also the first release to ship a public **template catalog website** — a static site generated from `templates///` in this repo, previewed at [awizemann.github.io/scarf/templates/](https://awizemann.github.io/scarf/templates/), with a CI-enforced validator for community submissions. + +### Project Templates + +- **Bundle format: `.scarftemplate`.** A zip carrying a `template.json` manifest, the project's dashboard, a required `AGENTS.md` (the [Linux Foundation cross-agent instructions standard](https://agents.md/) — reads natively in Claude Code, Cursor, Codex, Aider, Jules, Copilot, Zed, and more), a README shown in the installer, optional per-agent instruction shims (`CLAUDE.md`, `GEMINI.md`, `.cursorrules`, `.github/copilot-instructions.md`), optional namespaced skills, optional cron job definitions, and an optional memory appendix. +- **Install preview sheet.** Before anything touches disk, Scarf shows you the exact project directory that will be created, every file inside it, every skill that will be namespaced under `~/.hermes/skills/templates//`, every cron job that will be registered (always paused — you enable each one manually), and a live diff of the memory appendix against your existing `MEMORY.md`. Markdown fields — the README, field descriptions, cron prompts — render inline. The manifest's content claim is cross-checked against the actual zip entries so a bundle can't hide files from the preview. +- **`scarf://install?url=…` deep links.** Register Scarf as the handler for the `scarf` URL scheme so a future catalog site can link one-click installs straight into the app. Only `https://` payloads are accepted; `file://`, `javascript:`, and `http://` are refused on principle. A 50 MB size cap keeps a malicious link from exhausting disk. The URL never auto-installs — the preview sheet is always user-confirmed. +- **Install-time token substitution.** Template authors use `{{PROJECT_DIR}}`, `{{TEMPLATE_ID}}`, and `{{TEMPLATE_SLUG}}` placeholders in cron prompts; the installer resolves them to absolute paths at install time so the registered cron job works regardless of where Hermes sets CWD. +- **Export any project as a template.** Select a project, open the new Templates menu in the Projects toolbar, fill in a handful of fields (id, name, version, description, optional author + category + tags), tick the skills and cron jobs you want to include, optionally drop in a memory snippet, and save. The exporter carries the authored configuration schema forward but **never** the user's values — exports are safe on projects with live config. +- **No-overwrite, reversible by design.** Installed templates drop a `/.scarf/template.lock.json` recording exactly what they wrote — every project file, skill path, cron job name, memory block id, and Keychain reference. Installing the same template id twice is refused at the preview step so you don't accidentally double-append to `MEMORY.md`. +- **Safe globals.** Skills install to `~/.hermes/skills/templates///` so they never collide with your own skills. Cron jobs are prefixed with `[tmpl:]` and start paused. The installer **never** touches `~/.hermes/config.yaml`, `auth.json`, sessions, or any credential-bearing path. + +### Template Configuration (schemaVersion 2) + +Templates can now declare a typed configuration schema that drives a form step during install — no more "edit a `sites.txt` file to get started." + +- **Typed field vocabulary.** Seven field types: `string`, `text` (multiline), `number` (with `min`/`max`), `bool`, `enum` (with `{value, label}` options), `list` (of strings, with `minItems`/`maxItems`), and `secret` (routed to the macOS Keychain). Constraints per type — `pattern` for regex, `minLength`/`maxLength` for text, etc. — are enforced at install and at edit time. +- **Configure step in the install flow.** If the template declares a schema, a **Configure** screen is inserted between "pick parent directory" and the preview sheet. Non-secret values land in `/.scarf/config.json`; secrets land in the macOS Keychain with a service name of `com.scarf.template.` and an account keyed to the project-directory hash (so two installs of the same template in different dirs don't collide on Keychain entries). +- **Post-install Configuration editor.** A slider icon in the dashboard header opens the same form pre-filled with the current values. Change a site, rotate a token, toggle a feature — the cron job picks up the new values on its next run. Secrets are never echoed back ("Saved in Keychain — leave empty to keep the stored value"). +- **Model recommendations.** Templates can suggest a preferred model (`claude-sonnet-4.5`, `claude-haiku-4`, `gpt-4.1`, etc.) with a rationale. Scarf surfaces the recommendation in the configure sheet without auto-switching your active model — always your call. +- **Secrets are tracked in the lock file.** Uninstalling a template runs `SecItemDelete` on every Keychain ref recorded at install, so a full clean-up leaves nothing behind. Absent entries (user already cleaned them) are no-ops. + +### Template Catalog + +A Sparkle-style pipeline for community-contributed templates, living on the same `gh-pages` branch as the auto-update feed. + +- **Static site.** [awizemann.github.io/scarf/templates/](https://awizemann.github.io/scarf/templates/) — generated from every `templates///` directory. Each template gets a detail page showing the README, a live preview of the post-install dashboard, and the configuration schema rendered with human-readable constraint summaries. One-click install via the `scarf://install?url=…` button. +- **Stdlib-only Python validator.** `tools/build-catalog.py` is a no-external-dependencies Python script that mirrors the Swift-side schema and validation invariants (supported widget types, supported field types, `contents` claim verification, secret-with-default rejection, bundle-size cap, high-confidence secret patterns). Run it locally with `./scripts/catalog.sh check` before submitting a PR. +- **CI gate on PRs.** [`.github/workflows/validate-template-pr.yml`](https://github.com/awizemann/scarf/blob/main/.github/workflows/validate-template-pr.yml) runs the validator + its 24-test suite on every PR touching `templates/`, the validator itself, or its tests. Failing PRs get an inline comment with the last 3 KB of the validator output; passing PRs get a tailored checklist naming the specific template directory being changed. +- **Install-URL hosting.** Bundles are raw-served from `main` at `https://raw.githubusercontent.com/awizemann/scarf/main/templates///.scarftemplate`. No per-template GitHub Releases ceremony. +- **Dogfood: the site uses Scarf's dashboard format.** `site/widgets.js` is ~300 lines of vanilla JS that renders a `ProjectDashboard` JSON using the same widget vocabulary the app uses, so each detail page's "live preview" is the actual dashboard the user will get. + +### Example template: `awizemann/site-status-checker` + +Ships as the first catalog entry and exercises every v2.2 surface. [See it in the catalog →](https://awizemann.github.io/scarf/templates/awizemann-site-status-checker/) + +- Configure step asks for a list of URLs and a per-URL timeout. +- A paused cron job runs daily at 09:00 (editable from the Cron sidebar), does HTTP GETs with 3-redirect follow, writes a timestamped results table to `status-log.md`, updates the dashboard's Sites Up / Sites Down / Last Checked stat widgets plus the Watched Sites list, and rewrites the Site tab's webview URL to the first configured site. +- Works in any agent — the `AGENTS.md` is the single source of truth; no per-agent shim needed. + +### Site tab + +A dashboard with at least one `webview` widget now exposes a **Site** tab next to Dashboard. Useful for templates that watch something renderable (a site, a preview endpoint, a Grafana panel). The `site-status-checker` example rewrites the webview URL to the first configured site on every cron run, so the tab stays in sync with live config. + +### Using templates + +- **Install from file:** Projects → Templates → *Install from File…*, pick a `.scarftemplate` from disk. +- **Install from URL:** Projects → Templates → *Install from URL…*, paste an https URL. +- **Install from the web:** click any `scarf://install?url=…` link in a browser. +- **Export:** select a project → Projects → Templates → *Export "<name>" as Template…*, fill the form, save. +- **Edit config post-install:** slider icon in the dashboard header. +- **Uninstall:** right-click the project in the sidebar → *Uninstall Template (remove installed files)…*, or click the uninstall icon in the dashboard header. The preview sheet lists every file, cron job, Keychain secret, and memory block that will be removed, plus every user-created file that will be preserved. + +### UX clarifications + +- **Remove from List vs. Uninstall Template.** Sidebar context-menu labels clarified so you can see at a glance whether a click is destructive. *Remove from List (keep files)…* is registry-only — nothing on disk is touched, cron jobs stay, Keychain secrets stay. A confirmation dialog spells this out before the click lands. *Uninstall Template (remove installed files)…* is the full, lock-driven cleanup. +- **Post-uninstall "folder kept" banner.** When the uninstaller preserves the project directory because the cron wrote a `status-log.md` (or the user dropped files in there), the success view now explicitly lists the preserved paths with a pointer to delete the folder from Finder if desired. +- **Run Now no longer blocks on agent runs.** The Cron sidebar's Run Now button used to show a "Run failed" toast whenever an agent job ran longer than 60 s — even when the job was finishing correctly in the background. Run Now now shows "Agent started — dashboard will update when it finishes" immediately and the dashboard watcher picks up the completed state when it lands (timeout bumped to 300 s for the catch-stuck-process case). + +### Uninstall + +- **One-click uninstall** driven by `template.lock.json`. The preview sheet lists every file, cron job, Keychain ref, and memory block that will be removed, and every user-created file that will be preserved. +- **User content is never removed.** Files you (or the agent) added to the project dir after install — like a `sites.txt` or `status-log.md` — are detected and listed as "keep" in the preview. The project directory itself is removed only if nothing user-owned is left inside. +- **Clean global state.** The isolated `~/.hermes/skills/templates//` namespace is removed wholesale. Tagged cron jobs are removed via `hermes cron remove`. Every recorded Keychain ref is cleared via `SecItemDelete`. The memory block between the `` markers is stripped, leaving the rest of MEMORY.md intact. The project registry entry is removed last. +- **No undo.** Uninstall is destructive — to reinstall, run the install flow again. + +### Under the hood + +- New models in `Core/Models/ProjectTemplate.swift` (manifest, inspection, install plan, lock file v2) and `Core/Models/TemplateConfig.swift` (schema + typed values + Keychain ref model). +- `Core/Services/ProjectTemplateService.swift` unzips, parses, and validates; `ProjectTemplateInstaller.swift` executes the plan with preflight + fail-fast semantics; `ProjectTemplateUninstaller.swift` reverses an install driven by the lock file; `ProjectTemplateExporter.swift` builds bundles from a live project + selections. +- `Core/Services/ProjectConfigService.swift` owns load/save/validation of `/.scarf/config.json` + secret resolution; `Core/Services/ProjectConfigKeychain.swift` is the thin `SecItemAdd`/`Copy`/`Delete` wrapper (the only Keychain consumer in Scarf today). +- `Core/Services/TemplateURLRouter.swift` is the process-wide landing pad for `scarf://` URLs so a cold-launch browser click still reaches the install sheet. +- New Swift Testing suites covering 57 tests across the service / installer / uninstaller / exporter / config / Keychain / URL-router paths. +- New Python validator (`tools/build-catalog.py`) + test suite (`tools/test_build_catalog.py`, 24 tests) mirrors the Swift invariants for the CI gate and the site generator. Schema is Swift-primary — additions go to Swift first, Python mirrors. +- `scripts/catalog.sh` wraps the validator with `check / build / preview / serve / publish` subcommands that parallel the `scripts/release.sh` shape. + +### Migrating from 2.1.x + +Sparkle will offer the update automatically. No config migration needed. Existing projects are untouched — templates are additive. If you had a v2.2.0-dev install of the earlier `project-templates` branch, uninstall and reinstall any previously-installed templates to pick up the schema-version-2 lock file. + +### Documentation + +- [Project Templates wiki page](https://github.com/awizemann/scarf/wiki/Project-Templates) — installing, exporting, configuring, authoring, uninstalling. +- [Catalog site](https://awizemann.github.io/scarf/templates/) — the public catalog with live dashboard previews. +- [`templates/CONTRIBUTING.md`](https://github.com/awizemann/scarf/blob/main/templates/CONTRIBUTING.md) — how to submit a template via PR. +- [Architecture notes in root `CLAUDE.md`](https://github.com/awizemann/scarf/blob/main/CLAUDE.md#project-templates) — service-layer map, Keychain scheme, schema-drift discipline. + +### Thanks + +Thanks to everyone who tested drafts of the install flow, caught the "Run Now blocks on agent" bug, and pushed on the Remove-vs-Uninstall UX until it was clear. A 2.3 follow-up will extend the catalog validator to enforce per-field-type constraints at PR-time (currently enforced on install but not at submission). diff --git a/scarf/scarf/Core/Models/ProjectTemplate.swift b/scarf/scarf/Core/Models/ProjectTemplate.swift new file mode 100644 index 0000000..3f8702b --- /dev/null +++ b/scarf/scarf/Core/Models/ProjectTemplate.swift @@ -0,0 +1,335 @@ +import Foundation + +// MARK: - Manifest (what lives inside the .scarftemplate zip) + +/// On-disk manifest for a Scarf project template. Shipped as `template.json` +/// at the root of a `.scarftemplate` (zip) bundle. +/// +/// The `contents` block is a claim the author makes about what the bundle +/// ships; the installer verifies the claim against the actual unpacked files +/// before showing the preview sheet so a malicious bundle can't hide extra +/// files from the user. +struct ProjectTemplateManifest: Codable, Sendable, Equatable { + let schemaVersion: Int + let id: String + let name: String + let version: String + let minScarfVersion: String? + let minHermesVersion: String? + let author: TemplateAuthor? + let description: String + let category: String? + let tags: [String]? + let icon: String? + let screenshots: [String]? + let contents: TemplateContents + /// Optional configuration schema (added in manifest schemaVersion 2). + /// When present, the installer presents a form during install and + /// writes values to `/.scarf/config.json` + the Keychain. + /// Schema-v1 manifests omit this field entirely — Codable's + /// optional-field decoding keeps them working unchanged. + let config: TemplateConfigSchema? + + /// Filesystem-safe slug derived from `id` (`"owner/name"` → `"owner-name"`). + /// Used for the install directory name, skills namespace, and cron-job tag. + nonisolated var slug: String { + let ascii = id.unicodeScalars.map { scalar -> Character in + let c = Character(scalar) + if c.isLetter || c.isNumber || c == "-" || c == "_" { return c } + return "-" + } + let collapsed = String(ascii) + .split(separator: "-", omittingEmptySubsequences: true) + .joined(separator: "-") + return collapsed.isEmpty ? "template" : collapsed + } +} + +struct TemplateAuthor: Codable, Sendable, Equatable { + let name: String + let url: String? +} + +struct TemplateContents: Codable, Sendable, Equatable { + let dashboard: Bool + let agentsMd: Bool + let instructions: [String]? + let skills: [String]? + let cron: Int? + let memory: TemplateMemoryClaim? + /// Number of configuration fields the template ships (schemaVersion 2+). + /// Cross-checked against `manifest.config?.fields.count` by the + /// validator so a bundle can't hide a schema from the preview. + /// `nil` or `0` means schema-less (v1-compatible behaviour). + let config: Int? +} + +struct TemplateMemoryClaim: Codable, Sendable, Equatable { + let append: Bool +} + +// MARK: - Inspection (what we learn by unpacking the zip) + +/// Result of unpacking a `.scarftemplate` into a temp directory and validating +/// it. Callers hand this to `buildInstallPlan` to produce the concrete +/// filesystem plan. +struct TemplateInspection: Sendable { + let manifest: ProjectTemplateManifest + /// Absolute path to the temp directory holding the unpacked bundle. The + /// installer reads files from here; the caller is responsible for + /// cleaning it up after install (or cancel). + let unpackedDir: String + /// Every file found in the unpacked dir, as paths relative to + /// `unpackedDir`. Verified against the manifest's `contents` claim. + let files: [String] + /// Parsed cron jobs (may be empty even if the manifest claims some — + /// verification catches that mismatch). + let cronJobs: [TemplateCronJobSpec] +} + +/// The subset of a Hermes cron job that a template can ship. Only the fields +/// the `hermes cron create` CLI accepts are included; runtime state +/// (`enabled`, `state`, `next_run_at`, …) is deliberately omitted so a +/// template can't arrive already-running. +struct TemplateCronJobSpec: Codable, Sendable, Equatable { + let name: String + let schedule: String + let prompt: String? + let deliver: String? + let skills: [String]? + let repeatCount: Int? + + enum CodingKeys: String, CodingKey { + case name, schedule, prompt, deliver, skills + case repeatCount = "repeat" + } +} + +// MARK: - Install Plan (the preview sheet reads this) + +/// Concrete, reviewed-before-apply filesystem operations the installer will +/// perform. Every side effect the installer can cause is represented here so +/// the preview sheet is an honest accounting of what's about to happen. +struct TemplateInstallPlan: Sendable { + let manifest: ProjectTemplateManifest + let unpackedDir: String + + /// Absolute path of the new project directory. Installer refuses if this + /// already exists. + let projectDir: String + /// Files that will be created under `projectDir`, keyed by relative path. + let projectFiles: [TemplateFileCopy] + + /// Absolute path of the skills namespace dir + /// (`~/.hermes/skills/templates//`). Created if skills are present. + let skillsNamespaceDir: String? + /// Files that will be created under the skills namespace dir. + let skillsFiles: [TemplateFileCopy] + + /// Cron job definitions to register via `hermes cron create`. Each job's + /// name is already prefixed with the template tag. All will be paused + /// immediately after creation. + let cronJobs: [TemplateCronJobSpec] + + /// Memory appendix text (already wrapped in begin/end markers). `nil` + /// means no memory write happens. + let memoryAppendix: String? + /// Target memory path (`~/.hermes/memories/MEMORY.md`). Only used when + /// `memoryAppendix` is non-nil. + let memoryPath: String + + /// `ProjectEntry.name` that will be appended to the projects registry. + let projectRegistryName: String + + /// Configuration schema declared by the template (manifest schemaVersion 2). + /// `nil` means the template is schema-less — the installer skips the + /// config sheet and writes no `.scarf/config.json` or manifest cache. + let configSchema: TemplateConfigSchema? + + /// Values the user entered in the configure sheet. Populated by the + /// VM just before `install()` runs; empty when `configSchema` is nil. + /// Secrets appear here as `.keychainRef(...)` — the bytes themselves + /// were routed straight from the form field into the Keychain and + /// never held in memory past that point. + var configValues: [String: TemplateConfigValue] + + /// Path at which the installer will stash a copy of `template.json` + /// so the post-install Configuration editor can render the form + /// offline. `nil` when `configSchema` is nil. + let manifestCachePath: String? + + /// Convenience: total number of writes (files + cron jobs + optional + /// memory append + registry append + optional config.json + one + /// entry per secret written to the Keychain). Displayed in the + /// preview sheet. + nonisolated var totalWriteCount: Int { + let configFileCount = (configSchema?.isEmpty ?? true) ? 0 : 1 + let secretCount = configValues.values.filter { + if case .keychainRef = $0 { return true } else { return false } + }.count + return projectFiles.count + + skillsFiles.count + + cronJobs.count + + (memoryAppendix == nil ? 0 : 1) + + 1 // registry entry + + configFileCount + + secretCount + } +} + +/// A single file to copy from the unpacked bundle into a target directory. +struct TemplateFileCopy: Sendable, Equatable { + /// Path inside `unpackedDir`, e.g. `"AGENTS.md"` or + /// `"skills/timer/SKILL.md"`. + let sourceRelativePath: String + /// Absolute path where the file should land. + let destinationPath: String +} + +// MARK: - Lock file (uninstall manifest, dropped into /.scarf/) + +/// Dropped at `/.scarf/template.lock.json` after a successful +/// install. Records exactly what was written so a future "Uninstall Template" +/// action can reverse it without guessing. +struct TemplateLock: Codable, Sendable { + let templateId: String + let templateVersion: String + let templateName: String + let installedAt: String + let projectFiles: [String] + let skillsNamespaceDir: String? + let skillsFiles: [String] + let cronJobNames: [String] + let memoryBlockId: String? + /// Every `keychain://service/account` URI the installer stored in + /// the Keychain for this project's secret fields. Empty/nil for + /// schema-less (v1-style) installs. The uninstaller iterates this + /// list and calls `SecItemDelete` for each entry; absent on older + /// lock files so Codable's optional decoding keeps pre-2.3 installs + /// uninstallable. + let configKeychainItems: [String]? + /// Field keys the installer wrote to `/.scarf/config.json`. + /// Informational — the actual removal of config.json rides on + /// `projectFiles`. Optional for back-compat. + let configFields: [String]? + + enum CodingKeys: String, CodingKey { + case templateId = "template_id" + case templateVersion = "template_version" + case templateName = "template_name" + case installedAt = "installed_at" + case projectFiles = "project_files" + case skillsNamespaceDir = "skills_namespace_dir" + case skillsFiles = "skills_files" + case cronJobNames = "cron_job_names" + case memoryBlockId = "memory_block_id" + case configKeychainItems = "config_keychain_items" + case configFields = "config_fields" + } +} + +// MARK: - Uninstall Plan (the uninstall-preview sheet reads this) + +/// Symmetric with `TemplateInstallPlan` but for removal. Built from the +/// `/.scarf/template.lock.json` the installer wrote. The preview +/// sheet lists every path the uninstall would touch; the uninstaller +/// executes the listed ops and nothing else. +struct TemplateUninstallPlan: Sendable { + /// The parsed lock file that seeded this plan. Kept so the sheet can + /// display the template id, version, and install timestamp. + let lock: TemplateLock + /// The registry entry that will be removed on success. + let project: ProjectEntry + + /// Lock-tracked files still present on disk that will be removed. + let projectFilesToRemove: [String] + /// Lock-tracked files that were already missing (e.g. user deleted them + /// after install). Shown in the sheet so the user isn't surprised that + /// a file isn't removed; uninstaller skips these. + let projectFilesAlreadyGone: [String] + /// User-added files/dirs in the project dir that are NOT in the lock. + /// These are preserved — the sheet lists them so the user knows the + /// project dir stays if any exist. + let extraProjectEntries: [String] + /// If `true`, the project dir ends up empty after removal and will be + /// removed along with its files. `false` means user content lives in + /// the dir and we leave it. + let projectDirBecomesEmpty: Bool + + /// Lock-recorded skills namespace dir. `nil` means the template never + /// installed skills. Uninstaller removes the entire dir recursively. + let skillsNamespaceDir: String? + + /// Cron jobs that will be removed, as (id, name) pairs. Ids were looked + /// up at plan time by matching lock names against the live cron list. + let cronJobsToRemove: [(id: String, name: String)] + /// Names recorded in the lock that we couldn't find in the current cron + /// list (user-deleted, renamed, etc.). Shown in the sheet; skipped on + /// uninstall. + let cronJobsAlreadyGone: [String] + + /// `true` if MEMORY.md still contains the template's begin/end markers + /// and those bytes will be stripped on uninstall. `false` means no + /// memory block was ever installed OR the user removed it by hand. + let memoryBlockPresent: Bool + /// Hermes-side path to MEMORY.md. Only touched when + /// `memoryBlockPresent` is true. + let memoryPath: String + + nonisolated var totalRemoveCount: Int { + projectFilesToRemove.count + + (skillsNamespaceDir == nil ? 0 : 1) + + cronJobsToRemove.count + + (memoryBlockPresent ? 1 : 0) + + 1 // registry entry + } +} + +// MARK: - Errors + +enum ProjectTemplateError: LocalizedError, Sendable { + case unzipFailed(String) + case manifestMissing + case manifestParseFailed(String) + case unsupportedSchemaVersion(Int) + case requiredFileMissing(String) + case contentClaimMismatch(String) + case projectDirExists(String) + case conflictingFile(String) + case memoryBlockAlreadyExists(String) + case cronCreateFailed(job: String, output: String) + case unsafeZipEntry(String) + case lockFileMissing(String) + case lockFileParseFailed(String) + + var errorDescription: String? { + switch self { + case .unzipFailed(let s): + return "Couldn't unpack template archive: \(s)" + case .manifestMissing: + return "Template is missing template.json at the archive root." + case .manifestParseFailed(let s): + return "Template manifest couldn't be parsed: \(s)" + case .unsupportedSchemaVersion(let v): + return "Template uses schemaVersion \(v), which this version of Scarf doesn't understand." + case .requiredFileMissing(let f): + return "Template is missing a required file: \(f)" + case .contentClaimMismatch(let s): + return "Template manifest doesn't match its contents: \(s)" + case .projectDirExists(let p): + return "A directory already exists at \(p). Refusing to overwrite — choose a different parent folder." + case .conflictingFile(let p): + return "An existing file would be overwritten at \(p). Refusing to clobber." + case .memoryBlockAlreadyExists(let id): + return "A memory block for template '\(id)' already exists in MEMORY.md. Remove it first or install a fresh copy." + case .cronCreateFailed(let job, let output): + return "Failed to register cron job '\(job)': \(output)" + case .unsafeZipEntry(let p): + return "Template archive contains an unsafe entry: \(p)" + case .lockFileMissing(let path): + return "No template.lock.json found at \(path). This project wasn't installed by Scarf's template system — remove it by hand." + case .lockFileParseFailed(let s): + return "Couldn't read template.lock.json: \(s)" + } + } +} diff --git a/scarf/scarf/Core/Models/TemplateConfig.swift b/scarf/scarf/Core/Models/TemplateConfig.swift new file mode 100644 index 0000000..458ce8d --- /dev/null +++ b/scarf/scarf/Core/Models/TemplateConfig.swift @@ -0,0 +1,278 @@ +import Foundation + +// MARK: - Schema (ships inside template.json as manifest.config) + +/// Author-declared configuration schema for a template. Published as the +/// `config` block of `template.json` (manifest schemaVersion 2). Users fill +/// in values at install time via `TemplateConfigSheet`; values land in +/// `/.scarf/config.json` with secrets resolved through the +/// macOS Keychain. +struct TemplateConfigSchema: Codable, Sendable, Equatable { + let fields: [TemplateConfigField] + let modelRecommendation: TemplateModelRecommendation? + + enum CodingKeys: String, CodingKey { + case fields = "schema" + case modelRecommendation + } + + nonisolated var isEmpty: Bool { fields.isEmpty } + + /// Fast lookup by key. Validators guarantee keys are unique within a + /// schema at manifest-parse time, so this is safe. + nonisolated func field(for key: String) -> TemplateConfigField? { + fields.first { $0.key == key } + } +} + +/// One configurable field the user fills in. Discriminated by `type`. +/// We keep one flat struct rather than an enum-associated-value encoding +/// so JSON reads cleanly as a record and authors can hand-edit manifests +/// without fighting Swift's `"case"` discriminator syntax. +struct TemplateConfigField: Codable, Sendable, Equatable, Identifiable { + nonisolated var id: String { key } + + let key: String + let type: FieldType + let label: String + let description: String? + let required: Bool + let placeholder: String? + + // Type-specific constraints — all optional. The validator enforces + // only the ones that apply to `type`; extras are ignored. + let defaultValue: TemplateConfigValue? + let options: [EnumOption]? // type == .enum + let minLength: Int? // type == .string / .text + let maxLength: Int? + let pattern: String? // type == .string (regex) + let minNumber: Double? // type == .number + let maxNumber: Double? + let step: Double? + let itemType: String? // type == .list — only "string" supported in v1 + let minItems: Int? + let maxItems: Int? + + enum CodingKeys: String, CodingKey { + case key, type, label, description, required, placeholder + case defaultValue = "default" + case options + case minLength, maxLength, pattern + case minNumber = "min" + case maxNumber = "max" + case step + case itemType, minItems, maxItems + } + + enum FieldType: String, Codable, Sendable, Equatable { + case string + case text + case number + case bool + case `enum` + case list + case secret + } + + /// One option of an `enum` field. `value` is what ends up in + /// `config.json`; `label` is the human-readable text shown in the UI. + struct EnumOption: Codable, Sendable, Equatable, Identifiable { + nonisolated var id: String { value } + let value: String + let label: String + } +} + +/// Author's model-of-choice hint, shown in the install preview + on the +/// catalog detail page. Purely advisory — Scarf never auto-switches the +/// active model. Individual cron jobs can override via +/// `HermesCronJob.model` if the author wants enforcement. +struct TemplateModelRecommendation: Codable, Sendable, Equatable { + let preferred: String + let rationale: String? + let alternatives: [String]? +} + +// MARK: - Values (what lands in config.json and the Keychain) + +/// One configured value. Secrets don't carry their raw bytes — only a +/// Keychain reference of the form `"keychain:///"` so +/// serialising config.json to disk never leaks the secret into git or +/// into backups. +enum TemplateConfigValue: Codable, Sendable, Equatable { + case string(String) + case number(Double) + case bool(Bool) + case list([String]) + case keychainRef(String) + + /// Convenience: the string representation suitable for display or + /// for writing into a placeholder that the agent reads. Keychain + /// refs return the ref string, not the resolved secret — callers + /// resolve through `ProjectConfigKeychain` explicitly when they + /// actually need the plaintext. + nonisolated var displayString: String { + switch self { + case .string(let s): return s + case .number(let n): + return n.truncatingRemainder(dividingBy: 1) == 0 + ? String(Int(n)) + : String(n) + case .bool(let b): return b ? "true" : "false" + case .list(let items): return items.joined(separator: ", ") + case .keychainRef(let ref): return ref + } + } + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let s = try? container.decode(String.self) { + // Preserve the keychain:// scheme so secrets round-trip as + // references, not as plaintext. + if s.hasPrefix("keychain://") { + self = .keychainRef(s) + } else { + self = .string(s) + } + } else if let b = try? container.decode(Bool.self) { + self = .bool(b) + } else if let n = try? container.decode(Double.self) { + self = .number(n) + } else if let arr = try? container.decode([String].self) { + self = .list(arr) + } else { + throw DecodingError.typeMismatch( + TemplateConfigValue.self, + .init(codingPath: decoder.codingPath, + debugDescription: "Expected String, Bool, Number, or [String]") + ) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .string(let s): try container.encode(s) + case .number(let n): try container.encode(n) + case .bool(let b): try container.encode(b) + case .list(let items): try container.encode(items) + case .keychainRef(let ref): try container.encode(ref) + } + } +} + +// MARK: - On-disk shape (what's in /.scarf/config.json) + +/// The JSON file the installer writes + the editor reads. Non-secret +/// values appear inline; secrets are `"keychain:///"` +/// references that `ProjectConfigService` resolves through the Keychain +/// on demand. +struct ProjectConfigFile: Codable, Sendable { + let schemaVersion: Int + let templateId: String + var values: [String: TemplateConfigValue] + let updatedAt: String + + enum CodingKeys: String, CodingKey { + case schemaVersion + case templateId + case values + case updatedAt + } +} + +// MARK: - Keychain reference helpers + +/// One secret stored via `ProjectConfigKeychain`. We derive both halves +/// (service + account) from the template slug + project-path hash so two +/// installs of the same template in different dirs don't collide in the +/// login Keychain. +struct TemplateKeychainRef: Sendable, Equatable { + /// Macro service name, e.g. `com.scarf.template.awizemann-site-status-checker`. + let service: String + /// Account name: `:`. The hash suffix + /// guarantees uniqueness across multiple installs of the same template. + let account: String + + /// `"keychain:///"` — what lands in `config.json`. + nonisolated var uri: String { "keychain://\(service)/\(account)" } + + /// Parse a `keychain://…` URI back into a ref. Returns `nil` when the + /// input isn't well-formed so callers can distinguish a missing ref + /// from a malformed one. + nonisolated static func parse(_ uri: String) -> TemplateKeychainRef? { + guard uri.hasPrefix("keychain://") else { return nil } + let rest = String(uri.dropFirst("keychain://".count)) + guard let slash = rest.firstIndex(of: "/") else { return nil } + let service = String(rest[.. TemplateKeychainRef { + TemplateKeychainRef( + service: "com.scarf.template.\(templateSlug)", + account: "\(fieldKey):\(Self.shortHash(of: projectPath))" + ) + } + + nonisolated static func shortHash(of string: String) -> String { + // 8 hex chars is 32 bits of uniqueness — plenty for + // distinguishing a handful of project dirs per template install. + let data = Data(string.utf8) + var hash: UInt32 = 0x811c9dc5 + for byte in data { + hash ^= UInt32(byte) + hash &*= 0x01000193 + } + return String(format: "%08x", hash) + } +} + +// MARK: - Validation + +/// One schema- or value-validation problem. Carries `fieldKey` so the +/// UI can surface the error inline with the field rather than at the +/// top of the form. +struct TemplateConfigValidationError: Error, Sendable, Equatable { + let fieldKey: String? + let message: String +} + +enum TemplateConfigSchemaError: LocalizedError, Sendable { + case duplicateKey(String) + case unsupportedType(String) + case emptyEnumOptions(String) + case duplicateEnumValue(key: String, value: String) + case unsupportedListItemType(key: String, itemType: String) + case secretFieldHasDefault(String) + case emptyModelPreferred + + var errorDescription: String? { + switch self { + case .duplicateKey(let k): + return "Config schema has duplicate key: \(k)" + case .unsupportedType(let t): + return "Config schema uses unsupported field type: \(t)" + case .emptyEnumOptions(let k): + return "Enum field '\(k)' must declare at least one option" + case .duplicateEnumValue(let k, let v): + return "Enum field '\(k)' has duplicate option value: \(v)" + case .unsupportedListItemType(let k, let t): + return "List field '\(k)' uses unsupported itemType '\(t)'. Only 'string' is supported in v1." + case .secretFieldHasDefault(let k): + return "Secret field '\(k)' cannot declare a default value — secrets belong only in the Keychain." + case .emptyModelPreferred: + return "modelRecommendation.preferred must be a non-empty model id." + } + } +} diff --git a/scarf/scarf/Core/Services/ProjectConfigKeychain.swift b/scarf/scarf/Core/Services/ProjectConfigKeychain.swift new file mode 100644 index 0000000..36f5590 --- /dev/null +++ b/scarf/scarf/Core/Services/ProjectConfigKeychain.swift @@ -0,0 +1,154 @@ +import Foundation +import Security +import os + +/// Thin wrapper around the macOS Keychain for template-config secrets. +/// Scarf doesn't have other Keychain users yet so this file is the one +/// place that touches the `Security` framework; keep it small and +/// auditable so a reader can tell at a glance what we store, under what +/// identifiers, and when items are removed. +/// +/// **What we store.** Generic passwords (kSecClassGenericPassword) in +/// the login Keychain. Each item is identified by a (service, account) +/// pair derived from the template slug + field key + project-path hash +/// — see `TemplateKeychainRef.make`. The stored Data is the user's +/// raw secret bytes; we never transform or encode them. +/// +/// **When items are written.** By `ProjectTemplateInstaller` after the +/// install preview is confirmed and the user has filled in the +/// configure sheet. By `TemplateConfigSheet` when the user edits a +/// secret field post-install. +/// +/// **When items are removed.** By `ProjectTemplateUninstaller`, +/// iterating the lock file's `configKeychainItems` list. The login +/// Keychain is never swept for stray entries — if the lock is out of +/// sync we log + skip rather than guess which items are ours. +/// +/// **What shows to the user.** macOS prompts "Scarf wants to access +/// the Keychain" the first time we read a secret in a given session. +/// User approves; subsequent reads in that session are silent. We +/// never bypass this — the prompt is the user's trust boundary. +struct ProjectConfigKeychain: Sendable { + private static let logger = Logger(subsystem: "com.scarf", category: "ProjectConfigKeychain") + + /// Which Keychain to target. The default is the login Keychain + /// (`nil` uses the user's default chain). Tests pass an explicit + /// namespace suffix via `testServiceSuffix` — see `TemplateConfigTests` — + /// so integration tests can roundtrip without polluting real + /// user state. + let testServiceSuffix: String? + + nonisolated init(testServiceSuffix: String? = nil) { + self.testServiceSuffix = testServiceSuffix + } + + /// Write or overwrite the secret for (service, account). Tests + /// route their items through a distinct service prefix via + /// `testServiceSuffix` so they can't leak into the user's real + /// Keychain. + nonisolated func set(service: String, account: String, secret: Data) throws { + let svc = resolved(service: service) + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: svc, + kSecAttrAccount as String: account, + ] + // Try update first — cheaper than delete-then-add and doesn't + // trip macOS's "item already exists" if another thread raced us. + let update: [String: Any] = [ + kSecValueData as String: secret, + ] + let updateStatus = SecItemUpdate(query as CFDictionary, update as CFDictionary) + if updateStatus == errSecSuccess { return } + if updateStatus != errSecItemNotFound { + throw Self.error(status: updateStatus, op: "update") + } + var insert = query + insert[kSecValueData as String] = secret + // kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly — stays in + // this device's Keychain, not synced via iCloud, usable after + // first unlock (so background cron triggers can read). + insert[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly + let addStatus = SecItemAdd(insert as CFDictionary, nil) + if addStatus != errSecSuccess { + throw Self.error(status: addStatus, op: "add") + } + } + + /// Retrieve the secret for (service, account). Returns `nil` when + /// the item simply doesn't exist (user never set it, or an + /// uninstall already removed it). Throws on every other Keychain + /// error so callers don't silently treat "access denied" or + /// "corrupt keychain" as "no value." + nonisolated func get(service: String, account: String) throws -> Data? { + let svc = resolved(service: service) + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: svc, + kSecAttrAccount as String: account, + kSecReturnData as String: true, + kSecMatchLimit as String: kSecMatchLimitOne, + ] + var result: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &result) + if status == errSecItemNotFound { return nil } + if status != errSecSuccess { + throw Self.error(status: status, op: "get") + } + return result as? Data + } + + /// Delete the secret for (service, account). Absent item is a + /// no-op; any other failure throws. Called by + /// `ProjectTemplateUninstaller` for every item in + /// `TemplateLock.configKeychainItems`. + nonisolated func delete(service: String, account: String) throws { + let svc = resolved(service: service) + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: svc, + kSecAttrAccount as String: account, + ] + let status = SecItemDelete(query as CFDictionary) + if status == errSecItemNotFound || status == errSecSuccess { return } + throw Self.error(status: status, op: "delete") + } + + /// Convenience: apply the test suffix when in test mode. + nonisolated private func resolved(service: String) -> String { + guard let suffix = testServiceSuffix, !suffix.isEmpty else { return service } + return "\(service).\(suffix)" + } + + /// Build a useful NSError from a Keychain OSStatus. Logs at warning + /// — callers decide whether the failure is fatal. + nonisolated private static func error(status: OSStatus, op: String) -> NSError { + let description = (SecCopyErrorMessageString(status, nil) as String?) ?? "Keychain error" + logger.warning("Keychain \(op, privacy: .public) failed: \(status) \(description, privacy: .public)") + return NSError( + domain: "com.scarf.keychain", + code: Int(status), + userInfo: [ + NSLocalizedDescriptionKey: "Keychain \(op) failed (\(status)): \(description)" + ] + ) + } +} + +// MARK: - Ref-shaped convenience layer + +extension ProjectConfigKeychain { + /// Set a secret using a pre-built `TemplateKeychainRef`. Mirrors the + /// service/account plumbing every caller would otherwise repeat. + nonisolated func set(ref: TemplateKeychainRef, secret: Data) throws { + try set(service: ref.service, account: ref.account, secret: secret) + } + + nonisolated func get(ref: TemplateKeychainRef) throws -> Data? { + try get(service: ref.service, account: ref.account) + } + + nonisolated func delete(ref: TemplateKeychainRef) throws { + try delete(service: ref.service, account: ref.account) + } +} diff --git a/scarf/scarf/Core/Services/ProjectConfigService.swift b/scarf/scarf/Core/Services/ProjectConfigService.swift new file mode 100644 index 0000000..091acda --- /dev/null +++ b/scarf/scarf/Core/Services/ProjectConfigService.swift @@ -0,0 +1,318 @@ +import Foundation +import os + +/// Per-project configuration I/O: reads `/.scarf/config.json` +/// into typed values, writes them back, resolves Keychain-backed secrets +/// on demand, and validates user-entered values against the schema. +/// +/// Separation of concerns: +/// +/// - **Schema authority.** `TemplateConfigSchema` lives in the bundle's +/// `template.json` and a copy is stashed at `/.scarf/manifest.json` +/// at install time so the post-install editor works offline. This +/// service treats the schema as read-only input; `validateSchema` +/// checks structural invariants and is called by +/// `ProjectTemplateService` during install-plan building. +/// - **Value storage.** Non-secret values live inline in `config.json`; +/// secret values are Keychain references of the form +/// `"keychain:///"`. The service owns both halves +/// of that storage — callers never open `config.json` or touch the +/// Keychain directly. +/// - **Remote readiness.** All file I/O goes through +/// `ServerContext.makeTransport()` so when `ProjectTemplateInstaller` +/// eventually supports remote contexts, the config store comes along +/// for the ride. Keychain access stays local (it's a macOS-side thing +/// by definition — agents on remote Hermes installs would fetch +/// values via Scarf's channel, same as today). +struct ProjectConfigService: Sendable { + private static let logger = Logger(subsystem: "com.scarf", category: "ProjectConfigService") + + let context: ServerContext + let keychain: ProjectConfigKeychain + + nonisolated init( + context: ServerContext = .local, + keychain: ProjectConfigKeychain = ProjectConfigKeychain() + ) { + self.context = context + self.keychain = keychain + } + + // MARK: - Paths + + nonisolated static func configPath(for project: ProjectEntry) -> String { + project.path + "/.scarf/config.json" + } + + nonisolated static func manifestCachePath(for project: ProjectEntry) -> String { + project.path + "/.scarf/manifest.json" + } + + // MARK: - Load / save on-disk config + + /// Read + decode `/.scarf/config.json`. Returns `nil` + /// cleanly when the file is absent (e.g. a project installed from + /// a schema-less template, or a hand-added project). Throws on + /// malformed JSON so the caller can surface a concrete error + /// rather than silently treating a corrupt file as missing. + nonisolated func load(project: ProjectEntry) throws -> ProjectConfigFile? { + let transport = context.makeTransport() + let path = Self.configPath(for: project) + guard transport.fileExists(path) else { return nil } + let data = try transport.readFile(path) + do { + return try JSONDecoder().decode(ProjectConfigFile.self, from: data) + } catch { + Self.logger.error("couldn't decode config.json at \(path, privacy: .public): \(error.localizedDescription, privacy: .public)") + throw error + } + } + + /// Write `/.scarf/config.json`. Secrets should already be + /// represented as `TemplateConfigValue.keychainRef` references here + /// — this service never inspects their plaintext. + nonisolated func save( + project: ProjectEntry, + templateId: String, + values: [String: TemplateConfigValue] + ) throws { + let transport = context.makeTransport() + let file = ProjectConfigFile( + schemaVersion: 2, + templateId: templateId, + values: values, + updatedAt: ISO8601DateFormatter().string(from: Date()) + ) + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let data = try encoder.encode(file) + let parent = (Self.configPath(for: project) as NSString).deletingLastPathComponent + try transport.createDirectory(parent) + try transport.writeFile(Self.configPath(for: project), data: data) + } + + // MARK: - Manifest cache (schema used by post-install editor) + + /// Copy a template's `template.json` into `/.scarf/manifest.json` + /// so the post-install "Configuration" button can render the form + /// offline. Called once by the installer after unpack + validate. + nonisolated func cacheManifest(project: ProjectEntry, manifestData: Data) throws { + let transport = context.makeTransport() + let path = Self.manifestCachePath(for: project) + let parent = (path as NSString).deletingLastPathComponent + try transport.createDirectory(parent) + try transport.writeFile(path, data: manifestData) + } + + /// Load the cached manifest into a `ProjectTemplateManifest` so the + /// editor can look up field types + labels. Returns `nil` when the + /// project wasn't installed from a schemaful template. + nonisolated func loadCachedManifest(project: ProjectEntry) throws -> ProjectTemplateManifest? { + let transport = context.makeTransport() + let path = Self.manifestCachePath(for: project) + guard transport.fileExists(path) else { return nil } + let data = try transport.readFile(path) + return try JSONDecoder().decode(ProjectTemplateManifest.self, from: data) + } + + // MARK: - Secrets + + /// Resolve a `keychainRef` value into the actual secret bytes. + /// Returns `nil` if the Keychain entry has been removed (e.g. + /// external user cleanup, a previous uninstall that didn't finish). + nonisolated func resolveSecret(ref value: TemplateConfigValue) throws -> Data? { + guard case .keychainRef(let uri) = value, + let ref = TemplateKeychainRef.parse(uri) else { + return nil + } + return try keychain.get(ref: ref) + } + + /// Store a freshly-entered secret. Returns the `keychainRef` value + /// suitable for writing into `config.json`. + nonisolated func storeSecret( + templateSlug: String, + fieldKey: String, + project: ProjectEntry, + secret: Data + ) throws -> TemplateConfigValue { + let ref = TemplateKeychainRef.make( + templateSlug: templateSlug, + fieldKey: fieldKey, + projectPath: project.path + ) + try keychain.set(ref: ref, secret: secret) + return .keychainRef(ref.uri) + } + + /// Delete every Keychain item tracked in `refs`. Absent items are + /// fine (uninstall may run after the user manually cleaned an + /// entry). Any other failure is logged and re-thrown so the + /// uninstaller can surface it. + nonisolated func deleteSecrets(refs: [TemplateKeychainRef]) throws { + for ref in refs { + try keychain.delete(ref: ref) + } + } + + // MARK: - Schema validation (author-facing; called at bundle inspect time) + + /// Verify structural invariants on a schema: unique keys, known + /// types, enum options, secret-without-default rule, model + /// recommendation non-empty when present. Called by + /// `ProjectTemplateService.inspect` before buildPlan runs. + nonisolated static func validateSchema(_ schema: TemplateConfigSchema) throws { + var seen = Set() + for field in schema.fields { + if !seen.insert(field.key).inserted { + throw TemplateConfigSchemaError.duplicateKey(field.key) + } + switch field.type { + case .enum: + let opts = field.options ?? [] + guard !opts.isEmpty else { + throw TemplateConfigSchemaError.emptyEnumOptions(field.key) + } + var seenValues = Set() + for opt in opts { + if !seenValues.insert(opt.value).inserted { + throw TemplateConfigSchemaError.duplicateEnumValue(key: field.key, value: opt.value) + } + } + case .list: + let item = field.itemType ?? "string" + if item != "string" { + throw TemplateConfigSchemaError.unsupportedListItemType(key: field.key, itemType: item) + } + case .secret: + if field.defaultValue != nil { + throw TemplateConfigSchemaError.secretFieldHasDefault(field.key) + } + case .string, .text, .number, .bool: + break + } + } + if let rec = schema.modelRecommendation { + if rec.preferred.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + throw TemplateConfigSchemaError.emptyModelPreferred + } + } + } + + // MARK: - Value validation (runs on user input in the configure sheet) + + /// Validate user-entered values against the schema. Returns one + /// `TemplateConfigValidationError` per problem. Empty array means + /// the form is submittable. + nonisolated static func validateValues( + _ values: [String: TemplateConfigValue], + against schema: TemplateConfigSchema + ) -> [TemplateConfigValidationError] { + var errors: [TemplateConfigValidationError] = [] + for field in schema.fields { + let value = values[field.key] + if field.required && !Self.hasMeaningfulValue(value, type: field.type) { + errors.append(.init(fieldKey: field.key, message: "\(field.label) is required.")) + continue + } + guard let value else { continue } + switch field.type { + case .string, .text: + if case .string(let s) = value { + if let min = field.minLength, s.count < min { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) must be at least \(min) characters.")) + } + if let max = field.maxLength, s.count > max { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) must be at most \(max) characters.")) + } + if let pattern = field.pattern, + s.range(of: pattern, options: .regularExpression) == nil { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) doesn't match the expected format.")) + } + } else { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) must be a string.")) + } + + case .number: + if case .number(let n) = value { + if let min = field.minNumber, n < min { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) must be ≥ \(min).")) + } + if let max = field.maxNumber, n > max { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) must be ≤ \(max).")) + } + } else { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) must be a number.")) + } + + case .bool: + if case .bool = value { /* ok */ } else { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) must be true or false.")) + } + + case .enum: + if case .string(let s) = value { + let options = (field.options ?? []).map(\.value) + if !options.contains(s) { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) must be one of \(options.joined(separator: ", ")).")) + } + } else { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) must be one of the predefined options.")) + } + + case .list: + if case .list(let items) = value { + if let min = field.minItems, items.count < min { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) needs at least \(min) item(s).")) + } + if let max = field.maxItems, items.count > max { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) accepts at most \(max) item(s).")) + } + } else { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) must be a list.")) + } + + case .secret: + if case .keychainRef = value { /* opaque — trust it */ } else { + errors.append(.init(fieldKey: field.key, + message: "\(field.label) must be supplied (Keychain entry missing).")) + } + } + } + return errors + } + + nonisolated private static func hasMeaningfulValue( + _ value: TemplateConfigValue?, + type: TemplateConfigField.FieldType + ) -> Bool { + guard let value else { return false } + switch (type, value) { + case (.string, .string(let s)), (.text, .string(let s)), (.enum, .string(let s)): + return !s.isEmpty + case (.number, .number): + return true + case (.bool, .bool): + return true + case (.list, .list(let arr)): + return !arr.isEmpty + case (.secret, .keychainRef): + return true + default: + return false + } + } +} diff --git a/scarf/scarf/Core/Services/ProjectDashboardService.swift b/scarf/scarf/Core/Services/ProjectDashboardService.swift index 9893708..2d616c7 100644 --- a/scarf/scarf/Core/Services/ProjectDashboardService.swift +++ b/scarf/scarf/Core/Services/ProjectDashboardService.swift @@ -1,6 +1,8 @@ import Foundation +import os struct ProjectDashboardService: Sendable { + private static let logger = Logger(subsystem: "com.scarf", category: "ProjectDashboardService") let context: ServerContext let transport: any ServerTransport @@ -19,23 +21,28 @@ struct ProjectDashboardService: Sendable { do { return try JSONDecoder().decode(ProjectRegistry.self, from: data) } catch { - print("[Scarf] Failed to decode project registry: \(error.localizedDescription)") + Self.logger.error("Failed to decode project registry: \(error.localizedDescription, privacy: .public)") return ProjectRegistry(projects: []) } } - func saveRegistry(_ registry: ProjectRegistry) { + /// Persist the project registry to `~/.hermes/scarf/projects.json`. + /// + /// **Throws** on every non-success path — the previous version of + /// this method silently swallowed `createDirectory` and `writeFile` + /// failures with `try?`, which meant the installer could return a + /// valid-looking `ProjectEntry` while the registry on disk never + /// received the new row (project would complete install, show a + /// success screen, then be invisible in the sidebar). Callers that + /// want fire-and-forget behaviour can still use `try?`, but the + /// choice is now theirs. + func saveRegistry(_ registry: ProjectRegistry) throws { let dir = context.paths.scarfDir if !transport.fileExists(dir) { - do { - try transport.createDirectory(dir) - } catch { - print("[Scarf] Failed to create scarf directory: \(error.localizedDescription)") - return - } + try transport.createDirectory(dir) } - guard let data = try? JSONEncoder().encode(registry) else { return } - // Pretty-print for readability (agents may read this file) + let data = try JSONEncoder().encode(registry) + // Pretty-print for readability (agents may read this file). let writeData: Data if let pretty = try? JSONSerialization.jsonObject(with: data), let formatted = try? JSONSerialization.data(withJSONObject: pretty, options: [.prettyPrinted, .sortedKeys]) { @@ -43,7 +50,7 @@ struct ProjectDashboardService: Sendable { } else { writeData = data } - try? transport.writeFile(context.paths.projectsRegistry, data: writeData) + try transport.writeFile(context.paths.projectsRegistry, data: writeData) } // MARK: - Dashboard diff --git a/scarf/scarf/Core/Services/ProjectTemplateExporter.swift b/scarf/scarf/Core/Services/ProjectTemplateExporter.swift new file mode 100644 index 0000000..ca1d3f2 --- /dev/null +++ b/scarf/scarf/Core/Services/ProjectTemplateExporter.swift @@ -0,0 +1,336 @@ +import Foundation +import os + +/// Builds a `.scarftemplate` bundle from an existing Scarf project plus the +/// caller's selection of skills and cron jobs. Symmetric with the +/// `ProjectTemplateService` + `ProjectTemplateInstaller` pair — the output +/// of this exporter can be fed straight back to `inspect()` + `install()`. +struct ProjectTemplateExporter: Sendable { + private static let logger = Logger(subsystem: "com.scarf", category: "ProjectTemplateExporter") + + let context: ServerContext + + nonisolated init(context: ServerContext = .local) { + self.context = context + } + + /// Known filenames in the project root that map to specific agents. When + /// the author opts to include them, each is copied verbatim into + /// `instructions/` in the bundle. + nonisolated static let knownInstructionFiles: [String] = [ + "CLAUDE.md", + "GEMINI.md", + ".cursorrules", + ".github/copilot-instructions.md" + ] + + /// Author-facing description of what `export` will do with the given + /// selections. Shown in the export sheet so the user knows exactly + /// what's about to go into the bundle before saving. + struct ExportPlan: Sendable { + let templateId: String + let templateName: String + let templateVersion: String + let projectDir: String + let dashboardPresent: Bool + let agentsMdPresent: Bool + let readmePresent: Bool + let instructionFiles: [String] + let skillIds: [String] + let cronJobs: [HermesCronJob] + let memoryAppendix: String? + } + + /// Inputs collected by the export sheet. + struct ExportInputs: Sendable { + let project: ProjectEntry + let templateId: String + let templateName: String + let templateVersion: String + let description: String + let authorName: String? + let authorUrl: String? + let category: String? + let tags: [String] + let includeSkillIds: [String] + let includeCronJobIds: [String] + /// Raw markdown the author wants appended to installers' MEMORY.md. + /// `nil` to skip. + let memoryAppendix: String? + } + + /// Scan the project dir and report what a fresh export would include + /// given the caller's inputs. Does not write anything. + /// + /// Existence checks go through the context's transport — the project + /// path comes from the registry on the active server and may be on a + /// remote filesystem (future remote-install support), where + /// `FileManager.default.fileExists` would silently return `false`. + nonisolated func previewPlan(for inputs: ExportInputs) -> ExportPlan { + let dir = inputs.project.path + let transport = context.makeTransport() + let dashboard = transport.fileExists(dir + "/.scarf/dashboard.json") + let readme = transport.fileExists(dir + "/README.md") + let agents = transport.fileExists(dir + "/AGENTS.md") + let instructions = Self.knownInstructionFiles.filter { + transport.fileExists(dir + "/" + $0) + } + let allJobs = HermesFileService(context: context).loadCronJobs() + let picked = allJobs.filter { inputs.includeCronJobIds.contains($0.id) } + return ExportPlan( + templateId: inputs.templateId, + templateName: inputs.templateName, + templateVersion: inputs.templateVersion, + projectDir: dir, + dashboardPresent: dashboard, + agentsMdPresent: agents, + readmePresent: readme, + instructionFiles: instructions, + skillIds: inputs.includeSkillIds, + cronJobs: picked, + memoryAppendix: inputs.memoryAppendix + ) + } + + /// Build the bundle and write it to `outputZipPath`. Throws if any + /// required file is missing or the zip step fails. + nonisolated func export( + inputs: ExportInputs, + outputZipPath: String + ) throws { + let stagingDir = NSTemporaryDirectory() + "scarf-template-export-" + UUID().uuidString + try FileManager.default.createDirectory(atPath: stagingDir, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(atPath: stagingDir) } + + let plan = previewPlan(for: inputs) + + guard plan.dashboardPresent else { + throw ProjectTemplateError.requiredFileMissing("dashboard.json (expected at \(plan.projectDir)/.scarf/dashboard.json)") + } + guard plan.readmePresent else { + throw ProjectTemplateError.requiredFileMissing("README.md (expected at \(plan.projectDir)/README.md)") + } + guard plan.agentsMdPresent else { + throw ProjectTemplateError.requiredFileMissing("AGENTS.md (expected at \(plan.projectDir)/AGENTS.md)") + } + + // Required files. All source reads go through the context's + // transport — project paths come from the registry on the active + // server and may be on a remote filesystem. Destinations are in + // the local staging dir so Foundation writes are correct. + let transport = context.makeTransport() + try copyFromHermes(plan.projectDir + "/.scarf/dashboard.json", to: stagingDir + "/dashboard.json", transport: transport) + try copyFromHermes(plan.projectDir + "/README.md", to: stagingDir + "/README.md", transport: transport) + try copyFromHermes(plan.projectDir + "/AGENTS.md", to: stagingDir + "/AGENTS.md", transport: transport) + + // Optional per-agent instruction shims + for relative in plan.instructionFiles { + let source = plan.projectDir + "/" + relative + let destination = stagingDir + "/instructions/" + relative + try createParent(of: destination) + try copyFromHermes(source, to: destination, transport: transport) + } + + // Skills (copied from the global skills dir) + if !plan.skillIds.isEmpty { + let skillsRoot = stagingDir + "/skills" + try FileManager.default.createDirectory(atPath: skillsRoot, withIntermediateDirectories: true) + let allSkills = HermesFileService(context: context).loadSkills() + .flatMap(\.skills) + for skillId in plan.skillIds { + guard let skill = allSkills.first(where: { $0.id == skillId }) else { + throw ProjectTemplateError.requiredFileMissing("skills/" + skillId) + } + // The bundle uses a flat `skills//` layout (no + // category), matching what the installer expects. If two + // categories ship skills with the same `name`, the second + // collides — warn by refusing rather than silently + // overwriting. + let targetDir = skillsRoot + "/" + skill.name + if FileManager.default.fileExists(atPath: targetDir) { + throw ProjectTemplateError.conflictingFile(targetDir) + } + try FileManager.default.createDirectory(atPath: targetDir, withIntermediateDirectories: true) + for file in skill.files { + try copyFromHermes(skill.path + "/" + file, to: targetDir + "/" + file, transport: transport) + } + } + } + + // Cron jobs (stripped to the create-CLI-shaped spec) + if !plan.cronJobs.isEmpty { + let specs = plan.cronJobs.map { Self.strip($0) } + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let data = try encoder.encode(specs) + let cronDir = stagingDir + "/cron" + try FileManager.default.createDirectory(atPath: cronDir, withIntermediateDirectories: true) + try data.write(to: URL(fileURLWithPath: cronDir + "/jobs.json")) + } + + // Memory appendix. A write failure here would silently produce a + // bundle whose manifest claims `memory.append = true` but ships an + // empty/missing file — installers would then fail on + // contentClaimMismatch with no breadcrumb pointing back at the + // export step. Let the error propagate. + if let appendix = plan.memoryAppendix, !appendix.isEmpty { + let memDir = stagingDir + "/memory" + try FileManager.default.createDirectory(atPath: memDir, withIntermediateDirectories: true) + guard let data = appendix.data(using: .utf8) else { + throw ProjectTemplateError.requiredFileMissing("memory/append.md (non-UTF8)") + } + try data.write(to: URL(fileURLWithPath: memDir + "/append.md")) + } + + // If the source project was itself installed from a schemaful + // template, its `.scarf/manifest.json` carries the schema we + // want to forward to the exported bundle. We carry only the + // SCHEMA — never user values. Exporting must be safe on a + // project with live config: the schema is author-supplied + // metadata; the values in `config.json` are the current user's + // secrets or personal settings. + let forwardedSchema: TemplateConfigSchema? = try Self.readCachedSchema( + from: plan.projectDir + ) + + // Bump schemaVersion to 2 when a schema is carried through; + // remain on 1 otherwise so schema-less exports stay + // byte-compatible with existing v2.2 catalog validators. + let schemaVersion = forwardedSchema == nil ? 1 : 2 + + // Manifest — claims exactly what we just wrote + let manifest = ProjectTemplateManifest( + schemaVersion: schemaVersion, + id: inputs.templateId, + name: inputs.templateName, + version: inputs.templateVersion, + minScarfVersion: nil, + minHermesVersion: nil, + author: inputs.authorName.map { + TemplateAuthor(name: $0, url: inputs.authorUrl) + }, + description: inputs.description, + category: inputs.category, + tags: inputs.tags.isEmpty ? nil : inputs.tags, + icon: nil, + screenshots: nil, + contents: TemplateContents( + dashboard: true, + agentsMd: true, + instructions: plan.instructionFiles.isEmpty ? nil : plan.instructionFiles, + skills: plan.skillIds.isEmpty ? nil : plan.skillIds.compactMap { $0.split(separator: "/").last.map(String.init) }, + cron: plan.cronJobs.isEmpty ? nil : plan.cronJobs.count, + memory: (inputs.memoryAppendix?.isEmpty == false) ? TemplateMemoryClaim(append: true) : nil, + config: forwardedSchema?.fields.count + ), + config: forwardedSchema + ) + let manifestEncoder = JSONEncoder() + manifestEncoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let manifestData = try manifestEncoder.encode(manifest) + try manifestData.write(to: URL(fileURLWithPath: stagingDir + "/template.json")) + + try zip(stagingDir: stagingDir, outputPath: outputZipPath) + } + + // MARK: - Private + + /// Copy a file whose source lives on the Hermes side (possibly remote) + /// into a local destination path under the staging dir. Using the + /// transport for the read keeps the exporter remote-ready; the write + /// goes through Foundation because the staging dir is always local to + /// the Mac running Scarf. + nonisolated private func copyFromHermes( + _ source: String, + to destination: String, + transport: any ServerTransport + ) throws { + let data = try transport.readFile(source) + try createParent(of: destination) + try data.write(to: URL(fileURLWithPath: destination)) + } + + nonisolated private func createParent(of path: String) throws { + let parent = (path as NSString).deletingLastPathComponent + if !FileManager.default.fileExists(atPath: parent) { + try FileManager.default.createDirectory(atPath: parent, withIntermediateDirectories: true) + } + } + + /// Read the cached manifest from `/.scarf/manifest.json` (if + /// present) and pull out just the config schema. Values in + /// `.scarf/config.json` are intentionally ignored — an exported + /// bundle carries the schema's shape, never the current user's + /// configured values. + nonisolated private static func readCachedSchema(from projectDir: String) throws -> TemplateConfigSchema? { + let manifestPath = projectDir + "/.scarf/manifest.json" + guard FileManager.default.fileExists(atPath: manifestPath) else { return nil } + let data = try Data(contentsOf: URL(fileURLWithPath: manifestPath)) + // Use a bespoke decode rather than ProjectTemplateManifest so + // this helper stays resilient if the manifest shape evolves + // incompatibly in a future release. + struct OnlyConfig: Decodable { let config: TemplateConfigSchema? } + let onlyConfig = try JSONDecoder().decode(OnlyConfig.self, from: data) + return onlyConfig.config + } + + /// Convert a live cron job (with runtime state) into the spec the + /// installer will feed back to `hermes cron create`. Only preserves + /// fields the CLI accepts. + nonisolated private static func strip(_ job: HermesCronJob) -> TemplateCronJobSpec { + let schedule: String = { + if let expr = job.schedule.expression, !expr.isEmpty { return expr } + if let runAt = job.schedule.runAt, !runAt.isEmpty { return runAt } + return job.schedule.display ?? "" + }() + return TemplateCronJobSpec( + name: job.name, + schedule: schedule, + prompt: job.prompt.isEmpty ? nil : job.prompt, + deliver: job.deliver?.isEmpty == false ? job.deliver : nil, + skills: (job.skills?.isEmpty == false) ? job.skills : nil, + repeatCount: nil + ) + } + + /// Shell out to `/usr/bin/zip -r` so the file ordering is deterministic + /// and the archive is standard — Apple-provided tools (and the system + /// `unzip` the installer uses) will read it without trouble. + nonisolated private func zip(stagingDir: String, outputPath: String) throws { + // `zip` writes relative paths based on the cwd it's invoked in. Chdir + // via Process.currentDirectoryURL so entries are `template.json`, + // `AGENTS.md`, etc., not absolute paths. + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/zip") + process.currentDirectoryURL = URL(fileURLWithPath: stagingDir) + process.arguments = ["-qq", "-r", outputPath, "."] + + let outPipe = Pipe() + let errPipe = Pipe() + process.standardOutput = outPipe + process.standardError = errPipe + + // Close both ends of each Pipe so we don't leak 4 fds per zip call. + func closePipes() { + try? outPipe.fileHandleForReading.close() + try? outPipe.fileHandleForWriting.close() + try? errPipe.fileHandleForReading.close() + try? errPipe.fileHandleForWriting.close() + } + + do { + try process.run() + } catch { + closePipes() + throw ProjectTemplateError.unzipFailed("zip failed to launch: \(error.localizedDescription)") + } + process.waitUntilExit() + let errData = try? errPipe.fileHandleForReading.readToEnd() + closePipes() + + guard process.terminationStatus == 0 else { + let err = errData.flatMap { String(data: $0, encoding: .utf8) } ?? "" + throw ProjectTemplateError.unzipFailed(err.isEmpty ? "exit \(process.terminationStatus)" : err) + } + } +} diff --git a/scarf/scarf/Core/Services/ProjectTemplateInstaller.swift b/scarf/scarf/Core/Services/ProjectTemplateInstaller.swift new file mode 100644 index 0000000..c39cf38 --- /dev/null +++ b/scarf/scarf/Core/Services/ProjectTemplateInstaller.swift @@ -0,0 +1,303 @@ +import Foundation +import os + +/// Executes a `TemplateInstallPlan`. All writes happen in one pass with +/// early-fail semantics: if any step throws, later steps don't run (but +/// earlier ones aren't reversed — v1 doesn't ship an atomic rollback). The +/// plan has already verified `projectDir` doesn't exist and no conflicting +/// file exists at target paths, so by the time we start writing, the +/// expected-error surface is small (mostly I/O failures). +struct ProjectTemplateInstaller: Sendable { + private static let logger = Logger(subsystem: "com.scarf", category: "ProjectTemplateInstaller") + + let context: ServerContext + + nonisolated init(context: ServerContext = .local) { + self.context = context + } + + /// Apply the plan. On success, returns the `ProjectEntry` that was added + /// to the registry so the caller can set `AppCoordinator.selectedProjectName`. + @discardableResult + nonisolated func install(plan: TemplateInstallPlan) throws -> ProjectEntry { + try preflight(plan: plan) + try createProjectFiles(plan: plan) + try createSkillsFiles(plan: plan) + try appendMemoryIfNeeded(plan: plan) + let cronJobNames = try createCronJobs(plan: plan) + let entry = try registerProject(plan: plan) + try writeLockFile(plan: plan, cronJobNames: cronJobNames) + Self.logger.info("installed template \(plan.manifest.id, privacy: .public) v\(plan.manifest.version, privacy: .public) into \(plan.projectDir, privacy: .public)") + return entry + } + + // MARK: - Preflight + + nonisolated private func preflight(plan: TemplateInstallPlan) throws { + // Plan was built on a recent snapshot of the filesystem; re-check the + // invariants at install time so concurrent activity between + // preview-and-confirm can't slip past us. + // + // All existence and read checks for paths that come from + // `context.paths` go through the transport — not `FileManager` — + // so this code works identically against a future remote + // `ServerContext`. See the warning on `ServerContext.readText`: + // "Foundation file APIs are LOCAL ONLY — using them with a remote + // path silently returns nil because the remote path doesn't exist + // on this Mac." + let transport = context.makeTransport() + if transport.fileExists(plan.projectDir) { + throw ProjectTemplateError.projectDirExists(plan.projectDir) + } + for copy in plan.projectFiles where transport.fileExists(copy.destinationPath) { + throw ProjectTemplateError.conflictingFile(copy.destinationPath) + } + for copy in plan.skillsFiles where transport.fileExists(copy.destinationPath) { + throw ProjectTemplateError.conflictingFile(copy.destinationPath) + } + // Memory appendix collision: re-scan MEMORY.md for an existing block + // with the same template id so two installs of v1.0.0 can't + // double-append. A missing MEMORY.md is fine (treated as empty), + // but any *other* read failure (permissions, bad file type) gets + // logged + surfaced so we don't silently pretend MEMORY.md is empty + // and append over a broken file. + if plan.memoryAppendix != nil { + let existing: String + if transport.fileExists(plan.memoryPath) { + do { + let data = try transport.readFile(plan.memoryPath) + existing = String(data: data, encoding: .utf8) ?? "" + } catch { + Self.logger.error("failed to read MEMORY.md at \(plan.memoryPath, privacy: .public): \(error.localizedDescription, privacy: .public)") + throw error + } + } else { + existing = "" + } + let marker = ProjectTemplateService.memoryBlockBeginMarker(templateId: plan.manifest.id) + if existing.contains(marker) { + throw ProjectTemplateError.memoryBlockAlreadyExists(plan.manifest.id) + } + } + } + + // MARK: - Project files + + nonisolated private func createProjectFiles(plan: TemplateInstallPlan) throws { + let transport = context.makeTransport() + try transport.createDirectory(plan.projectDir) + for copy in plan.projectFiles { + let parent = (copy.destinationPath as NSString).deletingLastPathComponent + try transport.createDirectory(parent) + + // Empty `sourceRelativePath` is the "synthesized content" + // sentinel used by `buildPlan` for `.scarf/config.json`. + // The installer materialises config.json from + // `plan.configValues` here rather than copying a bundle + // file that doesn't exist. + if copy.sourceRelativePath.isEmpty { + if copy.destinationPath.hasSuffix("/.scarf/config.json") { + let data = try encodeConfigFile(plan: plan) + try transport.writeFile(copy.destinationPath, data: data) + continue + } + throw ProjectTemplateError.requiredFileMissing( + "synthesized file with unknown destination: \(copy.destinationPath)" + ) + } + + let source = plan.unpackedDir + "/" + copy.sourceRelativePath + let data = try Data(contentsOf: URL(fileURLWithPath: source)) + try transport.writeFile(copy.destinationPath, data: data) + } + } + + /// Serialise `plan.configValues` into the `/.scarf/config.json` + /// shape. Secrets appear as `keychainRef` URIs — the raw bytes were + /// routed into the Keychain by the VM before `install()` was called. + nonisolated private func encodeConfigFile(plan: TemplateInstallPlan) throws -> Data { + let file = ProjectConfigFile( + schemaVersion: 2, + templateId: plan.manifest.id, + values: plan.configValues, + updatedAt: ISO8601DateFormatter().string(from: Date()) + ) + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + return try encoder.encode(file) + } + + // MARK: - Skills + + nonisolated private func createSkillsFiles(plan: TemplateInstallPlan) throws { + guard let namespaceDir = plan.skillsNamespaceDir else { return } + let transport = context.makeTransport() + try transport.createDirectory(namespaceDir) + for copy in plan.skillsFiles { + let source = plan.unpackedDir + "/" + copy.sourceRelativePath + let data = try Data(contentsOf: URL(fileURLWithPath: source)) + let parent = (copy.destinationPath as NSString).deletingLastPathComponent + try transport.createDirectory(parent) + try transport.writeFile(copy.destinationPath, data: data) + } + } + + // MARK: - Memory + + nonisolated private func appendMemoryIfNeeded(plan: TemplateInstallPlan) throws { + guard let appendix = plan.memoryAppendix else { return } + let transport = context.makeTransport() + let existing = (try? transport.readFile(plan.memoryPath)).flatMap { String(data: $0, encoding: .utf8) } ?? "" + let combined = existing + appendix + guard let data = combined.data(using: .utf8) else { + throw ProjectTemplateError.requiredFileMissing("memory/append.md (non-UTF8)") + } + let parent = (plan.memoryPath as NSString).deletingLastPathComponent + try transport.createDirectory(parent) + try transport.writeFile(plan.memoryPath, data: data) + } + + // MARK: - Cron + + /// Create each cron job via `hermes cron create`, then immediately pause + /// it (Hermes creates jobs enabled). Returns the list of resolved job + /// names, which is what the lock file records — we don't know the job + /// ids without parsing the create output, but the name is enough to + /// find + remove them later. + nonisolated private func createCronJobs(plan: TemplateInstallPlan) throws -> [String] { + guard !plan.cronJobs.isEmpty else { return [] } + + let existingBefore = Set(HermesFileService(context: context).loadCronJobs().map(\.id)) + var createdNames: [String] = [] + + for job in plan.cronJobs { + var args = ["cron", "create", "--name", job.name] + if let deliver = job.deliver, !deliver.isEmpty { args += ["--deliver", deliver] } + if let repeatCount = job.repeatCount { args += ["--repeat", String(repeatCount)] } + for skill in job.skills ?? [] where !skill.isEmpty { + args += ["--skill", skill] + } + args.append(job.schedule) + if let prompt = job.prompt, !prompt.isEmpty { + // Substitute template-author tokens with install-time + // values. Hermes doesn't set a CWD for cron runs — when + // the agent fires the prompt, any relative path + // (`.scarf/config.json`, `status-log.md`, etc.) resolves + // against the agent's own dir, not the project. Templates + // use `{{PROJECT_DIR}}` as a placeholder for the absolute + // path; we swap in the real project dir here so the + // registered cron job carries a fully-qualified prompt + // that works regardless of CWD. + let resolvedPrompt = Self.substituteCronTokens(prompt, plan: plan) + args.append(resolvedPrompt) + } + + let (output, exit) = context.runHermes(args) + guard exit == 0 else { + throw ProjectTemplateError.cronCreateFailed(job: job.name, output: output) + } + createdNames.append(job.name) + } + + // Diff the current job set against the snapshot we took before + // creating — anything new belongs to this install and gets paused. + // We pause by id (not name) because `cron pause` takes an id. + let currentJobs = HermesFileService(context: context).loadCronJobs() + let newJobs = currentJobs.filter { !existingBefore.contains($0.id) && createdNames.contains($0.name) } + for job in newJobs { + let (_, exit) = context.runHermes(["cron", "pause", job.id]) + if exit != 0 { + Self.logger.warning("couldn't pause newly-created cron job \(job.id, privacy: .public) — leaving enabled") + } + } + + return createdNames + } + + // MARK: - Registry + + nonisolated private func registerProject(plan: TemplateInstallPlan) throws -> ProjectEntry { + let service = ProjectDashboardService(context: context) + var registry = service.loadRegistry() + let entry = ProjectEntry(name: plan.projectRegistryName, path: plan.projectDir) + registry.projects.append(entry) + // Must throw on failure — silent failure here used to make the + // installer return a valid entry while the registry on disk + // never got updated, producing the "install completed but the + // project doesn't show up in the sidebar" bug. If the registry + // write fails, the whole install is surfaced as failed so the + // user can see + address the underlying problem. + try service.saveRegistry(registry) + return entry + } + + // MARK: - Token substitution (install-time placeholder resolution) + + /// Supported placeholders for template-author prompts. Keep the set + /// intentionally small — every token here becomes a load-bearing + /// part of the template format that we can't rename without + /// breaking existing bundles. + /// + /// - `{{PROJECT_DIR}}`: absolute path of the newly-created project + /// directory. Required for cron prompts because Hermes doesn't + /// establish a CWD when firing cron jobs; relative paths would + /// resolve against whatever dir Hermes happens to be in. + /// + /// - `{{TEMPLATE_ID}}`: the `owner/name` id from the manifest. + /// Less load-bearing; occasionally useful for tagging or + /// delivery targets that reference the template. + /// + /// - `{{TEMPLATE_SLUG}}`: the sanitised slug the installer used + /// for the skills namespace and project dir name. + nonisolated static func substituteCronTokens( + _ prompt: String, + plan: TemplateInstallPlan + ) -> String { + var out = prompt + out = out.replacingOccurrences(of: "{{PROJECT_DIR}}", with: plan.projectDir) + out = out.replacingOccurrences(of: "{{TEMPLATE_ID}}", with: plan.manifest.id) + out = out.replacingOccurrences(of: "{{TEMPLATE_SLUG}}", with: plan.manifest.slug) + return out + } + + // MARK: - Lock file + + nonisolated private func writeLockFile( + plan: TemplateInstallPlan, + cronJobNames: [String] + ) throws { + // Every value that ended up as a keychainRef in config.json gets + // tracked in the lock so the uninstaller can SecItemDelete each + // entry. Field keys are recorded separately for informational + // display in the uninstall preview sheet. + let keychainItems: [String]? = { + let refs = plan.configValues.compactMap { (_, value) -> String? in + if case .keychainRef(let uri) = value { return uri } else { return nil } + } + return refs.isEmpty ? nil : refs.sorted() + }() + let configFields: [String]? = { + guard let schema = plan.configSchema, !schema.isEmpty else { return nil } + return schema.fields.map(\.key) + }() + + let lock = TemplateLock( + templateId: plan.manifest.id, + templateVersion: plan.manifest.version, + templateName: plan.manifest.name, + installedAt: ISO8601DateFormatter().string(from: Date()), + projectFiles: plan.projectFiles.map(\.destinationPath), + skillsNamespaceDir: plan.skillsNamespaceDir, + skillsFiles: plan.skillsFiles.map(\.destinationPath), + cronJobNames: cronJobNames, + memoryBlockId: plan.memoryAppendix == nil ? nil : plan.manifest.id, + configKeychainItems: keychainItems, + configFields: configFields + ) + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let data = try encoder.encode(lock) + let path = plan.projectDir + "/.scarf/template.lock.json" + try context.makeTransport().writeFile(path, data: data) + } +} diff --git a/scarf/scarf/Core/Services/ProjectTemplateService.swift b/scarf/scarf/Core/Services/ProjectTemplateService.swift new file mode 100644 index 0000000..9462ecc --- /dev/null +++ b/scarf/scarf/Core/Services/ProjectTemplateService.swift @@ -0,0 +1,500 @@ +import Foundation +import os + +/// Reads, validates, and plans the install of a `.scarftemplate` bundle. Pure +/// — owns no state across calls. The installer (see +/// `ProjectTemplateInstaller`) consumes the `TemplateInstallPlan` this +/// produces. +/// +/// Responsibilities: +/// 1. Unpack a `.scarftemplate` zip into a caller-owned temp directory. +/// 2. Parse `template.json` and validate it against the schema we know about. +/// 3. Walk the unpacked contents and verify they match the manifest's +/// `contents` claim (so a malicious bundle can't hide files from the +/// preview sheet). +/// 4. Produce a `TemplateInstallPlan` describing every concrete filesystem +/// op the installer will perform, given a parent directory the user +/// picked. +struct ProjectTemplateService: Sendable { + private static let logger = Logger(subsystem: "com.scarf", category: "ProjectTemplateService") + + let context: ServerContext + + nonisolated init(context: ServerContext = .local) { + self.context = context + } + + // MARK: - Inspection + + /// Unpack the zip at `zipPath` into a fresh temp directory, parse and + /// validate the manifest, and walk the contents. Throws on any + /// inconsistency. On success, the caller owns `inspection.unpackedDir` + /// and must remove it once they're done. + nonisolated func inspect(zipPath: String) throws -> TemplateInspection { + let unpackedDir = try makeTempDir() + try unzip(zipPath: zipPath, intoDir: unpackedDir) + + let manifestPath = unpackedDir + "/template.json" + guard FileManager.default.fileExists(atPath: manifestPath) else { + throw ProjectTemplateError.manifestMissing + } + + let manifestData: Data + do { + manifestData = try Data(contentsOf: URL(fileURLWithPath: manifestPath)) + } catch { + throw ProjectTemplateError.manifestParseFailed(error.localizedDescription) + } + let manifest: ProjectTemplateManifest + do { + manifest = try JSONDecoder().decode(ProjectTemplateManifest.self, from: manifestData) + } catch { + throw ProjectTemplateError.manifestParseFailed(error.localizedDescription) + } + + // schemaVersion 1 is the original v2.2 bundle; 2 adds the + // optional `config` block. Both are valid. Newer versions get + // refused so the installer never silently misinterprets a + // future-shape bundle. + guard manifest.schemaVersion == 1 || manifest.schemaVersion == 2 else { + throw ProjectTemplateError.unsupportedSchemaVersion(manifest.schemaVersion) + } + + // Validate the optional config schema at inspect time — a + // malformed schema (duplicate keys, secret-with-default, etc.) + // gets rejected before the user ever sees the preview sheet. + if let schema = manifest.config { + do { + try ProjectConfigService.validateSchema(schema) + } catch { + throw ProjectTemplateError.manifestParseFailed( + "invalid config schema: \(error.localizedDescription)" + ) + } + } + + let files = try Self.walk(unpackedDir) + let cronJobs = try Self.readCronJobs(unpackedDir: unpackedDir) + try Self.verifyClaims(manifest: manifest, files: files, cronJobCount: cronJobs.count) + + return TemplateInspection( + manifest: manifest, + unpackedDir: unpackedDir, + files: files, + cronJobs: cronJobs + ) + } + + // MARK: - Planning + + /// Turn an inspection into a concrete install plan given the parent + /// directory the user picked. The plan is deterministic — two calls with + /// the same inputs produce the same ops. + nonisolated func buildPlan( + inspection: TemplateInspection, + parentDir: String + ) throws -> TemplateInstallPlan { + let manifest = inspection.manifest + let slug = manifest.slug + let projectDir = parentDir + "/" + slug + + if FileManager.default.fileExists(atPath: projectDir) { + throw ProjectTemplateError.projectDirExists(projectDir) + } + + var projectFiles: [TemplateFileCopy] = [ + TemplateFileCopy( + sourceRelativePath: "README.md", + destinationPath: projectDir + "/README.md" + ), + TemplateFileCopy( + sourceRelativePath: "AGENTS.md", + destinationPath: projectDir + "/AGENTS.md" + ), + TemplateFileCopy( + sourceRelativePath: "dashboard.json", + destinationPath: projectDir + "/.scarf/dashboard.json" + ) + ] + + // Optional per-agent instruction shims. Each is copied verbatim to + // its conventional project-root path; we don't try to be clever. + let instructionRoot = "instructions" + for relative in (manifest.contents.instructions ?? []) { + let source = instructionRoot + "/" + relative + guard inspection.files.contains(source) else { + throw ProjectTemplateError.requiredFileMissing(source) + } + projectFiles.append( + TemplateFileCopy( + sourceRelativePath: source, + destinationPath: projectDir + "/" + relative + ) + ) + } + + // Namespaced skills: copied wholesale from skills//** into + // ~/.hermes/skills/templates///**. + var skillsFiles: [TemplateFileCopy] = [] + var skillsNamespaceDir: String? = nil + if let skillNames = manifest.contents.skills, !skillNames.isEmpty { + let namespaceDir = context.paths.skillsDir + "/templates/" + slug + skillsNamespaceDir = namespaceDir + for skillName in skillNames { + let prefix = "skills/" + skillName + "/" + let skillFiles = inspection.files.filter { $0.hasPrefix(prefix) } + guard !skillFiles.isEmpty else { + throw ProjectTemplateError.requiredFileMissing(prefix) + } + for relative in skillFiles { + let suffix = String(relative.dropFirst("skills/".count)) + skillsFiles.append( + TemplateFileCopy( + sourceRelativePath: relative, + destinationPath: namespaceDir + "/" + suffix + ) + ) + } + } + } + + // Cron jobs: always prefix name with the template tag so users can + // find and remove them later. Jobs ship disabled — the installer + // pauses each one immediately after `cron create`. + let cronJobs: [TemplateCronJobSpec] = inspection.cronJobs.map { job in + TemplateCronJobSpec( + name: "[tmpl:\(manifest.id)] \(job.name)", + schedule: job.schedule, + prompt: job.prompt, + deliver: job.deliver, + skills: job.skills, + repeatCount: job.repeatCount + ) + } + + // Memory appendix: wrap whatever the template ships in + // begin/end markers so an uninstall can find and remove exactly the + // bytes this template added. `verifyClaims` already guaranteed the + // file is present — so a read error here means something unusual + // (permissions, encoding, etc.); surface it with the real + // `error.localizedDescription` rather than hiding behind a + // generic "file missing." + var memoryAppendix: String? = nil + if manifest.contents.memory?.append == true { + let appendSource = inspection.unpackedDir + "/memory/append.md" + let raw: String + do { + raw = try String(contentsOf: URL(fileURLWithPath: appendSource), encoding: .utf8) + } catch { + Self.logger.error("failed to read memory/append.md in unpacked bundle: \(error.localizedDescription, privacy: .public)") + throw ProjectTemplateError.manifestParseFailed("memory/append.md: \(error.localizedDescription)") + } + memoryAppendix = Self.wrapMemoryBlock( + templateId: manifest.id, + templateVersion: manifest.version, + body: raw.trimmingCharacters(in: .whitespacesAndNewlines) + ) + } + + // Configuration schema + manifest cache. The installer writes + // `.scarf/config.json` (non-secret values) + `.scarf/manifest.json` + // (schema cache used by the post-install editor) when the + // template declares a non-empty schema. Both paths go into + // projectFiles so the uninstaller picks them up via the lock. + var configSchema: TemplateConfigSchema? = nil + var manifestCachePath: String? = nil + if let schema = manifest.config, !schema.isEmpty { + configSchema = schema + let configPath = projectDir + "/.scarf/config.json" + projectFiles.append( + // Source is synthesized by the installer from configValues; + // no file in the unpacked bundle maps to this entry. We use + // an empty `sourceRelativePath` as the "no physical source" + // sentinel — the installer special-cases it below (see + // ProjectTemplateInstaller.createProjectFiles). + TemplateFileCopy( + sourceRelativePath: "", + destinationPath: configPath + ) + ) + let cachePath = projectDir + "/.scarf/manifest.json" + manifestCachePath = cachePath + projectFiles.append( + TemplateFileCopy( + sourceRelativePath: "template.json", + destinationPath: cachePath + ) + ) + } + + return TemplateInstallPlan( + manifest: manifest, + unpackedDir: inspection.unpackedDir, + projectDir: projectDir, + projectFiles: projectFiles, + skillsNamespaceDir: skillsNamespaceDir, + skillsFiles: skillsFiles, + cronJobs: cronJobs, + memoryAppendix: memoryAppendix, + memoryPath: context.paths.memoryMD, + projectRegistryName: Self.uniqueProjectName(preferred: manifest.name, context: context), + configSchema: configSchema, + configValues: [:], // filled in by TemplateInstallerViewModel before install() + manifestCachePath: manifestCachePath + ) + } + + // MARK: - Cleanup + + /// Remove a temp dir created by `inspect`. Safe to call if it already + /// doesn't exist (install or cancel flows both end here). + nonisolated func cleanupTempDir(_ path: String) { + try? FileManager.default.removeItem(atPath: path) + } + + // MARK: - Memory block helpers (installer + future uninstaller share these) + + nonisolated static func memoryBlockBeginMarker(templateId: String) -> String { + "" + } + + nonisolated static func memoryBlockEndMarker(templateId: String) -> String { + "" + } + + nonisolated static func wrapMemoryBlock( + templateId: String, + templateVersion: String, + body: String + ) -> String { + let begin = memoryBlockBeginMarker(templateId: templateId) + let end = memoryBlockEndMarker(templateId: templateId) + return "\n\n\(begin) v\(templateVersion)\n\(body)\n\(end)\n" + } + + // MARK: - Private + + private nonisolated func makeTempDir() throws -> String { + let base = NSTemporaryDirectory() + "scarf-template-" + UUID().uuidString + try FileManager.default.createDirectory( + atPath: base, + withIntermediateDirectories: true + ) + return base + } + + /// Shell out to `/usr/bin/unzip` — matches the existing profile-export + /// pattern (`hermes profile import` shells to `unzip`) and avoids + /// pulling in a third-party zip library. + private nonisolated func unzip(zipPath: String, intoDir: String) throws { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/unzip") + process.arguments = ["-qq", "-o", zipPath, "-d", intoDir] + + let outPipe = Pipe() + let errPipe = Pipe() + process.standardOutput = outPipe + process.standardError = errPipe + + // Foundation dup()s these handles into the child on `run()`, but the + // parent copies stay open until explicitly released. Both ends must + // be closed or each Process spawn leaks 4 fds. + func closePipes() { + try? outPipe.fileHandleForReading.close() + try? outPipe.fileHandleForWriting.close() + try? errPipe.fileHandleForReading.close() + try? errPipe.fileHandleForWriting.close() + } + + do { + try process.run() + } catch { + closePipes() + throw ProjectTemplateError.unzipFailed(error.localizedDescription) + } + process.waitUntilExit() + let errData = try? errPipe.fileHandleForReading.readToEnd() + closePipes() + + guard process.terminationStatus == 0 else { + let err = errData.flatMap { String(data: $0, encoding: .utf8) } ?? "" + throw ProjectTemplateError.unzipFailed(err.isEmpty ? "exit \(process.terminationStatus)" : err) + } + } + + /// Recursively walk `dir` and return every file (not directory) as a + /// path relative to `dir`. Skips symlinks entirely — templates should + /// never contain them, and following them could escape the unpack dir. + /// + /// Both the base dir and the enumerated URLs are resolved via + /// `resolvingSymlinksInPath` before comparison. On macOS, temp dirs + /// under `/var/folders/…` resolve to `/private/var/folders/…`, so a + /// naive string-prefix check would produce malformed relative paths + /// when the base is unresolved but enumerated URLs are resolved. + nonisolated private static func walk(_ dir: String) throws -> [String] { + var results: [String] = [] + let baseURL = URL(fileURLWithPath: dir).resolvingSymlinksInPath() + let basePath = baseURL.path.hasSuffix("/") ? baseURL.path : baseURL.path + "/" + let enumerator = FileManager.default.enumerator( + at: baseURL, + includingPropertiesForKeys: [.isRegularFileKey, .isSymbolicLinkKey], + options: [.skipsHiddenFiles] + ) + while let url = enumerator?.nextObject() as? URL { + let values = try url.resourceValues(forKeys: [.isRegularFileKey, .isSymbolicLinkKey]) + if values.isSymbolicLink == true { + throw ProjectTemplateError.unsafeZipEntry(url.path) + } + guard values.isRegularFile == true else { continue } + var full = url.resolvingSymlinksInPath().path + if full.hasPrefix(basePath) { + full.removeFirst(basePath.count) + } + if full.contains("..") { + throw ProjectTemplateError.unsafeZipEntry(full) + } + results.append(full) + } + return results + } + + nonisolated private static func readCronJobs(unpackedDir: String) throws -> [TemplateCronJobSpec] { + let path = unpackedDir + "/cron/jobs.json" + guard FileManager.default.fileExists(atPath: path) else { return [] } + let data: Data + do { + data = try Data(contentsOf: URL(fileURLWithPath: path)) + } catch { + throw ProjectTemplateError.requiredFileMissing("cron/jobs.json") + } + do { + return try JSONDecoder().decode([TemplateCronJobSpec].self, from: data) + } catch { + throw ProjectTemplateError.manifestParseFailed("cron/jobs.json: \(error.localizedDescription)") + } + } + + /// Verify the manifest's `contents` claim exactly matches the unpacked + /// files. Any mismatch — claimed-but-missing or present-but-unclaimed — + /// throws, so the preview sheet the user sees is always accurate. + nonisolated private static func verifyClaims( + manifest: ProjectTemplateManifest, + files: [String], + cronJobCount: Int + ) throws { + let fileSet = Set(files) + + if manifest.contents.dashboard { + if !fileSet.contains("dashboard.json") { + throw ProjectTemplateError.requiredFileMissing("dashboard.json") + } + } + if manifest.contents.agentsMd { + if !fileSet.contains("AGENTS.md") { + throw ProjectTemplateError.requiredFileMissing("AGENTS.md") + } + } + // README and AGENTS are always required; dashboard is always required + // per spec. `contents.dashboard`/`contents.agentsMd` exist so a future + // schema can relax those rules; for v1 we hard-require them regardless. + if !fileSet.contains("README.md") { + throw ProjectTemplateError.requiredFileMissing("README.md") + } + if !fileSet.contains("AGENTS.md") { + throw ProjectTemplateError.requiredFileMissing("AGENTS.md") + } + if !fileSet.contains("dashboard.json") { + throw ProjectTemplateError.requiredFileMissing("dashboard.json") + } + + if let claimed = manifest.contents.instructions { + for rel in claimed { + let full = "instructions/" + rel + if !fileSet.contains(full) { + throw ProjectTemplateError.contentClaimMismatch( + "manifest lists \(full) but the file is missing from the bundle" + ) + } + } + let present = fileSet.filter { $0.hasPrefix("instructions/") } + let claimedFull = Set(claimed.map { "instructions/" + $0 }) + if let extra = present.first(where: { !claimedFull.contains($0) }) { + throw ProjectTemplateError.contentClaimMismatch( + "bundle contains \(extra) but it's not listed in manifest.contents.instructions" + ) + } + } else if fileSet.contains(where: { $0.hasPrefix("instructions/") }) { + throw ProjectTemplateError.contentClaimMismatch( + "bundle has instructions/ files but manifest.contents.instructions is missing" + ) + } + + if let claimed = manifest.contents.skills { + for name in claimed { + let prefix = "skills/" + name + "/" + if !fileSet.contains(where: { $0.hasPrefix(prefix) }) { + throw ProjectTemplateError.contentClaimMismatch( + "manifest lists skill \(name) but skills/\(name)/ has no files" + ) + } + } + let presentSkills = Set(fileSet.compactMap { path -> String? in + guard path.hasPrefix("skills/") else { return nil } + let rest = path.dropFirst("skills/".count) + return rest.split(separator: "/", maxSplits: 1).first.map(String.init) + }) + let claimedSet = Set(claimed) + if let extra = presentSkills.subtracting(claimedSet).first { + throw ProjectTemplateError.contentClaimMismatch( + "bundle contains skills/\(extra)/ but it's not listed in manifest.contents.skills" + ) + } + } else if fileSet.contains(where: { $0.hasPrefix("skills/") }) { + throw ProjectTemplateError.contentClaimMismatch( + "bundle contains skills/ but manifest.contents.skills is missing" + ) + } + + let claimedCron = manifest.contents.cron ?? 0 + if claimedCron != cronJobCount { + throw ProjectTemplateError.contentClaimMismatch( + "manifest.contents.cron=\(claimedCron) but bundle contains \(cronJobCount) cron jobs" + ) + } + + let hasMemoryFile = fileSet.contains("memory/append.md") + let claimsMemory = manifest.contents.memory?.append == true + if claimsMemory != hasMemoryFile { + throw ProjectTemplateError.contentClaimMismatch( + "manifest.contents.memory.append=\(claimsMemory) disagrees with memory/append.md presence=\(hasMemoryFile)" + ) + } + + // Config claim must match the schema's actual field count so + // the preview sheet is honest about the size of the configure + // step. `nil` in contents means "no schema" just like `0`; + // we normalise both to 0 before comparing. + let claimedConfig = manifest.contents.config ?? 0 + let actualConfig = manifest.config?.fields.count ?? 0 + if claimedConfig != actualConfig { + throw ProjectTemplateError.contentClaimMismatch( + "manifest.contents.config=\(claimedConfig) but config.schema has \(actualConfig) field(s)" + ) + } + } + + /// Resolve a project-registry name that doesn't collide. Deterministic + /// — given the same existing registry, always returns the same answer. + nonisolated private static func uniqueProjectName( + preferred: String, + context: ServerContext + ) -> String { + let existing = Set(ProjectDashboardService(context: context).loadRegistry().projects.map(\.name)) + if !existing.contains(preferred) { return preferred } + var i = 2 + while existing.contains("\(preferred) \(i)") { + i += 1 + } + return "\(preferred) \(i)" + } +} diff --git a/scarf/scarf/Core/Services/ProjectTemplateUninstaller.swift b/scarf/scarf/Core/Services/ProjectTemplateUninstaller.swift new file mode 100644 index 0000000..93f6830 --- /dev/null +++ b/scarf/scarf/Core/Services/ProjectTemplateUninstaller.swift @@ -0,0 +1,329 @@ +import Foundation +import os + +/// Reverses the work of `ProjectTemplateInstaller`, driven by the +/// `/.scarf/template.lock.json` the installer dropped. Symmetric +/// with the installer: `loadUninstallPlan(for:)` builds a plan the preview +/// sheet can display honestly; `uninstall(plan:)` executes it. No hidden +/// side effects — every path the uninstaller touches is in the plan. +/// +/// **User-added files are preserved.** The lock records exactly what the +/// installer wrote; any file the user created in the project dir after +/// install (e.g. a `sites.txt` or `status-log.md` authored by the agent +/// on first run) is listed as an "extra entry" in the plan and left on +/// disk. If the project dir ends up empty after removing lock-tracked +/// files, the dir itself is removed; otherwise the dir (with user content) +/// stays. +struct ProjectTemplateUninstaller: Sendable { + private static let logger = Logger(subsystem: "com.scarf", category: "ProjectTemplateUninstaller") + + let context: ServerContext + + nonisolated init(context: ServerContext = .local) { + self.context = context + } + + // MARK: - Detection + + /// Is the given project installed from a template that we can + /// uninstall cleanly? Cheap — just a file-existence check on the lock + /// path. + nonisolated func isTemplateInstalled(project: ProjectEntry) -> Bool { + context.makeTransport().fileExists(lockPath(for: project)) + } + + // MARK: - Planning + + /// Read the lock file, walk the filesystem + cron list, and produce a + /// plan listing every op the uninstaller will perform. Does not + /// modify anything. + nonisolated func loadUninstallPlan(for project: ProjectEntry) throws -> TemplateUninstallPlan { + let transport = context.makeTransport() + let path = lockPath(for: project) + guard transport.fileExists(path) else { + throw ProjectTemplateError.lockFileMissing(path) + } + let lockData: Data + do { + lockData = try transport.readFile(path) + } catch { + throw ProjectTemplateError.lockFileParseFailed(error.localizedDescription) + } + let lock: TemplateLock + do { + lock = try JSONDecoder().decode(TemplateLock.self, from: lockData) + } catch { + throw ProjectTemplateError.lockFileParseFailed(error.localizedDescription) + } + + // Partition tracked project files into present vs. already-gone. + // The lock file itself is always in `projectFiles` — the installer + // doesn't explicitly record it, but the preview sheet and the + // execute step must remove it. + var lockTrackedFiles = lock.projectFiles + lockTrackedFiles.append(path) + var toRemove: [String] = [] + var alreadyGone: [String] = [] + for file in lockTrackedFiles { + if transport.fileExists(file) { + toRemove.append(file) + } else { + alreadyGone.append(file) + } + } + + // Scan the project dir for entries that AREN'T in the lock — these + // are user-added and we preserve them. An empty project dir (after + // removing lock-tracked files) gets removed too. + let trackedSet = Set(lockTrackedFiles) + let extras = try enumerateProjectDirExtras( + projectDir: project.path, + trackedPaths: trackedSet, + transport: transport + ) + let projectDirBecomesEmpty = extras.isEmpty + + // Resolve cron job ids by matching lock names against the live + // list. Names that no longer exist go into the already-gone bucket + // — the user likely removed them by hand. + let currentJobs = HermesFileService(context: context).loadCronJobs() + var cronToRemove: [(id: String, name: String)] = [] + var cronGone: [String] = [] + for name in lock.cronJobNames { + if let match = currentJobs.first(where: { $0.name == name }) { + cronToRemove.append((id: match.id, name: match.name)) + } else { + cronGone.append(name) + } + } + + // Memory block detection. The installer wraps its appendix between + // `` / `:end -->` markers; look + // for the begin marker in the current MEMORY.md. If it's missing + // (never installed, or removed by hand) we simply skip the memory + // strip step. + let memoryPath = context.paths.memoryMD + var memoryBlockPresent = false + if lock.memoryBlockId != nil { + if transport.fileExists(memoryPath), + let data = try? transport.readFile(memoryPath), + let text = String(data: data, encoding: .utf8) { + let beginMarker = ProjectTemplateService.memoryBlockBeginMarker( + templateId: lock.memoryBlockId! + ) + memoryBlockPresent = text.contains(beginMarker) + } + } + + return TemplateUninstallPlan( + lock: lock, + project: project, + projectFilesToRemove: toRemove, + projectFilesAlreadyGone: alreadyGone, + extraProjectEntries: extras, + projectDirBecomesEmpty: projectDirBecomesEmpty, + skillsNamespaceDir: lock.skillsNamespaceDir, + cronJobsToRemove: cronToRemove, + cronJobsAlreadyGone: cronGone, + memoryBlockPresent: memoryBlockPresent, + memoryPath: memoryPath + ) + } + + // MARK: - Execution + + /// Execute the plan. Non-atomic: steps run in order, and if any step + /// throws, later steps don't run. v1 doesn't ship rollback — the lock + /// file itself is only removed at the very end, so a mid-flight + /// failure leaves enough breadcrumbs for the user to retry or finish + /// by hand. + nonisolated func uninstall(plan: TemplateUninstallPlan) throws { + let transport = context.makeTransport() + + // 1. Project files (tracked only — user additions untouched). + for file in plan.projectFilesToRemove { + do { + try transport.removeFile(file) + } catch { + Self.logger.warning("couldn't remove project file \(file, privacy: .public): \(error.localizedDescription, privacy: .public)") + // keep going — partial cleanup is better than bailing and + // leaving orphan skills/cron state + } + } + if plan.projectDirBecomesEmpty, transport.fileExists(plan.project.path) { + do { + try transport.removeFile(plan.project.path) + } catch { + Self.logger.warning("couldn't remove empty project dir \(plan.project.path, privacy: .public): \(error.localizedDescription, privacy: .public)") + } + } + + // 2. Skills namespace dir (always removed wholesale — it's + // isolated, never mixed with user skills). + if let skillsDir = plan.skillsNamespaceDir, transport.fileExists(skillsDir) { + try removeRecursively(skillsDir, transport: transport) + } + + // 3. Cron jobs via CLI — `hermes cron remove `. A non-zero + // exit gets logged but doesn't abort the uninstall; leaving a + // stray cron job is better than leaving it AND the skills/memory + // state that was supposed to pair with it. + for job in plan.cronJobsToRemove { + let (output, exit) = context.runHermes(["cron", "remove", job.id]) + if exit != 0 { + Self.logger.warning("failed to remove cron job \(job.id, privacy: .public) \(job.name, privacy: .public): \(output, privacy: .public)") + } + } + + // 4. Memory block — strip the bracketed block in place. Safe + // when the block is absent; we already decided presence in the + // plan and only come here when `memoryBlockPresent` was true + // AND the plan recorded a memoryBlockId. + if plan.memoryBlockPresent, let blockId = plan.lock.memoryBlockId { + try stripMemoryBlock(blockId: blockId, memoryPath: plan.memoryPath, transport: transport) + } + + // 4a. Config Keychain items — remove every secret the template's + // install step stashed in the login Keychain. Items that were + // already deleted (e.g. user cleaned them with Keychain Access) + // hit the `errSecItemNotFound` no-op path inside the wrapper, so + // a stale lock doesn't abort the rest of the uninstall. + let keychain = ProjectConfigKeychain() + for uri in plan.lock.configKeychainItems ?? [] { + guard let ref = TemplateKeychainRef.parse(uri) else { + Self.logger.warning("lock recorded unparseable keychain uri \(uri, privacy: .public); skipping") + continue + } + do { + try keychain.delete(ref: ref) + } catch { + Self.logger.warning("couldn't delete keychain item \(uri, privacy: .public): \(error.localizedDescription, privacy: .public)") + } + } + + // 5. Projects registry — remove the entry by path (more stable + // than name: user may have renamed the project in the UI). + let dashboardService = ProjectDashboardService(context: context) + var registry = dashboardService.loadRegistry() + registry.projects.removeAll { $0.path == plan.project.path } + // saveRegistry throws now — log a write failure but don't abort + // the uninstall. Every earlier step already completed (files + // removed, skills removed, cron jobs removed, memory stripped, + // Keychain cleared); failing here leaves a stale registry row + // pointing at a deleted project — cosmetic and easy to fix + // from the sidebar. + do { + try dashboardService.saveRegistry(registry) + } catch { + Self.logger.warning("uninstall couldn't rewrite projects registry: \(error.localizedDescription, privacy: .public)") + } + + Self.logger.info("uninstalled template \(plan.lock.templateId, privacy: .public) from \(plan.project.path, privacy: .public)") + } + + // MARK: - Helpers + + nonisolated private func lockPath(for project: ProjectEntry) -> String { + project.path + "/.scarf/template.lock.json" + } + + /// Walk the project dir and return the absolute paths of every entry + /// not in `trackedPaths`. `.scarf/` (and its remaining contents after + /// the lock is recorded) is filtered out because the installer owns + /// that directory entirely — if the user dropped a file into it, + /// that's on them, but the common case is that `.scarf/` only holds + /// our dashboard.json + template.lock.json. + nonisolated private func enumerateProjectDirExtras( + projectDir: String, + trackedPaths: Set, + transport: any ServerTransport + ) throws -> [String] { + guard transport.fileExists(projectDir) else { return [] } + var extras: [String] = [] + let entries: [String] + do { + entries = try transport.listDirectory(projectDir) + } catch { + return [] + } + for entry in entries { + let full = projectDir + "/" + entry + // Skip the .scarf/ dir entirely when deciding "does the + // project dir have user content?" — the only files we put + // there (dashboard.json + lock) are tracked already, and + // if they're still there the overall project is not yet + // "empty." + if entry == ".scarf" { continue } + if trackedPaths.contains(full) { continue } + extras.append(full) + } + return extras + } + + /// Recursively delete a directory via the transport. The transport's + /// `removeFile` works on files and on empty directories; we walk + /// children first, then remove the now-empty parent. + nonisolated private func removeRecursively( + _ path: String, + transport: any ServerTransport + ) throws { + guard transport.fileExists(path) else { return } + if transport.stat(path)?.isDirectory != true { + try transport.removeFile(path) + return + } + let entries = (try? transport.listDirectory(path)) ?? [] + for entry in entries { + try removeRecursively(path + "/" + entry, transport: transport) + } + try transport.removeFile(path) + } + + /// Remove the ` … :end -->` block + /// from MEMORY.md, preserving everything else. A missing end marker + /// is logged but doesn't fail — we strip from the begin marker to + /// EOF in that case, on the theory that a broken template block is + /// worse than a slightly aggressive strip. + nonisolated private func stripMemoryBlock( + blockId: String, + memoryPath: String, + transport: any ServerTransport + ) throws { + let beginMarker = ProjectTemplateService.memoryBlockBeginMarker(templateId: blockId) + let endMarker = ProjectTemplateService.memoryBlockEndMarker(templateId: blockId) + + let data = try transport.readFile(memoryPath) + guard let text = String(data: data, encoding: .utf8) else { return } + guard let beginRange = text.range(of: beginMarker) else { return } + + let stripRange: Range + if let endRange = text.range(of: endMarker, range: beginRange.upperBound.. text.startIndex { + let prev = text.index(before: lower) + if text[prev] == "\n", prev > text.startIndex { + let prevPrev = text.index(before: prev) + if text[prevPrev] == "\n" { + lower = prev + } + } + } + let updated = text.replacingCharacters(in: lower.. Bool { + if url.isFileURL { + return handleFileURL(url) + } + if url.scheme?.lowercased() == "scarf" { + return handleScarfURL(url) + } + Self.logger.warning("Ignored URL with unknown scheme: \(url.absoluteString, privacy: .public)") + return false + } + + private func handleFileURL(_ url: URL) -> Bool { + guard url.pathExtension.lowercased() == "scarftemplate" else { + Self.logger.warning("file:// URL handed to Scarf but not a .scarftemplate: \(url.absoluteString, privacy: .public)") + return false + } + pendingInstallURL = url + Self.logger.info("file:// install staged \(url.path, privacy: .public)") + return true + } + + private func handleScarfURL(_ url: URL) -> Bool { + guard url.host?.lowercased() == "install" else { + Self.logger.warning("Ignored unknown scarf:// host: \(url.absoluteString, privacy: .public)") + return false + } + guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false), + let raw = components.queryItems?.first(where: { $0.name == "url" })?.value, + let remote = URL(string: raw) else { + Self.logger.warning("scarf://install missing or invalid ?url=: \(url.absoluteString, privacy: .public)") + return false + } + // Refuse anything but https — defense-in-depth against a browser or + // mail client that would happily hand us a javascript: or http:// + // URL pointing at something unexpected. + guard remote.scheme?.lowercased() == "https" else { + Self.logger.warning("scarf://install refused non-https url=\(remote.absoluteString, privacy: .public)") + return false + } + pendingInstallURL = remote + Self.logger.info("scarf://install staged \(remote.absoluteString, privacy: .public)") + return true + } + + /// Called by the install sheet once it has picked up the URL. + func consume() { + pendingInstallURL = nil + } +} diff --git a/scarf/scarf/Features/Cron/ViewModels/CronViewModel.swift b/scarf/scarf/Features/Cron/ViewModels/CronViewModel.swift index 834e9c8..6ae4196 100644 --- a/scarf/scarf/Features/Cron/ViewModels/CronViewModel.swift +++ b/scarf/scarf/Features/Cron/ViewModels/CronViewModel.swift @@ -65,7 +65,61 @@ final class CronViewModel { } func runNow(_ job: HermesCronJob) { - runAndReload(["cron", "run", job.id], success: "Scheduled for next tick") + // `hermes cron run ` only marks the job as due on the next + // scheduler tick — it doesn't actually execute. If the Hermes + // gateway's scheduler isn't running (common during dev + right + // after install), the user's "Run now" click results in zero + // visible effect because the tick never comes. We follow up + // with `hermes cron tick` which runs all due jobs once and + // exits. Redundant-but-harmless when the gateway is running; + // the actual trigger when it isn't. + // + // Feedback model: show a "Agent started" toast as soon as + // `cron run` succeeds, WITHOUT waiting for `cron tick` to + // return. Agent jobs routinely run past a minute (network IO + + // an LLM call + a file rewrite), and earlier versions with a + // 60s tick timeout surfaced a misleading "Run failed" toast + // every time while the job kept running in the background. + // The app's HermesFileWatcher picks up the dashboard.json + // rewrite that the agent lands at the end — that's what the + // user actually watches for, not this toast. + let svc = fileService + let jobID = job.id + Task.detached { [weak self] in + let runResult = svc.runHermesCLI(args: ["cron", "run", jobID], timeout: 30) + await MainActor.run { [weak self] in + guard let self else { return } + if runResult.exitCode != 0 { + self.message = "Run failed to queue: \(runResult.output.prefix(200))" + self.logger.warning("cron run failed: \(runResult.output)") + self.load() + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in + self?.message = nil + } + return + } + self.message = "Agent started — dashboard will update when it finishes" + self.load() + } + // `cron run` is queued; now force the tick. The 300s + // timeout catches truly stuck processes without killing + // the long-but-valid agent case that blew up the 60s + // version. A timeout here is survivable — the Hermes + // scheduler re-runs due jobs on its own cadence — so we + // log but don't surface it as a failure toast. + try? await Task.sleep(for: .milliseconds(250)) + let tickResult = svc.runHermesCLI(args: ["cron", "tick"], timeout: 300) + await MainActor.run { [weak self] in + guard let self else { return } + if tickResult.exitCode != 0 { + self.logger.warning("cron tick exited non-zero (job may still complete via scheduler): \(tickResult.output)") + } + self.load() + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in + self?.message = nil + } + } + } } func deleteJob(_ job: HermesCronJob) { diff --git a/scarf/scarf/Features/Projects/ViewModels/ProjectsViewModel.swift b/scarf/scarf/Features/Projects/ViewModels/ProjectsViewModel.swift index cd46faf..53bfb14 100644 --- a/scarf/scarf/Features/Projects/ViewModels/ProjectsViewModel.swift +++ b/scarf/scarf/Features/Projects/ViewModels/ProjectsViewModel.swift @@ -1,7 +1,9 @@ import Foundation +import os @Observable final class ProjectsViewModel { + private let logger = Logger(subsystem: "com.scarf", category: "ProjectsViewModel") let context: ServerContext private let service: ProjectDashboardService @@ -39,7 +41,19 @@ final class ProjectsViewModel { guard !registry.projects.contains(where: { $0.name == name }) else { return } let entry = ProjectEntry(name: name, path: path) registry.projects.append(entry) - service.saveRegistry(registry) + // saveRegistry throws now. The VM doesn't currently have a + // surface for user-visible errors (there's no alert/toast in + // the Projects view), so log at error level to the unified + // log and keep the in-memory state consistent with whatever + // landed on disk. If the write fails, the added entry won't + // persist across launches — the user sees it appear + work + // this session, then it's gone at relaunch. Not ideal, but + // matches today's UX and flagged for a proper alert later. + do { + try service.saveRegistry(registry) + } catch { + logger.error("addProject couldn't persist registry: \(error.localizedDescription, privacy: .public)") + } projects = registry.projects selectProject(entry) } @@ -47,7 +61,11 @@ final class ProjectsViewModel { func removeProject(_ project: ProjectEntry) { var registry = service.loadRegistry() registry.projects.removeAll { $0.name == project.name } - service.saveRegistry(registry) + do { + try service.saveRegistry(registry) + } catch { + logger.error("removeProject couldn't persist registry: \(error.localizedDescription, privacy: .public)") + } projects = registry.projects if selectedProject?.name == project.name { selectedProject = nil diff --git a/scarf/scarf/Features/Projects/Views/ProjectsView.swift b/scarf/scarf/Features/Projects/Views/ProjectsView.swift index c8c1154..f80be89 100644 --- a/scarf/scarf/Features/Projects/Views/ProjectsView.swift +++ b/scarf/scarf/Features/Projects/Views/ProjectsView.swift @@ -1,4 +1,5 @@ import SwiftUI +import UniformTypeIdentifiers private enum DashboardTab: String, CaseIterable { case dashboard = "Dashboard" @@ -14,12 +15,40 @@ private enum DashboardTab: String, CaseIterable { struct ProjectsView: View { @State private var viewModel: ProjectsViewModel + @State private var installerViewModel: TemplateInstallerViewModel + @State private var uninstallerViewModel: TemplateUninstallerViewModel @Environment(AppCoordinator.self) private var coordinator @Environment(HermesFileWatcher.self) private var fileWatcher + @Environment(\.serverContext) private var serverContext @State private var showingAddSheet = false + @State private var showingInstallSheet = false + @State private var exportSheetProject: ProjectEntry? + @State private var showingInstallURLPrompt = false + @State private var installURLInput = "" + @State private var showingUninstallSheet = false + @State private var configEditorProject: ProjectEntry? + /// Project queued for the "remove from list" confirmation dialog. + /// Non-nil while the dialog is up; the `confirmationDialog` binding + /// flips based on presence. We store the full entry (not just a + /// flag) so the dialog's action closure knows which project to + /// drop from the registry. + @State private var pendingRemoveFromList: ProjectEntry? + + private let uninstaller: ProjectTemplateUninstaller init(context: ServerContext) { _viewModel = State(initialValue: ProjectsViewModel(context: context)) + _installerViewModel = State(initialValue: TemplateInstallerViewModel(context: context)) + _uninstallerViewModel = State(initialValue: TemplateUninstallerViewModel(context: context)) + self.uninstaller = ProjectTemplateUninstaller(context: context) + } + + /// True when the given project has a cached manifest (i.e. was + /// installed from a schemaful template). Cheap — just a file + /// existence check via the transport. + private func isConfigurable(_ project: ProjectEntry) -> Bool { + let path = ProjectConfigService.manifestCachePath(for: project) + return serverContext.makeTransport().fileExists(path) } @State private var selectedTab: DashboardTab = .dashboard @@ -32,6 +61,7 @@ struct ProjectsView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } .navigationTitle("Projects") + .toolbar { templatesToolbar } .task { viewModel.load() if let name = coordinator.selectedProjectName, @@ -39,11 +69,195 @@ struct ProjectsView: View { viewModel.selectProject(project) } fileWatcher.updateProjectWatches(viewModel.dashboardPaths) + // Cold-launch deep link or Finder double-click: the router may + // have a URL staged before this view installed the onChange + // observer below. Without this first-appearance check, + // SwiftUI's .onChange would never fire (it only reacts to + // *changes* after installation) and the URL would sit on the + // singleton forever. + if let pending = TemplateURLRouter.shared.pendingInstallURL { + dispatchPendingInstall(pending) + } } .onChange(of: fileWatcher.lastChangeDate) { viewModel.load() fileWatcher.updateProjectWatches(viewModel.dashboardPaths) } + .onChange(of: TemplateURLRouter.shared.pendingInstallURL) { _, new in + // A URL landed *while the app was already running*. + if let new { + dispatchPendingInstall(new) + } + } + .sheet(isPresented: $showingInstallSheet) { + TemplateInstallSheet(viewModel: installerViewModel) { entry in + viewModel.load() + coordinator.selectedProjectName = entry.name + if let project = viewModel.projects.first(where: { $0.name == entry.name }) { + viewModel.selectProject(project) + } + fileWatcher.updateProjectWatches(viewModel.dashboardPaths) + } + } + .sheet(item: $exportSheetProject) { project in + TemplateExportSheet( + viewModel: TemplateExporterViewModel(context: serverContext, project: project) + ) + } + .sheet(isPresented: $showingInstallURLPrompt) { + installURLSheet + } + .sheet(isPresented: $showingUninstallSheet) { + TemplateUninstallSheet(viewModel: uninstallerViewModel) { removed in + // Refresh the registry and clear selection if we just + // removed the project the user was viewing. + if viewModel.selectedProject?.path == removed.path { + viewModel.selectedProject = nil + } + if coordinator.selectedProjectName == removed.name { + coordinator.selectedProjectName = nil + } + viewModel.load() + fileWatcher.updateProjectWatches(viewModel.dashboardPaths) + } + } + .sheet(item: $configEditorProject) { project in + ConfigEditorSheet( + context: serverContext, + project: project + ) + } + // Confirmation dialog for the sidebar's "Remove from List" action. + // The action is registry-only (doesn't touch disk), but the name + // historically confused users into thinking it was a full delete. + // A confirmation with explicit wording clarifies scope before the + // click is destructive-looking but actually harmless. + .confirmationDialog( + removeFromListDialogTitle, + isPresented: Binding( + get: { pendingRemoveFromList != nil }, + set: { if !$0 { pendingRemoveFromList = nil } } + ), + titleVisibility: .visible, + presenting: pendingRemoveFromList + ) { project in + Button("Remove from List") { + viewModel.removeProject(project) + if coordinator.selectedProjectName == project.name { + coordinator.selectedProjectName = nil + } + pendingRemoveFromList = nil + } + Button("Cancel", role: .cancel) { + pendingRemoveFromList = nil + } + } message: { project in + Text( + "\(project.name) will be removed from Scarf's project list. " + + "Nothing on disk is touched — the folder, cron job, skills, and memory block all stay. " + + "To actually remove installed files, use \"Uninstall Template…\" instead." + ) + } + } + + /// Title string for the remove-from-list confirmation dialog. Kept + /// as a computed property so the dialog and any future reuse share + /// the exact same copy. + private var removeFromListDialogTitle: LocalizedStringKey { + "Remove from Scarf's project list?" + } + + // MARK: - Toolbar + + @ToolbarContentBuilder + private var templatesToolbar: some ToolbarContent { + ToolbarItem(placement: .primaryAction) { + Menu { + Button("Install from File…", systemImage: "tray.and.arrow.down") { + openInstallFilePicker() + } + Button("Install from URL…", systemImage: "link") { + installURLInput = "" + showingInstallURLPrompt = true + } + Divider() + if let selected = viewModel.selectedProject { + Button("Export \"\(selected.name)\" as Template…", systemImage: "tray.and.arrow.up") { + exportSheetProject = selected + } + } else { + Button("Export as Template…", systemImage: "tray.and.arrow.up") {} + .disabled(true) + } + } label: { + Label("Templates", systemImage: "shippingbox") + } + } + } + + private var installURLSheet: some View { + VStack(alignment: .leading, spacing: 12) { + Text("Install Template from URL") + .font(.headline) + Text("Paste an https URL pointing at a .scarftemplate file.") + .font(.caption) + .foregroundStyle(.secondary) + TextField("https://example.com/my.scarftemplate", text: $installURLInput) + .textFieldStyle(.roundedBorder) + HStack { + Button("Cancel") { showingInstallURLPrompt = false } + .keyboardShortcut(.cancelAction) + Spacer() + Button("Install") { + if let url = URL(string: installURLInput), url.scheme?.lowercased() == "https" { + installerViewModel.openRemoteURL(url) + showingInstallURLPrompt = false + showingInstallSheet = true + } + } + .keyboardShortcut(.defaultAction) + .buttonStyle(.borderedProminent) + .disabled(URL(string: installURLInput)?.scheme?.lowercased() != "https") + } + } + .padding() + .frame(minWidth: 480) + } + + /// Route a pending install URL to the right VM entry point. `file://` + /// URLs come from Finder double-clicks + the "Install from File…" flow + /// when routed via the router; `https://` URLs come from `scarf://` + /// deep links and the "Install from URL…" prompt. + private func dispatchPendingInstall(_ url: URL) { + if url.isFileURL { + installerViewModel.openLocalFile(url.path) + } else { + installerViewModel.openRemoteURL(url) + } + TemplateURLRouter.shared.consume() + showingInstallSheet = true + } + + private func openInstallFilePicker() { + let panel = NSOpenPanel() + panel.canChooseDirectories = false + panel.canChooseFiles = true + panel.allowsMultipleSelection = false + // Accept both the declared Scarf template UTI and plain zip — the + // custom UTI wins for files with the .scarftemplate extension, and + // the zip fallback means an author distributing under .zip (e.g. + // before the UTI is registered on the receiving Mac) still works. + var types: [UTType] = [.zip] + if let templateType = UTType("com.scarf.template") { + types.insert(templateType, at: 0) + } + panel.allowedContentTypes = types + panel.allowsOtherFileTypes = true + panel.prompt = String(localized: "Install Template") + if panel.runModal() == .OK, let url = panel.url { + installerViewModel.openLocalFile(url.path) + showingInstallSheet = true + } } // MARK: - Project List @@ -65,6 +279,32 @@ struct ProjectsView: View { Text(project.name) } .tag(project) + .contextMenu { + if isConfigurable(project) { + Button("Configuration…", systemImage: "slider.horizontal.3") { + configEditorProject = project + } + } + if uninstaller.isTemplateInstalled(project: project) { + // "Uninstall Template…" only appears for projects + // installed from a `.scarftemplate`. Trailing + // ellipsis signals a confirmation sheet follows + // (macOS HIG convention); the sheet itself lists + // every file/cron/skill that will be removed. + Button("Uninstall Template (remove installed files)…", systemImage: "trash") { + uninstallerViewModel.begin(project: project) + showingUninstallSheet = true + } + Divider() + } + // "Remove from List" used to be "Remove from Scarf", + // which users read as a full delete. Clarified label + + // ellipsis + confirmation dialog all spell out that + // this is registry-only; nothing on disk is touched. + Button("Remove from List (keep files)…", systemImage: "minus.circle") { + pendingRemoveFromList = project + } + } } .listStyle(.sidebar) @@ -76,10 +316,16 @@ struct ProjectsView: View { .buttonStyle(.borderless) Spacer() if let selected = viewModel.selectedProject { - Button(action: { viewModel.removeProject(selected) }) { + // Route through the same confirmation dialog as the + // context-menu "Remove from List" entry. The minus + // icon is a drive-by click target right next to "+" — + // confirming before mutating the registry stops the + // "I clicked by accident and my project's gone" case. + Button(action: { pendingRemoveFromList = selected }) { Image(systemName: "minus") } .buttonStyle(.borderless) + .help("Remove \(selected.name) from Scarf's project list (files are kept on disk)") } } .padding(8) @@ -216,6 +462,25 @@ struct ProjectsView: View { Image(systemName: "folder") } .buttonStyle(.borderless) + if isConfigurable(project) { + Button { + configEditorProject = project + } label: { + Image(systemName: "slider.horizontal.3") + } + .buttonStyle(.borderless) + .help("Edit configuration") + } + if uninstaller.isTemplateInstalled(project: project) { + Button { + uninstallerViewModel.begin(project: project) + showingUninstallSheet = true + } label: { + Image(systemName: "shippingbox.and.arrow.backward") + } + .buttonStyle(.borderless) + .help("Uninstall template") + } } } } diff --git a/scarf/scarf/Features/Templates/ViewModels/TemplateConfigEditorViewModel.swift b/scarf/scarf/Features/Templates/ViewModels/TemplateConfigEditorViewModel.swift new file mode 100644 index 0000000..cec6bbf --- /dev/null +++ b/scarf/scarf/Features/Templates/ViewModels/TemplateConfigEditorViewModel.swift @@ -0,0 +1,118 @@ +import Foundation +import Observation +import os + +/// Drives the post-install "Configuration" button on the project +/// dashboard. Loads `/.scarf/manifest.json` + `config.json`, +/// hands a `TemplateConfigViewModel` seeded with current values to the +/// sheet, then writes the edited values back on commit. +/// +/// Smaller surface than `TemplateInstallerViewModel` — no unzipping, +/// no parent-dir picking, no cron CLI. Just: read → edit → save. +@Observable +@MainActor +final class TemplateConfigEditorViewModel { + private static let logger = Logger(subsystem: "com.scarf", category: "TemplateConfigEditorViewModel") + + enum Stage: Sendable { + case idle + case loading + /// Manifest + config loaded; the sheet is displaying the form. + case editing + case saving + case succeeded + case failed(String) + /// Project wasn't installed from a schemaful template — no + /// manifest cache on disk. The dashboard button is hidden in + /// this case so we shouldn't hit this stage normally. + case notConfigurable + } + + let context: ServerContext + let project: ProjectEntry + private let configService: ProjectConfigService + + init(context: ServerContext, project: ProjectEntry) { + self.context = context + self.project = project + self.configService = ProjectConfigService(context: context) + } + + var stage: Stage = .idle + var manifest: ProjectTemplateManifest? + var currentValues: [String: TemplateConfigValue] = [:] + + /// Non-nil while `.editing`; used to construct the sheet's VM. + var formViewModel: TemplateConfigViewModel? + + /// Load the cached manifest + current config values, then move to + /// `.editing` so the sheet can render the form. + func begin() { + stage = .loading + let service = configService + let project = project + Task.detached { [weak self] in + do { + guard let cachedManifest = try service.loadCachedManifest(project: project), + let schema = cachedManifest.config, + !schema.isEmpty else { + await MainActor.run { [weak self] in + self?.stage = .notConfigurable + } + return + } + let configFile = try service.load(project: project) + await MainActor.run { [weak self] in + guard let self else { return } + self.manifest = cachedManifest + self.currentValues = configFile?.values ?? [:] + self.formViewModel = TemplateConfigViewModel( + schema: schema, + templateId: cachedManifest.id, + templateSlug: cachedManifest.slug, + initialValues: self.currentValues, + mode: .edit(project: project) + ) + self.stage = .editing + } + } catch { + Self.logger.error("couldn't load config for \(project.path, privacy: .public): \(error.localizedDescription, privacy: .public)") + await MainActor.run { [weak self] in + self?.stage = .failed(error.localizedDescription) + } + } + } + } + + /// Called when the sheet's commit succeeded. Persists the edited + /// values to `/.scarf/config.json`. Secrets are already + /// in the Keychain — the VM's commit step wrote them. + func save(values: [String: TemplateConfigValue]) { + guard let manifest else { return } + stage = .saving + let service = configService + let project = project + Task.detached { [weak self] in + do { + try service.save( + project: project, + templateId: manifest.id, + values: values + ) + await MainActor.run { [weak self] in + self?.stage = .succeeded + } + } catch { + Self.logger.error("couldn't save config for \(project.path, privacy: .public): \(error.localizedDescription, privacy: .public)") + await MainActor.run { [weak self] in + self?.stage = .failed(error.localizedDescription) + } + } + } + } + + func cancel() { + stage = .idle + formViewModel = nil + } +} diff --git a/scarf/scarf/Features/Templates/ViewModels/TemplateConfigViewModel.swift b/scarf/scarf/Features/Templates/ViewModels/TemplateConfigViewModel.swift new file mode 100644 index 0000000..9a26bc9 --- /dev/null +++ b/scarf/scarf/Features/Templates/ViewModels/TemplateConfigViewModel.swift @@ -0,0 +1,198 @@ +import Foundation +import Observation +import os + +/// Drives the configure form for template install + post-install editing. +/// +/// **Timing of secret storage.** The VM keeps freshly-entered secret bytes +/// in-memory (`pendingSecrets`) until the user clicks the commit button. +/// Only then does `commit()` push each secret through +/// `ProjectConfigService.storeSecret` and get back a `keychainRef` URI. +/// This means cancelling the sheet never leaves an orphan Keychain +/// entry behind — the form is transactional from the user's POV. +/// +/// **Validation.** Runs via `ProjectConfigService.validateValues` every +/// time the user attempts to commit. Per-field errors are tracked in +/// `errors` so the sheet can surface them inline with the offending field. +/// No live validation on every keystroke — that creates a messy +/// "error appears the moment you start typing" UX. +@Observable +@MainActor +final class TemplateConfigViewModel { + private static let logger = Logger(subsystem: "com.scarf", category: "TemplateConfigViewModel") + + enum Mode: Sendable { + /// User is filling in values for the first time as part of the + /// install flow. Secrets will be written to the Keychain when + /// `commit` succeeds. + case install + /// User is editing values for an already-installed project. + /// Existing keychain refs are preserved for fields the user + /// doesn't touch; only secrets the user actually changes get + /// re-written to the Keychain. + case edit(project: ProjectEntry) + } + + let schema: TemplateConfigSchema + let templateId: String + let templateSlug: String + let mode: Mode + private let configService: ProjectConfigService + + /// Current form values, keyed by field key. Non-secret values live + /// here directly; secret fields either hold a `.keychainRef(...)` + /// (existing, untouched in edit mode) or nothing at all (user + /// hasn't entered a secret yet, or they just cleared it). + var values: [String: TemplateConfigValue] = [:] + + /// Raw secret bytes waiting to be written to the Keychain on + /// `commit()`. Indexed by field key. `values[key]` stays as its + /// current `.keychainRef(...)` (for edit mode) or missing (for + /// install mode) until commit swaps it for the freshly-written + /// ref URI. + var pendingSecrets: [String: Data] = [:] + + /// One error per field with a problem. Populated by `commit()` on + /// validation failure; the sheet surfaces the message inline below + /// the offending control. + var errors: [String: String] = [:] + + init( + schema: TemplateConfigSchema, + templateId: String, + templateSlug: String, + initialValues: [String: TemplateConfigValue] = [:], + mode: Mode, + configService: ProjectConfigService = ProjectConfigService() + ) { + self.schema = schema + self.templateId = templateId + self.templateSlug = templateSlug + self.mode = mode + self.configService = configService + self.values = Self.applyDefaults(schema: schema, initial: initialValues) + } + + // MARK: - Field setters (the sheet calls these as controls change) + + func setString(_ key: String, _ value: String) { + values[key] = .string(value) + errors.removeValue(forKey: key) + } + + func setNumber(_ key: String, _ value: Double) { + values[key] = .number(value) + errors.removeValue(forKey: key) + } + + func setBool(_ key: String, _ value: Bool) { + values[key] = .bool(value) + errors.removeValue(forKey: key) + } + + func setList(_ key: String, _ items: [String]) { + values[key] = .list(items) + errors.removeValue(forKey: key) + } + + /// Stage a new secret value. Doesn't hit the Keychain until + /// `commit()`. An empty `value` clears both the pending secret and + /// the field's stored keychainRef — only valid in edit mode, where + /// "empty" means "I want to remove this secret." + func setSecret(_ key: String, _ value: String) { + if value.isEmpty { + pendingSecrets.removeValue(forKey: key) + values.removeValue(forKey: key) + } else { + pendingSecrets[key] = Data(value.utf8) + // Keep any existing ref around; the sheet can display + // "(changed)" while the ref is still the old one. commit() + // overwrites on disk. + } + errors.removeValue(forKey: key) + } + + // MARK: - Commit + + /// Validate, persist secrets to the Keychain, and hand back the + /// final values dictionary. On validation failure, `errors` is + /// populated and the method returns `nil` without touching the + /// Keychain — the form is transactional. + /// + /// In install mode, `project` is required (secrets need a path + /// hash for their Keychain account). In edit mode it falls out of + /// the `.edit(project:)` associated value. + func commit(project: ProjectEntry? = nil) -> [String: TemplateConfigValue]? { + // Build the value set we're about to validate. For secrets + // that have a pending update, we treat them as present (we'll + // write them in a moment); for secrets already stored as + // keychainRef, we treat them as present too. Only a completely + // empty secret field is "missing." + var candidate = values + for key in pendingSecrets.keys { + // The field is about to have a fresh keychainRef — for + // validation purposes, use a placeholder ref so the type + // check passes. The real ref replaces it below. + candidate[key] = .keychainRef("pending://\(key)") + } + let validationErrors = ProjectConfigService.validateValues(candidate, against: schema) + guard validationErrors.isEmpty else { + var byField: [String: String] = [:] + for err in validationErrors { + guard let key = err.fieldKey else { continue } + byField[key] = err.message + } + self.errors = byField + return nil + } + + // Validation passed — write the pending secrets to the Keychain. + let targetProject: ProjectEntry + switch mode { + case .install: + guard let project else { + Self.logger.error("commit(project:) called in install mode without a project") + return nil + } + targetProject = project + case .edit(let proj): + targetProject = proj + } + + for (key, secret) in pendingSecrets { + do { + let ref = try configService.storeSecret( + templateSlug: templateSlug, + fieldKey: key, + project: targetProject, + secret: secret + ) + values[key] = ref + } catch { + Self.logger.error("failed to store secret for \(key, privacy: .public): \(error.localizedDescription, privacy: .public)") + errors[key] = "Couldn't save secret to the Keychain: \(error.localizedDescription)" + return nil + } + } + pendingSecrets.removeAll() + errors.removeAll() + return values + } + + // MARK: - Helpers + + /// Seed the form with any author-supplied defaults for fields that + /// don't already have an initial value (from a saved config.json). + nonisolated private static func applyDefaults( + schema: TemplateConfigSchema, + initial: [String: TemplateConfigValue] + ) -> [String: TemplateConfigValue] { + var out = initial + for field in schema.fields where out[field.key] == nil { + if let def = field.defaultValue { + out[field.key] = def + } + } + return out + } +} diff --git a/scarf/scarf/Features/Templates/ViewModels/TemplateExporterViewModel.swift b/scarf/scarf/Features/Templates/ViewModels/TemplateExporterViewModel.swift new file mode 100644 index 0000000..a887ec0 --- /dev/null +++ b/scarf/scarf/Features/Templates/ViewModels/TemplateExporterViewModel.swift @@ -0,0 +1,131 @@ +import Foundation +import os + +/// Drives the template export sheet. Holds form state for the author-facing +/// fields (id, name, version, description, …) and the selection of skills +/// and cron jobs to include, then builds and writes the `.scarftemplate` on +/// confirm. +@Observable +@MainActor +final class TemplateExporterViewModel { + private static let logger = Logger(subsystem: "com.scarf", category: "TemplateExporterViewModel") + + enum Stage: Sendable { + case idle + case exporting + case succeeded(path: String) + case failed(String) + } + + let context: ServerContext + let project: ProjectEntry + private let exporter: ProjectTemplateExporter + + init(context: ServerContext, project: ProjectEntry) { + self.context = context + self.project = project + self.exporter = ProjectTemplateExporter(context: context) + + self.templateName = project.name + self.templateId = "you/\(ProjectTemplateExporter.slugify(project.name))" + } + + // Form fields + var templateId: String + var templateName: String + var templateVersion: String = "1.0.0" + var templateDescription: String = "" + var authorName: String = "" + var authorURL: String = "" + var category: String = "" + var tags: String = "" + var includeSkillIds: Set = [] + var includeCronJobIds: Set = [] + var memoryAppendix: String = "" + + // Derived: what the author can pick from + var availableSkills: [HermesSkill] = [] + var availableCronJobs: [HermesCronJob] = [] + + var stage: Stage = .idle + + func load() { + let ctx = context + Task.detached { [weak self] in + let service = HermesFileService(context: ctx) + let skills = service.loadSkills().flatMap(\.skills) + let jobs = service.loadCronJobs() + await MainActor.run { [weak self] in + self?.availableSkills = skills + self?.availableCronJobs = jobs + } + } + } + + func previewPlan() -> ProjectTemplateExporter.ExportPlan { + exporter.previewPlan(for: currentInputs) + } + + /// Kick off the export, writing to `outputPath`. The caller is + /// responsible for bouncing the user through an `NSSavePanel` to get + /// that path. + func export(to outputPath: String) { + stage = .exporting + let exporter = exporter + let inputs = currentInputs + Task.detached { [weak self] in + do { + try exporter.export(inputs: inputs, outputZipPath: outputPath) + await MainActor.run { [weak self] in + self?.stage = .succeeded(path: outputPath) + } + } catch { + await MainActor.run { [weak self] in + self?.stage = .failed(error.localizedDescription) + } + } + } + } + + // MARK: - Private + + private var currentInputs: ProjectTemplateExporter.ExportInputs { + let parsedTags = tags + .split(separator: ",") + .map { $0.trimmingCharacters(in: .whitespaces) } + .filter { !$0.isEmpty } + let trimmedAppendix = memoryAppendix.trimmingCharacters(in: .whitespacesAndNewlines) + return ProjectTemplateExporter.ExportInputs( + project: project, + templateId: templateId.trimmingCharacters(in: .whitespaces), + templateName: templateName.trimmingCharacters(in: .whitespaces), + templateVersion: templateVersion.trimmingCharacters(in: .whitespaces), + description: templateDescription.trimmingCharacters(in: .whitespaces), + authorName: authorName.isEmpty ? nil : authorName, + authorUrl: authorURL.isEmpty ? nil : authorURL, + category: category.isEmpty ? nil : category, + tags: parsedTags, + includeSkillIds: Array(includeSkillIds), + includeCronJobIds: Array(includeCronJobIds), + memoryAppendix: trimmedAppendix.isEmpty ? nil : trimmedAppendix + ) + } +} + +extension ProjectTemplateExporter { + /// Lowercase-and-hyphenate a human name into something safe for a + /// template id suffix. Only used to seed the default id in the export + /// form — the author can overwrite it. + nonisolated static func slugify(_ raw: String) -> String { + let lower = raw.lowercased() + let mapped = lower.unicodeScalars.map { scalar -> Character in + let c = Character(scalar) + if c.isLetter || c.isNumber { return c } + return "-" + } + let collapsed = String(mapped) + .split(separator: "-", omittingEmptySubsequences: true) + .joined(separator: "-") + return collapsed.isEmpty ? "template" : collapsed + } +} diff --git a/scarf/scarf/Features/Templates/ViewModels/TemplateInstallerViewModel.swift b/scarf/scarf/Features/Templates/ViewModels/TemplateInstallerViewModel.swift new file mode 100644 index 0000000..4b2e476 --- /dev/null +++ b/scarf/scarf/Features/Templates/ViewModels/TemplateInstallerViewModel.swift @@ -0,0 +1,230 @@ +import Foundation +import os + +/// Drives the template install sheet. Handles three entry points: +/// 1. `openLocalFile(_:)` — user picked a `.scarftemplate` from disk. +/// 2. `openRemoteURL(_:)` — user pasted/deeplinked a https URL. +/// 3. `confirmInstall()` — user clicked "Install" in the preview sheet. +/// +/// The view model owns one ephemeral temp dir at a time (the unpacked +/// bundle). `cancel()` or `confirmInstall()` removes it. +@Observable +@MainActor +final class TemplateInstallerViewModel { + private static let logger = Logger(subsystem: "com.scarf", category: "TemplateInstallerViewModel") + + enum Stage: Sendable { + case idle + case fetching(sourceDescription: String) + case inspecting + case awaitingParentDirectory + /// Template declared a non-empty config schema; the sheet + /// presents `TemplateConfigSheet` before continuing to the + /// preview. Schema-less templates skip this stage entirely. + case awaitingConfig + case planned + case installing + case succeeded(installed: ProjectEntry) + case failed(String) + } + + let context: ServerContext + private let templateService: ProjectTemplateService + private let installer: ProjectTemplateInstaller + + init(context: ServerContext) { + self.context = context + self.templateService = ProjectTemplateService(context: context) + self.installer = ProjectTemplateInstaller(context: context) + } + + var stage: Stage = .idle + var inspection: TemplateInspection? + var plan: TemplateInstallPlan? + var chosenParentDirectory: String? + /// README body preloaded off MainActor when inspection completes, so the + /// preview sheet can render it without hitting `String(contentsOf:)` from + /// inside a View body. + var readmeBody: String? + + // MARK: - Entry points + + /// Inspect a local `.scarftemplate` file. Moves stage to `.inspecting` + /// then either `.awaitingParentDirectory` or `.failed`. The unpacked + /// README body is read off MainActor here and stored on the VM so the + /// preview sheet doesn't do sync I/O during View body evaluation. + func openLocalFile(_ zipPath: String) { + resetTempState() + stage = .inspecting + let service = templateService + Task.detached { [weak self] in + do { + let inspection = try service.inspect(zipPath: zipPath) + let readme = Self.readReadme(unpackedDir: inspection.unpackedDir) + await MainActor.run { [weak self] in + guard let self else { return } + self.inspection = inspection + self.readmeBody = readme + self.stage = .awaitingParentDirectory + } + } catch { + await MainActor.run { [weak self] in + self?.stage = .failed(error.localizedDescription) + } + } + } + } + + /// Read README.md from an unpacked template dir. Nonisolated so the + /// inspect task can call it off MainActor. Returns `nil` on any I/O + /// failure — the preview sheet treats a nil README as "no section." + nonisolated private static func readReadme(unpackedDir: String) -> String? { + let path = unpackedDir + "/README.md" + do { + return try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) + } catch { + Logger(subsystem: "com.scarf", category: "TemplateInstallerViewModel") + .warning("couldn't read README at \(path, privacy: .public): \(error.localizedDescription, privacy: .public)") + return nil + } + } + + /// Download a https `.scarftemplate` to a temp file, then hand off to + /// `openLocalFile`. The 50 MB cap matches the plan — templates shouldn't + /// be anywhere near that, and rejecting huge downloads is cheap defense. + /// + /// Content-Length is checked first as an early-out, but chunked + /// transfer responses omit that header. The authoritative check is the + /// actual on-disk file size after the download completes — it runs + /// unconditionally and covers the chunked-transfer case. + func openRemoteURL(_ url: URL) { + resetTempState() + stage = .fetching(sourceDescription: url.host ?? url.absoluteString) + Task.detached { [weak self] in + let maxBytes: Int64 = 50 * 1024 * 1024 + do { + let tempZip = NSTemporaryDirectory() + "scarf-template-download-" + UUID().uuidString + ".scarftemplate" + let (tempURL, response) = try await URLSession.shared.download(from: url) + defer { try? FileManager.default.removeItem(at: tempURL) } + if let httpResponse = response as? HTTPURLResponse { + guard (200...299).contains(httpResponse.statusCode) else { + throw ProjectTemplateError.unzipFailed("HTTP \(httpResponse.statusCode)") + } + if let length = httpResponse.value(forHTTPHeaderField: "Content-Length"), + let bytes = Int64(length), bytes > maxBytes { + throw ProjectTemplateError.unzipFailed("template exceeds 50 MB size cap (\(bytes) bytes)") + } + } + // Unconditional post-download size check — catches chunked + // responses that ship no Content-Length. The download already + // hit disk, but refusing to *process* it bounds the blast + // radius to one temp file that gets removed in the defer. + let attrs = try FileManager.default.attributesOfItem(atPath: tempURL.path) + let actualSize = (attrs[.size] as? NSNumber)?.int64Value ?? 0 + guard actualSize <= maxBytes else { + throw ProjectTemplateError.unzipFailed("template exceeds 50 MB size cap (\(actualSize) bytes)") + } + try FileManager.default.moveItem(atPath: tempURL.path, toPath: tempZip) + await MainActor.run { [weak self] in + self?.openLocalFile(tempZip) + } + } catch { + await MainActor.run { [weak self] in + self?.stage = .failed("Couldn't fetch template: \(error.localizedDescription)") + } + } + } + } + + // MARK: - Planning + confirmation + + /// Finalize the plan now that the user has picked a parent directory. + func pickParentDirectory(_ parentDir: String) { + guard let inspection else { return } + chosenParentDirectory = parentDir + let service = templateService + Task.detached { [weak self] in + do { + let plan = try service.buildPlan(inspection: inspection, parentDir: parentDir) + await MainActor.run { [weak self] in + guard let self else { return } + self.plan = plan + // If the template declares a non-empty config + // schema, insert the configure step before the + // preview sheet. Otherwise go straight to .planned. + if let schema = plan.configSchema, !schema.isEmpty { + self.stage = .awaitingConfig + } else { + self.stage = .planned + } + } + } catch { + await MainActor.run { [weak self] in + self?.stage = .failed(error.localizedDescription) + } + } + } + } + + /// Called by `TemplateInstallSheet` once the user has filled in + /// the configure form and `TemplateConfigViewModel.commit()` + /// succeeded. Stashes the values in the plan and advances to the + /// preview stage (`.planned`). Secrets in `values` are already + /// `.keychainRef(...)` — the VM's commit step wrote them to the + /// Keychain. + func submitConfig(values: [String: TemplateConfigValue]) { + guard var plan else { return } + plan.configValues = values + self.plan = plan + stage = .planned + } + + /// Called when the user cancels out of the configure step without + /// committing. Returns to `.awaitingParentDirectory` so they can + /// try again (or dismiss the whole sheet). + func cancelConfig() { + stage = .awaitingParentDirectory + } + + func confirmInstall() { + guard let plan else { return } + stage = .installing + let installer = installer + let service = templateService + Task.detached { [weak self] in + do { + let entry = try installer.install(plan: plan) + service.cleanupTempDir(plan.unpackedDir) + await MainActor.run { [weak self] in + guard let self else { return } + self.stage = .succeeded(installed: entry) + self.inspection = nil + self.plan = nil + self.chosenParentDirectory = nil + self.readmeBody = nil + } + } catch { + await MainActor.run { [weak self] in + self?.stage = .failed(error.localizedDescription) + } + } + } + } + + // MARK: - Cleanup + + func cancel() { + resetTempState() + stage = .idle + } + + private func resetTempState() { + if let inspection { + templateService.cleanupTempDir(inspection.unpackedDir) + } + inspection = nil + plan = nil + chosenParentDirectory = nil + readmeBody = nil + } +} diff --git a/scarf/scarf/Features/Templates/ViewModels/TemplateUninstallerViewModel.swift b/scarf/scarf/Features/Templates/ViewModels/TemplateUninstallerViewModel.swift new file mode 100644 index 0000000..349eacc --- /dev/null +++ b/scarf/scarf/Features/Templates/ViewModels/TemplateUninstallerViewModel.swift @@ -0,0 +1,110 @@ +import Foundation +import os + +/// Drives the template-uninstall sheet. Mirrors the installer VM in +/// stage shape: open a plan (`begin`), preview it, confirm or cancel. +@Observable +@MainActor +final class TemplateUninstallerViewModel { + private static let logger = Logger(subsystem: "com.scarf", category: "TemplateUninstallerViewModel") + + enum Stage: Sendable { + case idle + case loading + case planned + case uninstalling + case succeeded(removed: ProjectEntry) + case failed(String) + } + + /// Snapshot of "what survived the uninstall" — surfaced in the + /// success screen so the user understands why the project directory + /// is or isn't gone from disk. Computed from the plan right before + /// executing it (`plan` itself is nil'd on success, so we can't + /// reach back for this info after the fact). + struct PreservedOutcome: Sendable { + /// True when the uninstaller removed the project dir (nothing + /// user-owned was left inside). In this case `preservedPaths` + /// is empty and the success view skips the banner entirely. + let projectDirRemoved: Bool + /// Absolute paths of files the uninstaller refused to touch + /// because they weren't installed by the template (typically + /// `status-log.md` after the cron ran, or anything the user + /// dropped into the project dir manually). + let preservedPaths: [String] + /// Project dir — echoed back so the success view can show the + /// user where the orphan files now live. + let projectDir: String + } + + let context: ServerContext + private let uninstaller: ProjectTemplateUninstaller + + init(context: ServerContext) { + self.context = context + self.uninstaller = ProjectTemplateUninstaller(context: context) + } + + var stage: Stage = .idle + var plan: TemplateUninstallPlan? + /// Populated on transition to `.succeeded`. Nil whenever the user + /// re-enters the flow (cancel/begin both clear it). + var preservedOutcome: PreservedOutcome? + + /// Load the `template.lock.json` for the given project and build a + /// removal plan. Moves stage to `.planned` on success. + func begin(project: ProjectEntry) { + stage = .loading + preservedOutcome = nil + let uninstaller = uninstaller + Task.detached { [weak self] in + do { + let plan = try uninstaller.loadUninstallPlan(for: project) + await MainActor.run { [weak self] in + guard let self else { return } + self.plan = plan + self.stage = .planned + } + } catch { + await MainActor.run { [weak self] in + self?.stage = .failed(error.localizedDescription) + } + } + } + } + + func confirmUninstall() { + guard let plan else { return } + stage = .uninstalling + let uninstaller = uninstaller + // Capture the preservation shape before executing — the plan + // itself gets nil'd on success and we want the banner to show + // whatever was true at the moment of removal. + let outcome = PreservedOutcome( + projectDirRemoved: plan.projectDirBecomesEmpty, + preservedPaths: plan.extraProjectEntries, + projectDir: plan.project.path + ) + Task.detached { [weak self] in + do { + try uninstaller.uninstall(plan: plan) + await MainActor.run { [weak self] in + guard let self else { return } + self.preservedOutcome = outcome + self.stage = .succeeded(removed: plan.project) + self.plan = nil + } + } catch { + await MainActor.run { [weak self] in + self?.stage = .failed(error.localizedDescription) + } + } + } + } + + func cancel() { + plan = nil + preservedOutcome = nil + stage = .idle + } +} diff --git a/scarf/scarf/Features/Templates/Views/ConfigEditorSheet.swift b/scarf/scarf/Features/Templates/Views/ConfigEditorSheet.swift new file mode 100644 index 0000000..88d438e --- /dev/null +++ b/scarf/scarf/Features/Templates/Views/ConfigEditorSheet.swift @@ -0,0 +1,133 @@ +import SwiftUI + +/// Post-install configuration editor. Thin wrapper around the same +/// `TemplateConfigSheet` the install flow uses — owns a +/// `TemplateConfigEditorViewModel` that loads the cached manifest + +/// current values from `/.scarf/`, feeds them to the form, +/// and writes the edited values back to `config.json` on commit. +/// +/// Entry points: right-click on the project list (when the project has +/// a cached manifest) and a button on the dashboard header (shown +/// only when `isConfigurable` is true). +struct ConfigEditorSheet: View { + @Environment(\.dismiss) private var dismiss + @State private var viewModel: TemplateConfigEditorViewModel + + init(context: ServerContext, project: ProjectEntry) { + _viewModel = State( + initialValue: TemplateConfigEditorViewModel( + context: context, + project: project + ) + ) + } + + var body: some View { + Group { + switch viewModel.stage { + case .idle, .loading: + VStack(spacing: 12) { + ProgressView() + Text("Loading configuration…") + .font(.subheadline) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .frame(minWidth: 560, minHeight: 320) + .padding() + case .editing: + if let form = viewModel.formViewModel, + let manifest = viewModel.manifest { + TemplateConfigSheet( + viewModel: form, + title: "Configure \(manifest.name)", + commitLabel: "Save", + project: nil, // edit mode; VM carries the project + onCommit: { values in + viewModel.save(values: values) + }, + onCancel: { + viewModel.cancel() + dismiss() + } + ) + } else { + unexpectedState + } + case .saving: + VStack(spacing: 12) { + ProgressView() + Text("Saving…") + .font(.subheadline) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .frame(minWidth: 560, minHeight: 320) + .padding() + case .succeeded: + VStack(spacing: 16) { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 48)) + .foregroundStyle(.green) + Text("Configuration saved").font(.title2.bold()) + Button("Done") { dismiss() } + .keyboardShortcut(.defaultAction) + .buttonStyle(.borderedProminent) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .frame(minWidth: 560, minHeight: 280) + .padding() + case .failed(let message): + VStack(spacing: 16) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 48)) + .foregroundStyle(.orange) + Text("Couldn't save").font(.title2.bold()) + Text(message) + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + Button("Close") { dismiss() } + .keyboardShortcut(.defaultAction) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .frame(minWidth: 560, minHeight: 280) + .padding() + case .notConfigurable: + VStack(spacing: 16) { + Image(systemName: "slider.horizontal.3") + .font(.system(size: 40)) + .foregroundStyle(.secondary) + Text("No configuration") + .font(.title3.bold()) + Text("This project wasn't installed from a schemaful template.") + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + Button("Close") { dismiss() } + .keyboardShortcut(.defaultAction) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .frame(minWidth: 560, minHeight: 280) + .padding() + } + } + .task { viewModel.begin() } + } + + private var unexpectedState: some View { + VStack(spacing: 12) { + Image(systemName: "questionmark.circle") + .font(.system(size: 40)) + .foregroundStyle(.secondary) + Text("Internal state inconsistency — please close and re-open.") + .font(.caption) + .foregroundStyle(.secondary) + Button("Close") { dismiss() } + .keyboardShortcut(.defaultAction) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .frame(minWidth: 560, minHeight: 280) + .padding() + } +} diff --git a/scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift b/scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift new file mode 100644 index 0000000..81700ef --- /dev/null +++ b/scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift @@ -0,0 +1,398 @@ +import SwiftUI + +/// The configure form rendered for template install + post-install +/// editing. One row per schema field; controls dispatch by field type. +/// Commit button returns the finalized values via `onCommit` — in +/// install mode the caller stashes them in the install plan; in edit +/// mode the caller writes them straight to `/.scarf/config.json`. +struct TemplateConfigSheet: View { + @Environment(\.dismiss) private var dismiss + + @State var viewModel: TemplateConfigViewModel + let title: LocalizedStringKey + let commitLabel: LocalizedStringKey + /// In install mode the caller passes the planned `ProjectEntry` + /// (project dir path is the unique key for the Keychain secret). + /// In edit mode the VM already holds the project; pass `nil` here. + let project: ProjectEntry? + let onCommit: ([String: TemplateConfigValue]) -> Void + let onCancel: () -> Void + + var body: some View { + VStack(spacing: 0) { + header + Divider() + ScrollView { + VStack(alignment: .leading, spacing: 18) { + if viewModel.schema.fields.isEmpty { + ContentUnavailableView( + "No fields", + systemImage: "slider.horizontal.3", + description: Text("This template has no configuration fields.") + ) + .frame(maxWidth: .infinity, minHeight: 120) + } else { + ForEach(viewModel.schema.fields) { field in + fieldRow(field) + } + } + if let rec = viewModel.schema.modelRecommendation { + modelRecommendation(rec) + } + } + .padding(20) + } + Divider() + footer + } + .frame(minWidth: 560, minHeight: 480) + } + + // MARK: - Header / footer + + @ViewBuilder + private var header: some View { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text(title).font(.title2.bold()) + Text(viewModel.templateId) + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + } + Spacer() + } + .padding(16) + } + + @ViewBuilder + private var footer: some View { + HStack { + Button("Cancel") { + // Caller owns dismissal — this view is used both as a + // standalone sheet (ConfigEditorSheet, where the caller + // wants dismissal) AND inlined inside the install sheet + // (TemplateInstallSheet.configureView, where calling + // .dismiss here would tear down the OUTER install sheet + // and abort the flow before .planned is reached). + onCancel() + } + .keyboardShortcut(.cancelAction) + Spacer() + Button(commitLabel) { + if let finalized = viewModel.commit(project: project) { + onCommit(finalized) + } + // Same dismissal-is-caller's-responsibility rule as + // Cancel — inside the install sheet, onCommit transitions + // stage to .planned and the outer view re-renders to + // show the preview. In the edit sheet, onCommit + // transitions the editor VM and its state machine + // handles dismissal via the success view's Done button. + } + .keyboardShortcut(.defaultAction) + .buttonStyle(.borderedProminent) + } + .padding(16) + } + + // MARK: - Field rows + + @ViewBuilder + private func fieldRow(_ field: TemplateConfigField) -> some View { + VStack(alignment: .leading, spacing: 6) { + HStack(alignment: .firstTextBaseline, spacing: 4) { + Text(field.label).font(.headline) + if field.required { + Text("*") + .font(.headline) + .foregroundStyle(.red) + } + Spacer() + Text(field.type.rawValue) + .font(.caption2.monospaced()) + .foregroundStyle(.secondary) + } + if let description = field.description, !description.isEmpty { + // Inline markdown so descriptions can include + // `[Create one](https://…)`-style links to token + // generation pages, **bold** emphasis on important + // prerequisites, etc. + TemplateMarkdown.inlineText(description) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + control(for: field) + if let err = viewModel.errors[field.key] { + Label(err, systemImage: "exclamationmark.triangle.fill") + .font(.caption) + .foregroundStyle(.red) + } + } + .padding(12) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(.background.secondary) + ) + } + + @ViewBuilder + private func control(for field: TemplateConfigField) -> some View { + switch field.type { + case .string: + StringControl( + value: stringBinding(for: field), + placeholder: field.placeholder + ) + case .text: + TextControl(value: stringBinding(for: field)) + case .number: + NumberControl(value: numberBinding(for: field)) + case .bool: + BoolControl(label: field.label, value: boolBinding(for: field)) + case .enum: + EnumControl( + options: field.options ?? [], + value: stringBinding(for: field) + ) + case .list: + ListControl(items: listBinding(for: field)) + case .secret: + SecretControl( + fieldKey: field.key, + placeholder: field.placeholder, + viewModel: viewModel + ) + } + } + + // MARK: - Model recommendation panel + + private func modelRecommendation(_ rec: TemplateModelRecommendation) -> some View { + VStack(alignment: .leading, spacing: 6) { + Label("Recommended model", systemImage: "lightbulb") + .font(.caption.bold()) + .foregroundStyle(.secondary) + Text(rec.preferred).font(.body.monospaced()) + if let rationale = rec.rationale, !rationale.isEmpty { + Text(rationale) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + if let alts = rec.alternatives, !alts.isEmpty { + Text("Also works: \(alts.joined(separator: ", "))") + .font(.caption2) + .foregroundStyle(.secondary) + } + Text("Scarf doesn't auto-switch your active model. Change it in Settings if you'd like.") + .font(.caption2) + .foregroundStyle(.tertiary) + } + .padding(12) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color.accentColor.opacity(0.08)) + ) + } + + // MARK: - Binding helpers (threading the VM through typed lenses) + + private func stringBinding(for field: TemplateConfigField) -> Binding { + Binding( + get: { + if case .string(let s) = viewModel.values[field.key] { return s } + return "" + }, + set: { viewModel.setString(field.key, $0) } + ) + } + + private func numberBinding(for field: TemplateConfigField) -> Binding { + Binding( + get: { + if case .number(let n) = viewModel.values[field.key] { return n } + return 0 + }, + set: { viewModel.setNumber(field.key, $0) } + ) + } + + private func boolBinding(for field: TemplateConfigField) -> Binding { + Binding( + get: { + if case .bool(let b) = viewModel.values[field.key] { return b } + return false + }, + set: { viewModel.setBool(field.key, $0) } + ) + } + + private func listBinding(for field: TemplateConfigField) -> Binding<[String]> { + Binding( + get: { + if case .list(let items) = viewModel.values[field.key] { return items } + return [] + }, + set: { viewModel.setList(field.key, $0) } + ) + } +} + +// MARK: - Field controls + +private struct StringControl: View { + @Binding var value: String + let placeholder: String? + var body: some View { + TextField(placeholder ?? "", text: $value) + .textFieldStyle(.roundedBorder) + } +} + +private struct TextControl: View { + @Binding var value: String + var body: some View { + TextEditor(text: $value) + .font(.body.monospaced()) + .frame(minHeight: 80, maxHeight: 160) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(.secondary.opacity(0.3)) + ) + } +} + +private struct NumberControl: View { + @Binding var value: Double + var body: some View { + TextField("", value: $value, format: .number) + .textFieldStyle(.roundedBorder) + } +} + +private struct BoolControl: View { + let label: String + @Binding var value: Bool + var body: some View { + Toggle(isOn: $value) { + Text(value ? "Enabled" : "Disabled") + .font(.caption) + .foregroundStyle(.secondary) + } + } +} + +private struct EnumControl: View { + let options: [TemplateConfigField.EnumOption] + @Binding var value: String + var body: some View { + // Segmented for ≤ 4 options, dropdown otherwise — fits Scarf's + // existing settings UI. + if options.count <= 4 { + Picker("", selection: $value) { + ForEach(options) { opt in + Text(opt.label).tag(opt.value) + } + } + .pickerStyle(.segmented) + .labelsHidden() + } else { + Picker("", selection: $value) { + ForEach(options) { opt in + Text(opt.label).tag(opt.value) + } + } + .labelsHidden() + } + } +} + +/// Variable-length list of string values. Each row is a text field +/// with an inline remove button; a + button adds a trailing row. +private struct ListControl: View { + @Binding var items: [String] + var body: some View { + VStack(alignment: .leading, spacing: 4) { + ForEach(items.indices, id: \.self) { i in + HStack(spacing: 6) { + TextField("", text: Binding( + get: { i < items.count ? items[i] : "" }, + set: { newValue in + guard i < items.count else { return } + items[i] = newValue + } + )) + .textFieldStyle(.roundedBorder) + Button { + guard i < items.count else { return } + items.remove(at: i) + } label: { + Image(systemName: "minus.circle") + } + .buttonStyle(.borderless) + .disabled(items.count <= 1) + } + } + Button { + items.append("") + } label: { + Label("Add", systemImage: "plus.circle") + .font(.caption) + } + .buttonStyle(.borderless) + } + } +} + +/// Secret fields never echo the previously-stored value back. Instead +/// we render "(unchanged)" when a Keychain ref already exists and let +/// the user type over it if they want to replace. Empty input in edit +/// mode signals "remove this secret entirely." +private struct SecretControl: View { + let fieldKey: String + let placeholder: String? + @Bindable var viewModel: TemplateConfigViewModel + + @State private var typedValue: String = "" + @State private var isRevealed: Bool = false + + private var hasStoredRef: Bool { + if case .keychainRef = viewModel.values[fieldKey] { return true } + return false + } + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 6) { + Group { + if isRevealed { + TextField(placeholder ?? "", text: $typedValue) + } else { + SecureField(placeholder ?? "", text: $typedValue) + } + } + .textFieldStyle(.roundedBorder) + .onChange(of: typedValue) { _, new in + viewModel.setSecret(fieldKey, new) + } + Button { + isRevealed.toggle() + } label: { + Image(systemName: isRevealed ? "eye.slash" : "eye") + } + .buttonStyle(.borderless) + .help(isRevealed ? "Hide" : "Show while typing") + } + if hasStoredRef && typedValue.isEmpty { + Text("Saved in Keychain — leave empty to keep the stored value.") + .font(.caption2) + .foregroundStyle(.secondary) + } else if !typedValue.isEmpty { + Text("Will be saved to the Keychain on commit.") + .font(.caption2) + .foregroundStyle(.secondary) + } + } + } +} diff --git a/scarf/scarf/Features/Templates/Views/TemplateExportSheet.swift b/scarf/scarf/Features/Templates/Views/TemplateExportSheet.swift new file mode 100644 index 0000000..b1630af --- /dev/null +++ b/scarf/scarf/Features/Templates/Views/TemplateExportSheet.swift @@ -0,0 +1,259 @@ +import SwiftUI +import AppKit +import UniformTypeIdentifiers + +/// Author-facing sheet for exporting an existing project as a +/// `.scarftemplate`. Mirrors the profile-export flow: fill in a few fields, +/// pick which skills/cron jobs to include, save via NSSavePanel. +struct TemplateExportSheet: View { + @Environment(\.dismiss) private var dismiss + @State var viewModel: TemplateExporterViewModel + + var body: some View { + VStack(spacing: 0) { + switch viewModel.stage { + case .idle: + form + case .exporting: + VStack(spacing: 12) { + ProgressView() + Text("Building template…") + .font(.subheadline) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + case .succeeded(let path): + VStack(spacing: 16) { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 48)) + .foregroundStyle(.green) + Text("Exported").font(.title2.bold()) + Text(path) + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + HStack { + Button("Show in Finder") { + NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: path)]) + } + Button("Done") { dismiss() } + .keyboardShortcut(.defaultAction) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + case .failed(let message): + VStack(spacing: 16) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 48)) + .foregroundStyle(.orange) + Text("Export Failed").font(.title2.bold()) + Text(message) + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + Button("Close") { dismiss() } + .keyboardShortcut(.defaultAction) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding() + } + } + .frame(minWidth: 620, minHeight: 560) + .padding() + .task { viewModel.load() } + } + + @ViewBuilder + private var form: some View { + ScrollView { + VStack(alignment: .leading, spacing: 16) { + Text("Export \"\(viewModel.project.name)\" as Template") + .font(.title2.bold()) + metadataGroup + Divider() + requiredFilesGroup + Divider() + instructionsGroup + Divider() + skillsGroup + Divider() + cronGroup + Divider() + memoryGroup + } + .padding(.bottom) + } + HStack { + Button("Cancel") { dismiss() } + .keyboardShortcut(.cancelAction) + Spacer() + Button("Export…") { runExport() } + .keyboardShortcut(.defaultAction) + .buttonStyle(.borderedProminent) + .disabled(!canExport) + } + .padding(.top, 8) + } + + private var metadataGroup: some View { + VStack(alignment: .leading, spacing: 8) { + Text("Metadata").font(.headline) + LabeledContent("Template ID") { + TextField("owner/name", text: $viewModel.templateId) + .textFieldStyle(.roundedBorder) + } + LabeledContent("Display Name") { + TextField("", text: $viewModel.templateName) + .textFieldStyle(.roundedBorder) + } + LabeledContent("Version") { + TextField("1.0.0", text: $viewModel.templateVersion) + .textFieldStyle(.roundedBorder) + } + LabeledContent("Description") { + TextField("One-line pitch", text: $viewModel.templateDescription, axis: .vertical) + .lineLimit(2...4) + .textFieldStyle(.roundedBorder) + } + LabeledContent("Author") { + TextField("Your name", text: $viewModel.authorName) + .textFieldStyle(.roundedBorder) + } + LabeledContent("Author URL") { + TextField("https://…", text: $viewModel.authorURL) + .textFieldStyle(.roundedBorder) + } + LabeledContent("Category") { + TextField("e.g. productivity", text: $viewModel.category) + .textFieldStyle(.roundedBorder) + } + LabeledContent("Tags (comma-separated)") { + TextField("focus, timer", text: $viewModel.tags) + .textFieldStyle(.roundedBorder) + } + } + } + + private var requiredFilesGroup: some View { + let plan = viewModel.previewPlan() + return VStack(alignment: .leading, spacing: 6) { + Text("Required Files").font(.headline) + check(label: "dashboard.json (\(plan.projectDir)/.scarf/dashboard.json)", ok: plan.dashboardPresent) + check(label: "README.md (\(plan.projectDir)/README.md)", ok: plan.readmePresent) + check(label: "AGENTS.md (\(plan.projectDir)/AGENTS.md)", ok: plan.agentsMdPresent) + } + } + + private var instructionsGroup: some View { + let plan = viewModel.previewPlan() + return VStack(alignment: .leading, spacing: 4) { + Text("Agent-specific instructions (optional)").font(.headline) + if plan.instructionFiles.isEmpty { + Text("No per-agent instruction files found in the project root.") + .font(.caption) + .foregroundStyle(.secondary) + } else { + ForEach(plan.instructionFiles, id: \.self) { file in + Label(file, systemImage: "doc.plaintext") + .font(.callout) + } + } + } + } + + private var skillsGroup: some View { + VStack(alignment: .leading, spacing: 6) { + Text("Include Skills").font(.headline) + if viewModel.availableSkills.isEmpty { + Text("No skills found.") + .font(.caption) + .foregroundStyle(.secondary) + } else { + ForEach(viewModel.availableSkills) { skill in + Toggle(isOn: Binding( + get: { viewModel.includeSkillIds.contains(skill.id) }, + set: { on in + if on { viewModel.includeSkillIds.insert(skill.id) } + else { viewModel.includeSkillIds.remove(skill.id) } + } + )) { + Text(skill.id).font(.callout.monospaced()) + } + } + } + } + } + + private var cronGroup: some View { + VStack(alignment: .leading, spacing: 6) { + Text("Include Cron Jobs").font(.headline) + if viewModel.availableCronJobs.isEmpty { + Text("No cron jobs found.") + .font(.caption) + .foregroundStyle(.secondary) + } else { + ForEach(viewModel.availableCronJobs) { job in + Toggle(isOn: Binding( + get: { viewModel.includeCronJobIds.contains(job.id) }, + set: { on in + if on { viewModel.includeCronJobIds.insert(job.id) } + else { viewModel.includeCronJobIds.remove(job.id) } + } + )) { + VStack(alignment: .leading, spacing: 0) { + Text(job.name).font(.callout) + Text(job.schedule.display ?? job.schedule.expression ?? job.schedule.kind) + .font(.caption) + .foregroundStyle(.secondary) + } + } + } + } + } + } + + private var memoryGroup: some View { + VStack(alignment: .leading, spacing: 6) { + Text("Memory Appendix (optional)").font(.headline) + Text("Markdown that will be appended to the installer's MEMORY.md, wrapped in template-specific markers so it can be removed cleanly later.") + .font(.caption) + .foregroundStyle(.secondary) + TextEditor(text: $viewModel.memoryAppendix) + .font(.callout.monospaced()) + .frame(minHeight: 80, maxHeight: 160) + .overlay( + RoundedRectangle(cornerRadius: 4) + .stroke(.secondary.opacity(0.4)) + ) + } + } + + private func check(label: String, ok: Bool) -> some View { + HStack(spacing: 6) { + Image(systemName: ok ? "checkmark.circle.fill" : "xmark.circle.fill") + .foregroundStyle(ok ? .green : .red) + Text(label) + .font(.caption) + .foregroundStyle(ok ? .primary : .secondary) + } + } + + private var canExport: Bool { + let plan = viewModel.previewPlan() + return plan.dashboardPresent + && plan.readmePresent + && plan.agentsMdPresent + && !viewModel.templateId.trimmingCharacters(in: .whitespaces).isEmpty + && !viewModel.templateName.trimmingCharacters(in: .whitespaces).isEmpty + && !viewModel.templateVersion.trimmingCharacters(in: .whitespaces).isEmpty + && !viewModel.templateDescription.trimmingCharacters(in: .whitespaces).isEmpty + } + + private func runExport() { + let panel = NSSavePanel() + panel.allowedContentTypes = [.zip] + panel.nameFieldStringValue = ProjectTemplateExporter.slugify(viewModel.templateName) + ".scarftemplate" + if panel.runModal() == .OK, let url = panel.url { + viewModel.export(to: url.path) + } + } +} diff --git a/scarf/scarf/Features/Templates/Views/TemplateInstallSheet.swift b/scarf/scarf/Features/Templates/Views/TemplateInstallSheet.swift new file mode 100644 index 0000000..e697050 --- /dev/null +++ b/scarf/scarf/Features/Templates/Views/TemplateInstallSheet.swift @@ -0,0 +1,420 @@ +import SwiftUI +import AppKit + +/// Preview-and-confirm sheet for installing a `.scarftemplate`. Honest +/// accounting: shows every file that will be written, every cron job that +/// will be registered, and the memory diff — nothing gets written until the +/// user clicks Install. +struct TemplateInstallSheet: View { + @Environment(\.dismiss) private var dismiss + @State var viewModel: TemplateInstallerViewModel + let onCompleted: (ProjectEntry) -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + switch viewModel.stage { + case .idle: + idleView + case .fetching(let src): + progress("Downloading from \(src)…") + case .inspecting: + progress("Inspecting template…") + case .awaitingParentDirectory: + pickParentView + case .awaitingConfig: + configureView + case .planned: + if let plan = viewModel.plan { + plannedView(plan: plan) + } else { + progress("Preparing…") + } + case .installing: + progress("Installing…") + case .succeeded(let entry): + successView(entry: entry) + case .failed(let message): + failureView(message: message) + } + } + .frame(minWidth: 640, minHeight: 520) + .padding() + } + + // MARK: - Stages + + private var idleView: some View { + VStack(spacing: 16) { + Text("No template loaded.") + .font(.headline) + Button("Close") { dismiss() } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + private func progress(_ label: LocalizedStringKey) -> some View { + VStack(spacing: 16) { + ProgressView() + Text(label) + .font(.subheadline) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + private var pickParentView: some View { + VStack(alignment: .leading, spacing: 12) { + if let manifest = viewModel.inspection?.manifest { + manifestHeader(manifest) + Divider() + } + Text("Where should this project live?") + .font(.headline) + Text("Scarf will create a new folder inside the directory you pick, named after the template id.") + .font(.subheadline) + .foregroundStyle(.secondary) + Spacer() + HStack { + Button("Cancel") { + viewModel.cancel() + dismiss() + } + .keyboardShortcut(.cancelAction) + Spacer() + Button("Choose Folder…") { chooseParentDirectory() } + .keyboardShortcut(.defaultAction) + } + } + } + + /// Configure step for schemaful templates. Inlines + /// `TemplateConfigSheet` into the install flow rather than pushing + /// a second sheet on top — keeps the user in one window. The + /// nested VM is created freshly each time `.awaitingConfig` is + /// entered so a Cancel + retry doesn't carry stale form state. + @ViewBuilder + private var configureView: some View { + if let plan = viewModel.plan, + let schema = plan.configSchema, + let manifest = viewModel.inspection?.manifest { + TemplateConfigSheet( + viewModel: TemplateConfigViewModel( + schema: schema, + templateId: manifest.id, + templateSlug: manifest.slug, + initialValues: plan.configValues, + mode: .install + ), + title: "Configure \(manifest.name)", + commitLabel: "Continue", + project: ProjectEntry(name: plan.projectRegistryName, path: plan.projectDir), + onCommit: { values in + viewModel.submitConfig(values: values) + }, + onCancel: { + viewModel.cancelConfig() + } + ) + } else { + progress("Preparing…") + } + } + + private func plannedView(plan: TemplateInstallPlan) -> some View { + VStack(alignment: .leading, spacing: 0) { + manifestHeader(plan.manifest) + .padding(.bottom, 8) + Divider() + ScrollView { + VStack(alignment: .leading, spacing: 16) { + projectFilesSection(plan: plan) + if plan.skillsNamespaceDir != nil { + skillsSection(plan: plan) + } + if !plan.cronJobs.isEmpty { + cronSection(plan: plan) + } + if plan.memoryAppendix != nil { + memorySection(plan: plan) + } + if let schema = plan.configSchema, !schema.isEmpty { + configurationSection(plan: plan, schema: schema) + } + readmeSection + } + .padding(.vertical) + } + Divider() + HStack { + Button("Cancel") { + viewModel.cancel() + dismiss() + } + .keyboardShortcut(.cancelAction) + Spacer() + Text("\(plan.totalWriteCount) changes") + .font(.caption) + .foregroundStyle(.secondary) + Button("Install") { viewModel.confirmInstall() } + .keyboardShortcut(.defaultAction) + .buttonStyle(.borderedProminent) + } + .padding(.top, 8) + } + } + + private func manifestHeader(_ manifest: ProjectTemplateManifest) -> some View { + VStack(alignment: .leading, spacing: 4) { + HStack(alignment: .firstTextBaseline) { + Text(manifest.name).font(.title2.bold()) + Text("v\(manifest.version)") + .font(.caption) + .foregroundStyle(.secondary) + Spacer() + Text(manifest.id) + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + } + // Inline-only markdown — descriptions are a sentence or two; + // bold/italic/code/links are all that reasonable template + // authors use there. + TemplateMarkdown.inlineText(manifest.description) + .font(.subheadline) + .foregroundStyle(.secondary) + if let author = manifest.author { + HStack(spacing: 4) { + Image(systemName: "person.crop.circle") + .font(.caption) + .foregroundStyle(.secondary) + Text(author.name) + .font(.caption) + .foregroundStyle(.secondary) + if let url = author.url, let parsed = URL(string: url) { + Link(parsed.host ?? url, destination: parsed) + .font(.caption) + } + } + } + } + } + + private func projectFilesSection(plan: TemplateInstallPlan) -> some View { + section(title: "New project directory", subtitle: plan.projectDir) { + VStack(alignment: .leading, spacing: 2) { + ForEach(plan.projectFiles, id: \.destinationPath) { copy in + fileRow(label: copy.destinationPath, systemImage: "doc.text") + } + } + } + } + + private func skillsSection(plan: TemplateInstallPlan) -> some View { + section( + title: "Skills (namespaced, safe to remove later)", + subtitle: plan.skillsNamespaceDir + ) { + VStack(alignment: .leading, spacing: 2) { + ForEach(plan.skillsFiles, id: \.destinationPath) { copy in + fileRow(label: copy.destinationPath, systemImage: "puzzlepiece") + } + } + } + } + + private func cronSection(plan: TemplateInstallPlan) -> some View { + section(title: "Cron jobs (created disabled — you can enable each one manually)", subtitle: nil) { + VStack(alignment: .leading, spacing: 10) { + ForEach(plan.cronJobs, id: \.name) { job in + VStack(alignment: .leading, spacing: 4) { + HStack(alignment: .firstTextBaseline, spacing: 8) { + Image(systemName: "clock.arrow.circlepath") + .foregroundStyle(.secondary) + VStack(alignment: .leading, spacing: 1) { + Text(job.name).font(.callout.monospaced()) + Text("schedule: \(job.schedule)") + .font(.caption) + .foregroundStyle(.secondary) + } + } + // Prompt preview — disclosed in an expandable + // group so the preview stays compact when the + // user doesn't care to read it. Markdown-rendered + // so prompts that include `code`, **bold**, or + // enumerated steps look right. Tokens like + // {{PROJECT_DIR}} are still visible here — they + // get substituted when the installer calls + // `hermes cron create`. + if let prompt = job.prompt, !prompt.isEmpty { + DisclosureGroup("Prompt") { + ScrollView { + TemplateMarkdown.render(prompt) + .frame(maxWidth: .infinity, alignment: .leading) + } + .frame(maxHeight: 140) + .padding(8) + .background(.quaternary.opacity(0.4)) + .clipShape(RoundedRectangle(cornerRadius: 6)) + } + .font(.caption) + .padding(.leading, 26) + } + } + } + } + } + } + + private func memorySection(plan: TemplateInstallPlan) -> some View { + section(title: "Memory appendix", subtitle: plan.memoryPath) { + ScrollView { + Text(plan.memoryAppendix ?? "") + .font(.caption.monospaced()) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(8) + .background(.quaternary.opacity(0.5)) + .clipShape(RoundedRectangle(cornerRadius: 6)) + } + .frame(maxHeight: 160) + } + } + + /// Configuration values the user entered in the configure step. + /// Secrets display masked so the preview never echoes a freshly + /// typed API key back on screen. + private func configurationSection(plan: TemplateInstallPlan, schema: TemplateConfigSchema) -> some View { + section(title: "Configuration", subtitle: "written to \(plan.projectDir)/.scarf/config.json") { + VStack(alignment: .leading, spacing: 4) { + ForEach(schema.fields) { field in + HStack(alignment: .firstTextBaseline, spacing: 8) { + Text(field.key) + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + .frame(minWidth: 120, alignment: .leading) + Text(displayValue(for: field, in: plan.configValues)) + .font(.caption) + .lineLimit(1) + .truncationMode(.tail) + } + } + } + } + } + + /// One-line display form for a value in the preview. Secrets are + /// always masked; lists show a count + first entry; strings are + /// truncated by `.lineLimit(1)` at the view level. + private func displayValue( + for field: TemplateConfigField, + in values: [String: TemplateConfigValue] + ) -> String { + switch field.type { + case .secret: + return values[field.key] == nil ? "(not set)" : "••••••• (Keychain)" + case .list: + if case .list(let items) = values[field.key] { + if items.isEmpty { return "(none)" } + if items.count == 1 { return items[0] } + return "\(items[0]) + \(items.count - 1) more" + } + return "(none)" + default: + return values[field.key]?.displayString ?? "(not set)" + } + } + + private var readmeSection: some View { + Group { + // The body is preloaded in the VM off MainActor when inspection + // completes — no sync file I/O during View body evaluation. + if let readme = viewModel.readmeBody { + section(title: "README", subtitle: nil) { + ScrollView { + TemplateMarkdown.render(readme) + .frame(maxWidth: .infinity, alignment: .leading) + } + .frame(maxHeight: 260) + } + } + } + } + + @ViewBuilder + private func section(title: String, subtitle: String?, @ViewBuilder content: () -> Content) -> some View { + VStack(alignment: .leading, spacing: 4) { + Text(title).font(.headline) + if let subtitle { + Text(subtitle) + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + } + content() + .padding(.top, 2) + } + } + + private func fileRow(label: String, systemImage: String) -> some View { + HStack(spacing: 6) { + Image(systemName: systemImage) + .foregroundStyle(.secondary) + .font(.caption) + Text(label) + .font(.caption.monospaced()) + .lineLimit(1) + .truncationMode(.head) + } + } + + private func successView(entry: ProjectEntry) -> some View { + VStack(spacing: 16) { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 48)) + .foregroundStyle(.green) + Text("Installed \(entry.name)") + .font(.title2.bold()) + Text(entry.path) + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + Button("Open Project") { + onCompleted(entry) + dismiss() + } + .keyboardShortcut(.defaultAction) + .buttonStyle(.borderedProminent) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + private func failureView(message: String) -> some View { + VStack(spacing: 16) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 48)) + .foregroundStyle(.orange) + Text("Install Failed").font(.title2.bold()) + Text(message) + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + Button("Close") { + viewModel.cancel() + dismiss() + } + .keyboardShortcut(.defaultAction) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding() + } + + // MARK: - Actions + + private func chooseParentDirectory() { + let panel = NSOpenPanel() + panel.canChooseDirectories = true + panel.canChooseFiles = false + panel.allowsMultipleSelection = false + panel.prompt = String(localized: "Choose Parent Folder") + if panel.runModal() == .OK, let url = panel.url { + viewModel.pickParentDirectory(url.path) + } + } + +} diff --git a/scarf/scarf/Features/Templates/Views/TemplateMarkdown.swift b/scarf/scarf/Features/Templates/Views/TemplateMarkdown.swift new file mode 100644 index 0000000..5c9c390 --- /dev/null +++ b/scarf/scarf/Features/Templates/Views/TemplateMarkdown.swift @@ -0,0 +1,192 @@ +import SwiftUI +import Foundation + +/// Minimal markdown renderer used by the template install/config UIs. +/// +/// SwiftUI `Text` has built-in inline-markdown support via +/// `AttributedString(markdown:)` — bold, italic, inline code, links. +/// That's enough for field descriptions + template taglines. For +/// longer content (README preview, full doc blocks), this helper adds +/// block-level handling: lines starting with `#`/`##`/`###` render +/// as bigger bold text; lines starting with `-`/`*`/`1.` render as +/// list items with a hanging indent; fenced ``` ``` blocks render as +/// monospaced; blank lines become paragraph breaks. +/// +/// Scope is intentionally small. This isn't a full CommonMark +/// renderer — it's "enough markdown to make template READMEs look +/// right in the install sheet without pulling in a dependency." If +/// the set of templates needs more over time, evolve this file or +/// graduate to a proper library. +enum TemplateMarkdown { + + /// Render a markdown source string as a SwiftUI view. Preserves + /// reading order and approximate visual hierarchy. Safe with + /// untrusted input — we never execute HTML or scripts. + @ViewBuilder + static func render(_ source: String) -> some View { + VStack(alignment: .leading, spacing: 6) { + let blocks = parse(source) + ForEach(blocks.indices, id: \.self) { i in + block(blocks[i]) + } + } + } + + /// Inline-only markdown (bold/italic/code/links) as a single + /// `Text`. Use for short strings where block structure doesn't + /// apply — field labels, one-line descriptions. + static func inlineText(_ source: String) -> Text { + if let attr = try? AttributedString( + markdown: source, + options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace) + ) { + return Text(attr) + } + return Text(source) + } + + // MARK: - Block model + + fileprivate enum Block { + case paragraph(AttributedString) + case heading(level: Int, text: AttributedString) + case bullet(AttributedString) + case numbered(index: Int, text: AttributedString) + case code(String) + } + + // MARK: - Parser + + fileprivate static func parse(_ source: String) -> [Block] { + var blocks: [Block] = [] + var lines = source.components(separatedBy: "\n") + var i = 0 + while i < lines.count { + let line = lines[i] + let trimmed = line.trimmingCharacters(in: .whitespaces) + + // Fenced code block. + if trimmed.hasPrefix("```") { + var body: [String] = [] + i += 1 + while i < lines.count { + let inner = lines[i] + if inner.trimmingCharacters(in: .whitespaces).hasPrefix("```") { + i += 1 + break + } + body.append(inner) + i += 1 + } + blocks.append(.code(body.joined(separator: "\n"))) + continue + } + + // Heading. + if let headingMatch = trimmed.firstMatch(of: /^(#{1,6})\s+(.*)$/) { + let level = (headingMatch.1).count + let text = String(headingMatch.2) + blocks.append(.heading(level: level, text: renderInline(text))) + i += 1 + continue + } + + // Bullet list. + if let bulletMatch = line.firstMatch(of: /^\s*[-*]\s+(.*)$/) { + let text = String(bulletMatch.1) + blocks.append(.bullet(renderInline(text))) + i += 1 + continue + } + + // Numbered list. + if let numMatch = line.firstMatch(of: /^\s*(\d+)\.\s+(.*)$/) { + let index = Int(String(numMatch.1)) ?? 1 + let text = String(numMatch.2) + blocks.append(.numbered(index: index, text: renderInline(text))) + i += 1 + continue + } + + // Blank line — skip. + if trimmed.isEmpty { + i += 1 + continue + } + + // Paragraph — collect contiguous non-blank lines that + // aren't headings/lists/fences into one paragraph block. + var paragraphLines: [String] = [line] + i += 1 + while i < lines.count { + let next = lines[i] + let nextTrim = next.trimmingCharacters(in: .whitespaces) + if nextTrim.isEmpty { break } + if nextTrim.hasPrefix("```") { break } + if nextTrim.firstMatch(of: /^#{1,6}\s/) != nil { break } + if next.firstMatch(of: /^\s*[-*]\s+/) != nil { break } + if next.firstMatch(of: /^\s*\d+\.\s+/) != nil { break } + paragraphLines.append(next) + i += 1 + } + let joined = paragraphLines.joined(separator: " ") + blocks.append(.paragraph(renderInline(joined))) + } + return blocks + } + + /// Parse inline markdown (bold, italic, inline code, links) into + /// an AttributedString. Falls back to plain text on parse failure. + fileprivate static func renderInline(_ source: String) -> AttributedString { + if let attr = try? AttributedString( + markdown: source, + options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace) + ) { + return attr + } + return AttributedString(source) + } + + // MARK: - Rendering + + @ViewBuilder + fileprivate static func block(_ b: Block) -> some View { + switch b { + case .paragraph(let text): + Text(text) + .font(.callout) + .fixedSize(horizontal: false, vertical: true) + case .heading(let level, let text): + headingText(text: text, level: level) + case .bullet(let text): + HStack(alignment: .firstTextBaseline, spacing: 6) { + Text("•").font(.callout) + Text(text).font(.callout) + .fixedSize(horizontal: false, vertical: true) + } + case .numbered(let index, let text): + HStack(alignment: .firstTextBaseline, spacing: 6) { + Text("\(index).").font(.callout.monospacedDigit()) + Text(text).font(.callout) + .fixedSize(horizontal: false, vertical: true) + } + case .code(let src): + Text(src) + .font(.caption.monospaced()) + .padding(8) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.quaternary.opacity(0.5)) + .clipShape(RoundedRectangle(cornerRadius: 6)) + } + } + + @ViewBuilder + fileprivate static func headingText(text: AttributedString, level: Int) -> some View { + switch level { + case 1: Text(text).font(.title2.bold()).padding(.top, 8) + case 2: Text(text).font(.title3.bold()).padding(.top, 6) + case 3: Text(text).font(.headline).padding(.top, 4) + default: Text(text).font(.subheadline.bold()).padding(.top, 2) + } + } +} diff --git a/scarf/scarf/Features/Templates/Views/TemplateUninstallSheet.swift b/scarf/scarf/Features/Templates/Views/TemplateUninstallSheet.swift new file mode 100644 index 0000000..ceea9d3 --- /dev/null +++ b/scarf/scarf/Features/Templates/Views/TemplateUninstallSheet.swift @@ -0,0 +1,369 @@ +import SwiftUI + +/// Preview-and-confirm sheet for uninstalling a template-installed +/// project. Symmetric with the install sheet: lists every file, cron +/// job, and memory block that will be removed BEFORE anything happens. +struct TemplateUninstallSheet: View { + @Environment(\.dismiss) private var dismiss + @State var viewModel: TemplateUninstallerViewModel + /// Called on success with the project that was removed. Parent uses + /// this to refresh its projects list and clear any selection. + let onCompleted: (ProjectEntry) -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + switch viewModel.stage { + case .idle: + idleView + case .loading: + progress("Reading template.lock.json…") + case .planned: + if let plan = viewModel.plan { + plannedView(plan: plan) + } else { + progress("Preparing…") + } + case .uninstalling: + progress("Removing…") + case .succeeded(let removed): + successView(removed: removed) + case .failed(let message): + failureView(message: message) + } + } + .frame(minWidth: 620, minHeight: 480) + .padding() + } + + // MARK: - Stages + + private var idleView: some View { + VStack(spacing: 16) { + Text("No template loaded.") + .font(.headline) + Button("Close") { dismiss() } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + private func progress(_ label: LocalizedStringKey) -> some View { + VStack(spacing: 16) { + ProgressView() + Text(label) + .font(.subheadline) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + private func plannedView(plan: TemplateUninstallPlan) -> some View { + VStack(alignment: .leading, spacing: 0) { + header(plan: plan) + .padding(.bottom, 8) + Divider() + ScrollView { + VStack(alignment: .leading, spacing: 16) { + projectFilesSection(plan: plan) + if plan.skillsNamespaceDir != nil { + skillsSection(plan: plan) + } + cronSection(plan: plan) + memorySection(plan: plan) + registrySection(plan: plan) + } + .padding(.vertical) + } + Divider() + HStack { + Button("Cancel") { + viewModel.cancel() + dismiss() + } + .keyboardShortcut(.cancelAction) + Spacer() + Text("\(plan.totalRemoveCount) changes") + .font(.caption) + .foregroundStyle(.secondary) + Button("Remove") { viewModel.confirmUninstall() } + .keyboardShortcut(.defaultAction) + .buttonStyle(.borderedProminent) + .tint(.red) + } + .padding(.top, 8) + } + } + + private func header(plan: TemplateUninstallPlan) -> some View { + VStack(alignment: .leading, spacing: 4) { + HStack(alignment: .firstTextBaseline) { + Text("Remove “\(plan.lock.templateName)”").font(.title2.bold()) + Text("v\(plan.lock.templateVersion)") + .font(.caption) + .foregroundStyle(.secondary) + Spacer() + Text(plan.lock.templateId) + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + } + Text("Installed \(plan.lock.installedAt)") + .font(.caption) + .foregroundStyle(.secondary) + } + } + + private func projectFilesSection(plan: TemplateUninstallPlan) -> some View { + section(title: "Project directory", subtitle: plan.project.path) { + VStack(alignment: .leading, spacing: 4) { + ForEach(plan.projectFilesToRemove, id: \.self) { path in + fileRow( + label: path, + systemImage: "minus.circle", + color: .red, + tag: "remove" + ) + } + ForEach(plan.projectFilesAlreadyGone, id: \.self) { path in + fileRow( + label: path, + systemImage: "questionmark.circle", + color: .secondary, + tag: "already gone" + ) + } + ForEach(plan.extraProjectEntries, id: \.self) { path in + fileRow( + label: path, + systemImage: "lock.shield", + color: .green, + tag: "keep (not installed by template)" + ) + } + if plan.projectDirBecomesEmpty { + Text("Project directory will also be removed (nothing user-owned left inside).") + .font(.caption) + .foregroundStyle(.secondary) + .padding(.top, 4) + } else if !plan.extraProjectEntries.isEmpty { + Text("Project directory stays — it still holds files you created after install.") + .font(.caption) + .foregroundStyle(.secondary) + .padding(.top, 4) + } + } + } + } + + private func skillsSection(plan: TemplateUninstallPlan) -> some View { + section( + title: "Skills", + subtitle: plan.skillsNamespaceDir + ) { + HStack(spacing: 6) { + Image(systemName: "minus.circle") + .foregroundStyle(.red) + .font(.caption) + Text("Remove the entire namespace dir recursively") + .font(.caption) + } + } + } + + private func cronSection(plan: TemplateUninstallPlan) -> some View { + section( + title: "Cron jobs", + subtitle: plan.cronJobsToRemove.isEmpty && plan.cronJobsAlreadyGone.isEmpty + ? "none" + : nil + ) { + VStack(alignment: .leading, spacing: 4) { + ForEach(plan.cronJobsToRemove, id: \.id) { job in + HStack(spacing: 6) { + Image(systemName: "minus.circle") + .foregroundStyle(.red) + .font(.caption) + VStack(alignment: .leading, spacing: 1) { + Text(job.name).font(.callout.monospaced()) + Text(job.id) + .font(.caption) + .foregroundStyle(.secondary) + } + } + } + ForEach(plan.cronJobsAlreadyGone, id: \.self) { name in + HStack(spacing: 6) { + Image(systemName: "questionmark.circle") + .foregroundStyle(.secondary) + .font(.caption) + Text("\(name) — already gone") + .font(.caption) + .foregroundStyle(.secondary) + } + } + } + } + } + + @ViewBuilder + private func memorySection(plan: TemplateUninstallPlan) -> some View { + if plan.memoryBlockPresent { + section(title: "Memory block", subtitle: plan.memoryPath) { + HStack(spacing: 6) { + Image(systemName: "minus.circle") + .foregroundStyle(.red) + .font(.caption) + Text("Strip the template's begin/end block, preserve everything else in MEMORY.md") + .font(.caption) + } + } + } else if plan.lock.memoryBlockId != nil { + section(title: "Memory block", subtitle: nil) { + Text("A memory block was recorded in the lock but is no longer present in MEMORY.md — skipping.") + .font(.caption) + .foregroundStyle(.secondary) + } + } + } + + private func registrySection(plan: TemplateUninstallPlan) -> some View { + section(title: "Projects registry", subtitle: nil) { + HStack(spacing: 6) { + Image(systemName: "minus.circle") + .foregroundStyle(.red) + .font(.caption) + Text("Remove \"\(plan.project.name)\" from Scarf's project list") + .font(.caption) + } + } + } + + @ViewBuilder + private func section( + title: LocalizedStringKey, + subtitle: String?, + @ViewBuilder content: () -> Content + ) -> some View { + VStack(alignment: .leading, spacing: 4) { + Text(title).font(.headline) + if let subtitle { + Text(subtitle) + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + } + content() + .padding(.top, 2) + } + } + + private func fileRow(label: String, systemImage: String, color: Color, tag: LocalizedStringKey) -> some View { + HStack(spacing: 6) { + Image(systemName: systemImage) + .foregroundStyle(color) + .font(.caption) + Text(label) + .font(.caption.monospaced()) + .lineLimit(1) + .truncationMode(.head) + Spacer() + Text(tag) + .font(.caption2) + .foregroundStyle(.secondary) + } + } + + private func successView(removed: ProjectEntry) -> some View { + VStack(spacing: 16) { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 48)) + .foregroundStyle(.green) + Text("Removed \(removed.name)") + .font(.title2.bold()) + + // Preserved-files banner. Only renders when the project dir + // stayed and at least one file was left behind — that's the + // case the user keeps getting surprised by ("I uninstalled + // but my project folder is still there?"). Explicit + // explanation + file list makes it obvious the files the + // user (or the cron job) created are intentionally kept. + if let outcome = viewModel.preservedOutcome, + outcome.projectDirRemoved == false, + outcome.preservedPaths.isEmpty == false { + preservedFilesBanner(outcome: outcome) + } + + Button("Done") { + onCompleted(removed) + dismiss() + } + .keyboardShortcut(.defaultAction) + .buttonStyle(.borderedProminent) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding() + } + + /// Orange informational banner listing the files the uninstaller + /// left in the project directory. Caps the visible list at 8 rows + /// with a "+N more…" tail so a long log (many runs = many status + /// file entries) doesn't blow out the sheet height. + private func preservedFilesBanner( + outcome: TemplateUninstallerViewModel.PreservedOutcome + ) -> some View { + let visible = Array(outcome.preservedPaths.prefix(8)) + let overflow = outcome.preservedPaths.count - visible.count + return VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 6) { + Image(systemName: "folder.badge.questionmark") + .foregroundStyle(.orange) + Text("Project folder kept") + .font(.headline) + } + Text("These files weren't installed by the template (the agent or you created them after install), so Scarf left them in place along with the folder itself.") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + VStack(alignment: .leading, spacing: 2) { + ForEach(visible, id: \.self) { path in + Text(path) + .font(.caption.monospaced()) + .lineLimit(1) + .truncationMode(.head) + } + if overflow > 0 { + Text("+ \(overflow) more…") + .font(.caption2) + .foregroundStyle(.secondary) + } + } + Text("Delete \(outcome.projectDir) from Finder if you don't need these files anymore.") + .font(.caption2) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + .frame(maxWidth: 520, alignment: .leading) + .padding(12) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color.orange.opacity(0.10)) + ) + } + + private func failureView(message: String) -> some View { + VStack(spacing: 16) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 48)) + .foregroundStyle(.orange) + Text("Uninstall Failed").font(.title2.bold()) + Text(message) + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + Button("Close") { + viewModel.cancel() + dismiss() + } + .keyboardShortcut(.defaultAction) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding() + } +} diff --git a/scarf/scarf/Info.plist b/scarf/scarf/Info.plist index 397ee1c..40b8c53 100644 --- a/scarf/scarf/Info.plist +++ b/scarf/scarf/Info.plist @@ -38,5 +38,56 @@ 86400 SUEnableInstallerLauncherService + CFBundleURLTypes + + + CFBundleURLName + com.scarf.url + CFBundleURLSchemes + + scarf + + + + UTExportedTypeDeclarations + + + UTTypeIdentifier + com.scarf.template + UTTypeDescription + Scarf Project Template + UTTypeConformsTo + + public.zip-archive + public.data + + UTTypeTagSpecification + + public.filename-extension + + scarftemplate + + public.mime-type + + application/vnd.scarf.template+zip + + + + + CFBundleDocumentTypes + + + CFBundleTypeName + Scarf Project Template + CFBundleTypeRole + Viewer + LSHandlerRank + Owner + LSItemContentTypes + + com.scarf.template + + + diff --git a/scarf/scarf/InfoPlist.xcstrings b/scarf/scarf/InfoPlist.xcstrings index 74402b0..ade1f25 100644 --- a/scarf/scarf/InfoPlist.xcstrings +++ b/scarf/scarf/InfoPlist.xcstrings @@ -1,6 +1,42 @@ { "sourceLanguage" : "en", "strings" : { + "CFBundleDisplayName" : { + "comment" : "Bundle display name", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Scarf" + } + } + } + }, + "CFBundleName" : { + "comment" : "Bundle name", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "scarf" + } + } + } + }, + "NSHumanReadableCopyright" : { + "comment" : "Copyright (human-readable)", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + }, "NSMicrophoneUsageDescription" : { "comment" : "Shown by macOS when Scarf first requests microphone access for Hermes voice chat.", "extractionState" : "manual", @@ -48,7 +84,10 @@ } } } + }, + "Scarf Project Template" : { + } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/scarf/scarf/Localizable.xcstrings b/scarf/scarf/Localizable.xcstrings index d5b17d7..a0a87bb 100644 --- a/scarf/scarf/Localizable.xcstrings +++ b/scarf/scarf/Localizable.xcstrings @@ -1,23441 +1,23957 @@ { - "sourceLanguage": "en", - "strings": { - "": {}, - "#%lld": {}, - "%@ ctx": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%@ Kontext" + "sourceLanguage" : "en", + "strings" : { + "" : { + + }, + "— or use user/password login —" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "— oder Benutzername/Passwort-Anmeldung verwenden —" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%@ contexto" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "— o usa inicio de sesión con usuario/contraseña —" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%@ contexte" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "— ou utilisez la connexion utilisateur/mot de passe —" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ コンテキスト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "— またはユーザー名/パスワードでログイン —" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%@ contexto" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "— ou use login com usuário/senha —" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%@ 上下文" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "— 或使用用户名/密码登录 —" } } } }, - "%@ in / %@ out": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%1$@ ein / %2$@ aus" + "·" : { + + }, + "(%lld tokens)" : { + + }, + "*" : { + "comment" : "A required asterisk.", + "isCommentAutoGenerated" : true + }, + "/%@" : { + + }, + "#%lld" : { + + }, + "%@ — already gone" : { + "comment" : "A row in the \"Cron jobs\" section of the TemplateUninstallSheet, indicating that a cron job is already gone. The argument is the name of the cron job.", + "isCommentAutoGenerated" : true + }, + "%@ · %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ · %2$@" } - }, - "en": { - "stringUnit": { - "state": "new", - "value": "%1$@ in / %2$@ out" + } + } + }, + "%@ → %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ → %2$@" + } + } + } + }, + "%@ ctx" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ Kontext" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%1$@ entrada / %2$@ salida" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ contexto" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%1$@ entrée / %2$@ sortie" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ contexte" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "入力 %1$@ / 出力 %2$@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ コンテキスト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%1$@ entrada / %2$@ saída" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ contexto" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "输入 %1$@ / 输出 %2$@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 上下文" } } } }, - "%@ reasoning": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%@ Reasoning" + "%@ in / %@ out" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ ein / %2$@ aus" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ in / %2$@ out" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%@ razonamiento" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ entrada / %2$@ salida" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%@ raisonnement" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ entrée / %2$@ sortie" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ 推論" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "入力 %1$@ / 出力 %2$@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%@ raciocínio" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ entrada / %2$@ saída" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%@ 推理" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "输入 %1$@ / 输出 %2$@" } } } }, - "%@ tokens": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%@ Tokens" + "%@ reasoning" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ Reasoning" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%@ tokens" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ razonamiento" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%@ jetons" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ raisonnement" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ トークン" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 推論" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%@ tokens" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ raciocínio" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%@ 个令牌" - } - } - } - }, - "%@ · %@": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "%1$@ · %2$@" - } - } - } - }, - "%@ → %@": { - "localizations": { - "en": { - "stringUnit": { - "state": "new", - "value": "%1$@ → %2$@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 推理" } } } }, - "%@s · %lld tools": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%1$@ s · %2$lld Tools" + "%@ tokens" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ Tokens" } }, - "en": { - "stringUnit": { - "state": "new", - "value": "%1$@s · %2$lld tools" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ tokens" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%1$@ s · %2$lld herramientas" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ jetons" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%1$@ s · %2$lld outils" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ トークン" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%1$@ 秒 · %2$lld ツール" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ tokens" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%1$@ s · %2$lld ferramentas" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%1$@秒 · %2$lld 个工具" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 个令牌" } } } }, - "%lld": {}, - "%lld %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%1$lld %2$@" + "%@s · %lld tools" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ s · %2$lld Tools" } }, - "en": { - "stringUnit": { - "state": "new", - "value": "%1$lld %2$@" + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@s · %2$lld tools" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%1$lld %2$@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ s · %2$lld herramientas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%1$lld %2$@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ s · %2$lld outils" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%1$lld %2$@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ 秒 · %2$lld ツール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%1$lld %2$@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@ s · %2$lld ferramentas" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%1$lld %2$@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$@秒 · %2$lld 个工具" } } } }, - "%lld chars": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%lld Zeichen" + "%lld" : { + + }, + "%lld %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld %2$@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%lld caracteres" + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$lld %2$@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%lld caractères" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld %2$@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%lld 文字" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld %2$@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%lld caracteres" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld %2$@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 个字符" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld %2$@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld %2$@" } } } }, - "%lld delivery failure%@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%1$lld Zustellfehler%2$@" - } - }, - "en": { - "stringUnit": { - "state": "new", - "value": "%1$lld delivery failure%2$@" + "%lld changes" : { + "comment" : "A label showing the number of changes that will be made when installing a template. The argument is the number of changes.", + "isCommentAutoGenerated" : true + }, + "%lld chars" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Zeichen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%1$lld error de entrega%2$@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld caracteres" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%1$lld échec de livraison%2$@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld caractères" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%1$lld 件の配信失敗%2$@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 文字" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%1$lld falha de entrega%2$@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld caracteres" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%1$lld 次投递失败%2$@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 个字符" } } } }, - "%lld entries": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%lld Einträge" + "%lld delivery failure%@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld Zustellfehler%2$@" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$lld delivery failure%2$@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%lld entradas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld error de entrega%2$@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%lld entrées" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld échec de livraison%2$@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%lld 件" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld 件の配信失敗%2$@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%lld entradas" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld falha de entrega%2$@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 条记录" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld 次投递失败%2$@" } } } }, - "%lld files": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%lld Dateien" + "%lld entries" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Einträge" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%lld archivos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld entradas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%lld fichiers" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld entrées" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%lld ファイル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 件" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%lld arquivos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld entradas" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 个文件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 条记录" } } } }, - "%lld messages": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%lld Nachrichten" + "%lld files" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Dateien" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%lld mensajes" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld archivos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%lld messages" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld fichiers" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%lld メッセージ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld ファイル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%lld mensagens" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld arquivos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 条消息" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 个文件" } } } }, - "%lld msgs": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%lld Nachr." + "%lld messages" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Nachrichten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%lld msjs" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld mensajes" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%lld msgs" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld messages" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%lld メッセージ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld メッセージ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%lld msgs" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld mensagens" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 条" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 条消息" } } } }, - "%lld of %lld enabled": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%1$lld von %2$lld aktiviert" + "%lld msgs" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Nachr." } }, - "en": { - "stringUnit": { - "state": "new", - "value": "%1$lld of %2$lld enabled" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld msjs" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%1$lld de %2$lld habilitados" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld msgs" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%1$lld sur %2$lld activés" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld メッセージ" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%2$lld 中 %1$lld 個が有効" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld msgs" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%1$lld de %2$lld ativadas" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已启用 %1$lld / %2$lld" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 条" } } } }, - "%lld reasoning": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%lld Reasoning" + "%lld of %lld enabled" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld von %2$lld aktiviert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%lld razonamiento" + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$lld of %2$lld enabled" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%lld raisonnement" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld de %2$lld habilitados" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%lld 推論" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld sur %2$lld activés" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%lld raciocínio" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%2$lld 中 %1$lld 個が有効" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 次推理" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%1$lld de %2$lld ativadas" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已启用 %1$lld / %2$lld" } } } }, - "%lld req": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%lld erforderlich" + "%lld reasoning" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Reasoning" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%lld requeridos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld razonamiento" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%lld requis" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld raisonnement" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%lld 必須" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 推論" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%lld obrig." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld raciocínio" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 个必填" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 次推理" } } } }, - "%lld required config": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%lld Pflichteinstellungen" + "%lld req" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld erforderlich" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%lld configuraciones requeridas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld requeridos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%lld configuration(s) requise(s)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld requis" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "必須設定 %lld 件" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 必須" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%lld configurações obrigatórias" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld obrig." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 项必需配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 个必填" } } } }, - "%lld sessions": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%lld Sitzungen" + "%lld required config" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Pflichteinstellungen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%lld sesiones" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld configuraciones requeridas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%lld sessions" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld configuration(s) requise(s)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%lld セッション" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "必須設定 %lld 件" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%lld sessões" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld configurações obrigatórias" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 次会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 项必需配置" } } } }, - "%lld tokens": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%lld Tokens" + "%lld sessions" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Sitzungen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%lld tokens" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld sesiones" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%lld jetons" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld sessions" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%lld トークン" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld セッション" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%lld tokens" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld sessões" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 个令牌" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 次会话" } } } }, - "%lld tools": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%lld Tools" + "%lld tokens" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Tokens" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "%lld herramientas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld tokens" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "%lld outils" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld jetons" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%lld ツール" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld トークン" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "%lld ferramentas" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld tokens" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%lld 个工具" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 个令牌" } } } }, - "%lld.": {}, - "(%lld tokens)": {}, - "/%@": {}, - "22": {}, - "30 Days": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "30 Tage" + "%lld tools" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld Tools" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "30 días" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld herramientas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "30 jours" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld outils" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "30 日間" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld ツール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "30 dias" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld ferramentas" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "30 天" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 个工具" } } } }, - "7 Days": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "7 Tage" + "%lld." : { + + }, + "•" : { + + }, + "••••••••••" : { + + }, + "+ %lld more…" : { + "comment" : "A button that shows the number of files that were left behind by the template uninstaller.", + "isCommentAutoGenerated" : true + }, + "<%@>" : { + + }, + "1.0.0" : { + "comment" : "A placeholder for the version of a template.", + "isCommentAutoGenerated" : true + }, + "7 Days" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "7 Tage" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "7 días" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "7 días" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "7 jours" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "7 jours" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "7 日間" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "7 日間" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "7 dias" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "7 dias" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "7 天" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "7 天" } } } }, - "90 Days": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "90 Tage" + "22" : { + + }, + "30 Days" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "30 Tage" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "90 días" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "30 días" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "90 jours" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "30 jours" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "90 日間" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "30 日間" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "90 dias" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "30 dias" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "90 天" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "30 天" } } } }, - "<%@>": {}, - "A QR code will appear below. Scan it with WhatsApp on your phone. The session is saved to ~/.hermes/platforms/whatsapp/ so you won't need to scan again after restarts.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Unten erscheint ein QR-Code. Scanne ihn mit WhatsApp auf deinem Telefon. Die Sitzung wird unter ~/.hermes/platforms/whatsapp/ gespeichert, damit nach Neustarts kein erneuter Scan nötig ist." + "90 Days" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "90 Tage" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Aparecerá un código QR abajo. Escanéalo con WhatsApp en tu teléfono. La sesión se guarda en ~/.hermes/platforms/whatsapp/ para no tener que volver a escanearla tras reiniciar." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "90 días" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Un QR code apparaîtra ci-dessous. Scannez-le avec WhatsApp sur votre téléphone. La session est enregistrée dans ~/.hermes/platforms/whatsapp/, vous n'aurez donc pas besoin de la rescanner après un redémarrage." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "90 jours" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "下に QR コードが表示されます。スマートフォンの WhatsApp でスキャンしてください。セッションは ~/.hermes/platforms/whatsapp/ に保存されるため、再起動後に再度スキャンする必要はありません。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "90 日間" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Um QR code aparecerá abaixo. Escaneie com o WhatsApp do seu celular. A sessão é salva em ~/.hermes/platforms/whatsapp/ para não precisar escanear de novo após reinícios." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "90 dias" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "二维码将在下方显示。用手机上的 WhatsApp 扫描。会话保存在 ~/.hermes/platforms/whatsapp/,重启后无需再次扫描。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "90 天" } } } }, - "API Key": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "API-Schlüssel" + "A memory block was recorded in the lock but is no longer present in MEMORY.md — skipping." : { + "comment" : "A message that appears when a memory block is no longer present in MEMORY.md.", + "isCommentAutoGenerated" : true + }, + "A QR code will appear below. Scan it with WhatsApp on your phone. The session is saved to ~/.hermes/platforms/whatsapp/ so you won't need to scan again after restarts." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unten erscheint ein QR-Code. Scanne ihn mit WhatsApp auf deinem Telefon. Die Sitzung wird unter ~/.hermes/platforms/whatsapp/ gespeichert, damit nach Neustarts kein erneuter Scan nötig ist." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Clave de API" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aparecerá un código QR abajo. Escanéalo con WhatsApp en tu teléfono. La sesión se guarda en ~/.hermes/platforms/whatsapp/ para no tener que volver a escanearla tras reiniciar." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Clé API" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Un QR code apparaîtra ci-dessous. Scannez-le avec WhatsApp sur votre téléphone. La session est enregistrée dans ~/.hermes/platforms/whatsapp/, vous n'aurez donc pas besoin de la rescanner après un redémarrage." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "API キー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "下に QR コードが表示されます。スマートフォンの WhatsApp でスキャンしてください。セッションは ~/.hermes/platforms/whatsapp/ に保存されるため、再起動後に再度スキャンする必要はありません。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Chave de API" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Um QR code aparecerá abaixo. Escaneie com o WhatsApp do seu celular. A sessão é salva em ~/.hermes/platforms/whatsapp/ para não precisar escanear de novo após reinícios." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "API 密钥" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "二维码将在下方显示。用手机上的 WhatsApp 扫描。会话保存在 ~/.hermes/platforms/whatsapp/,重启后无需再次扫描。" } } } }, - "API keys are never displayed in full. Scarf only shows the last 4 characters for identification. Full key values are stored by hermes in ~/.hermes/auth.json.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "API-Schlüssel werden nie vollständig angezeigt. Scarf zeigt nur die letzten 4 Zeichen zur Identifikation. Die vollständigen Werte speichert hermes in ~/.hermes/auth.json." + "Access Control" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zugriffskontrolle" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Las claves de API nunca se muestran completas. Scarf solo muestra los últimos 4 caracteres para identificación. Los valores completos los guarda hermes en ~/.hermes/auth.json." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Control de acceso" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les clés API ne sont jamais affichées en entier. Scarf n'affiche que les 4 derniers caractères pour identification. Les valeurs complètes sont stockées par hermes dans ~/.hermes/auth.json." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contrôle d'accès" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "API キーは完全な形では表示されません。Scarf は識別用に末尾 4 文字のみを表示します。完全なキーの値は hermes が ~/.hermes/auth.json に保存します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクセス制御" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Chaves de API nunca são exibidas por completo. O Scarf mostra apenas os últimos 4 caracteres para identificação. Os valores completos são armazenados pelo hermes em ~/.hermes/auth.json." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Controle de acesso" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "API 密钥永远不会完整显示。Scarf 仅显示后 4 位用于识别。完整密钥由 hermes 存储在 ~/.hermes/auth.json。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "访问控制" } } } }, - "Access Control": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Zugriffskontrolle" + "Actions" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktionen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Control de acceso" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Acciones" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Contrôle d'accès" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actions" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクセス制御" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクション" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Controle de acesso" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ações" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "访问控制" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "操作" } } } }, - "Actions": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktionen" + "active" : { + + }, + "Active" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktiv" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Acciones" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Actions" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actif" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクション" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティブ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ações" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ativa" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "操作" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "活跃" } } } }, - "Active": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktiv" + "Active Personality" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktive Persönlichkeit" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Activo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalidad activa" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Actif" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personnalité active" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティブ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティブなパーソナリティ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ativa" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalidade ativa" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "活跃" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "活跃人格" } } } }, - "Active Personality": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktive Persönlichkeit" + "Active profile" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktives Profil" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Personalidad activa" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Perfil activo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Personnalité active" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil actif" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティブなパーソナリティ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティブなプロファイル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Personalidade ativa" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Perfil ativo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "活跃人格" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当前配置" } } } }, - "Active profile": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktives Profil" + "Activity" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivität" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Perfil activo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actividad" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Profil actif" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activité" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティブなプロファイル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティビティ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Perfil ativo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atividade" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "当前配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "活动" } } } }, - "Activity": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktivität" + "Activity Patterns" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivitätsmuster" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Actividad" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Patrones de actividad" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Activité" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schémas d'activité" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティビティ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティビティパターン" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Atividade" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Padrões de atividade" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "活动" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "活动模式" } } } }, - "Activity Patterns": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktivitätsmuster" + "Add" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hinzufügen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Patrones de actividad" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añadir" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Schémas d'activité" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティビティパターン" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "追加" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Padrões de atividade" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "活动模式" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加" } } } }, - "Add": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hinzufügen" + "Add a project folder to get started. Create a .scarf/dashboard.json file in your project to define widgets." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Füge einen Projektordner hinzu, um zu beginnen. Lege in deinem Projekt eine .scarf/dashboard.json an, um Widgets zu definieren." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añadir" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añade una carpeta de proyecto para empezar. Crea un archivo .scarf/dashboard.json en tu proyecto para definir widgets." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajouter" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajoutez un dossier de projet pour commencer. Créez un fichier .scarf/dashboard.json dans votre projet pour définir des widgets." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "追加" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロジェクトフォルダを追加して始めましょう。ウィジェットを定義するには、プロジェクト内に .scarf/dashboard.json ファイルを作成してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicionar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicione uma pasta de projeto para começar. Crie um arquivo .scarf/dashboard.json no seu projeto para definir widgets." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加项目文件夹以开始使用。在项目中创建 .scarf/dashboard.json 文件来定义小部件。" } } } }, - "Add Command": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Befehl hinzufügen" + "Add Command" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Befehl hinzufügen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añadir comando" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añadir comando" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajouter une commande" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter une commande" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コマンドを追加" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "コマンドを追加" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicionar comando" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar comando" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加命令" } } } }, - "Add Credential": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Anmeldedaten hinzufügen" + "Add Credential" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anmeldedaten hinzufügen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añadir credencial" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añadir credencial" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajouter des identifiants" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter des identifiants" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "資格情報を追加" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "資格情報を追加" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicionar credencial" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar credencial" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加凭证" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加凭证" } } } }, - "Add Custom": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Eigene hinzufügen" + "Add credentials in **Configure → Credential Pools**, set `ANTHROPIC_API_KEY` (or similar) in `~/.hermes/.env`, or export it in your shell profile, then restart Scarf." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anmeldedaten in **Konfigurieren → Credential-Pools** hinzufügen, `ANTHROPIC_API_KEY` (oder ähnlich) in `~/.hermes/.env` setzen oder in deinem Shell-Profil exportieren, dann Scarf neu starten." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añadir personalizado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añade credenciales en **Configurar → Grupos de credenciales**, establece `ANTHROPIC_API_KEY` (o similar) en `~/.hermes/.env`, o expórtala en tu perfil de shell, y reinicia Scarf." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajouter personnalisé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajoutez des identifiants dans **Configurer → Pools d'identifiants**, définissez `ANTHROPIC_API_KEY` (ou équivalent) dans `~/.hermes/.env`, ou exportez-la dans votre profil shell, puis redémarrez Scarf." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "カスタム追加" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "**設定 → 資格情報プール** で資格情報を追加するか、`~/.hermes/.env` で `ANTHROPIC_API_KEY`(または類似の変数)を設定するか、シェルプロファイルでエクスポートしてから Scarf を再起動してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicionar personalizado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicione credenciais em **Configurar → Pools de credenciais**, defina `ANTHROPIC_API_KEY` (ou similar) em `~/.hermes/.env` ou exporte no seu perfil de shell, e reinicie o Scarf." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "自定义添加" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 **配置 → 凭证池** 中添加凭证,在 `~/.hermes/.env` 中设置 `ANTHROPIC_API_KEY`(或类似变量),或在 shell 配置中导出该变量,然后重启 Scarf。" } } } }, - "Add Custom MCP Server": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Eigenen MCP-Server hinzufügen" + "Add Custom" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eigene hinzufügen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añadir servidor MCP personalizado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añadir personalizado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajouter un serveur MCP personnalisé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter personnalisé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "カスタム MCP サーバーを追加" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "カスタム追加" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicionar servidor MCP personalizado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar personalizado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加自定义 MCP 服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自定义添加" } } } }, - "Add Project": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Projekt hinzufügen" + "Add Custom MCP Server" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eigenen MCP-Server hinzufügen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añadir proyecto" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añadir servidor MCP personalizado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajouter un projet" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter un serveur MCP personnalisé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロジェクトを追加" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "カスタム MCP サーバーを追加" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicionar projeto" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar servidor MCP personalizado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加项目" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加自定义 MCP 服务器" } } } }, - "Add Quick Command": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Schnellbefehl hinzufügen" + "Add from Preset" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aus Voreinstellung hinzufügen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añadir comando rápido" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añadir desde preajuste" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajouter une commande rapide" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter depuis un préréglage" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "クイックコマンドを追加" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プリセットから追加" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicionar comando rápido" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar a partir de predefinição" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加快捷命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从预设添加" } } } }, - "Add Remote Server": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Remote-Server hinzufügen" + "Add Project" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Projekt hinzufügen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añadir servidor remoto" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añadir proyecto" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajouter un serveur distant" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter un projet" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リモートサーバーを追加" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロジェクトを追加" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicionar servidor remoto" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar projeto" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加远程服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加项目" } } } }, - "Add Server": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Server hinzufügen" + "Add Quick Command" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schnellbefehl hinzufügen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añadir servidor" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añadir comando rápido" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajouter un serveur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter une commande rapide" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サーバーを追加" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クイックコマンドを追加" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicionar servidor" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar comando rápido" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加快捷命令" } } } }, - "Add a project folder to get started. Create a .scarf/dashboard.json file in your project to define widgets.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Füge einen Projektordner hinzu, um zu beginnen. Lege in deinem Projekt eine .scarf/dashboard.json an, um Widgets zu definieren." + "Add Remote Server" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remote-Server hinzufügen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añade una carpeta de proyecto para empezar. Crea un archivo .scarf/dashboard.json en tu proyecto para definir widgets." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añadir servidor remoto" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajoutez un dossier de projet pour commencer. Créez un fichier .scarf/dashboard.json dans votre projet pour définir des widgets." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter un serveur distant" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロジェクトフォルダを追加して始めましょう。ウィジェットを定義するには、プロジェクト内に .scarf/dashboard.json ファイルを作成してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リモートサーバーを追加" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicione uma pasta de projeto para começar. Crie um arquivo .scarf/dashboard.json no seu projeto para definir widgets." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar servidor remoto" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加项目文件夹以开始使用。在项目中创建 .scarf/dashboard.json 文件来定义小部件。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加远程服务器" } } } }, - "Add credentials in **Configure → Credential Pools**, set `ANTHROPIC_API_KEY` (or similar) in `~/.hermes/.env`, or export it in your shell profile, then restart Scarf.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Anmeldedaten in **Konfigurieren → Credential-Pools** hinzufügen, `ANTHROPIC_API_KEY` (oder ähnlich) in `~/.hermes/.env` setzen oder in deinem Shell-Profil exportieren, dann Scarf neu starten." + "Add rotation credentials so hermes can failover between keys when one hits rate limits." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rotations-Anmeldedaten hinzufügen, damit hermes zwischen Schlüsseln wechseln kann, wenn einer ein Rate-Limit erreicht." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añade credenciales en **Configurar → Grupos de credenciales**, establece `ANTHROPIC_API_KEY` (o similar) en `~/.hermes/.env`, o expórtala en tu perfil de shell, y reinicia Scarf." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añade credenciales de rotación para que hermes pueda cambiar entre claves cuando una alcance el límite de tasa." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajoutez des identifiants dans **Configurer → Pools d'identifiants**, définissez `ANTHROPIC_API_KEY` (ou équivalent) dans `~/.hermes/.env`, ou exportez-la dans votre profil shell, puis redémarrez Scarf." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajoutez des identifiants de rotation pour que hermes puisse basculer entre les clés lorsqu'une atteint la limite de débit." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "**設定 → 資格情報プール** で資格情報を追加するか、`~/.hermes/.env` で `ANTHROPIC_API_KEY`(または類似の変数)を設定するか、シェルプロファイルでエクスポートしてから Scarf を再起動してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ローテーション用の資格情報を追加すると、あるキーがレート制限に達した際に hermes が別のキーにフェイルオーバーできます。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicione credenciais em **Configurar → Pools de credenciais**, defina `ANTHROPIC_API_KEY` (ou similar) em `~/.hermes/.env` ou exporte no seu perfil de shell, e reinicie o Scarf." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicione credenciais de rotação para que o hermes possa alternar entre chaves quando uma atingir o limite de taxa." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在 **配置 → 凭证池** 中添加凭证,在 `~/.hermes/.env` 中设置 `ANTHROPIC_API_KEY`(或类似变量),或在 shell 配置中导出该变量,然后重启 Scarf。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加轮换凭证,以便 hermes 在某个密钥达到速率限制时能切换到其他密钥。" } } } }, - "Add from Preset": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aus Voreinstellung hinzufügen" + "Add Server" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server hinzufügen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añadir desde preajuste" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añadir servidor" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajouter depuis un préréglage" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter un serveur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プリセットから追加" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サーバーを追加" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicionar a partir de predefinição" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicionar servidor" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从预设添加" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加服务器" } } } }, - "Add rotation credentials so hermes can failover between keys when one hits rate limits.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Rotations-Anmeldedaten hinzufügen, damit hermes zwischen Schlüsseln wechseln kann, wenn einer ein Rate-Limit erreicht." + "Add your first command" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Füge deinen ersten Befehl hinzu" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añade credenciales de rotación para que hermes pueda cambiar entre claves cuando una alcance el límite de tasa." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Añade tu primer comando" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajoutez des identifiants de rotation pour que hermes puisse basculer entre les clés lorsqu'une atteint la limite de débit." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajoutez votre première commande" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ローテーション用の資格情報を追加すると、あるキーがレート制限に達した際に hermes が別のキーにフェイルオーバーできます。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "最初のコマンドを追加" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicione credenciais de rotação para que o hermes possa alternar entre chaves quando uma atingir o limite de taxa." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Adicione seu primeiro comando" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加轮换凭证,以便 hermes 在某个密钥达到速率限制时能切换到其他密钥。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加第一个命令" } } } }, - "Add your first command": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Füge deinen ersten Befehl hinzu" + "Advanced" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erweitert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Añade tu primer comando" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avanzado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ajoutez votre première commande" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avancé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "最初のコマンドを追加" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "詳細" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Adicione seu primeiro comando" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Avançado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加第一个命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "高级" } } } }, - "Advanced": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erweitert" + "After approving in your browser, the provider shows a code. Paste it below and submit." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nach der Genehmigung im Browser zeigt der Anbieter einen Code. Füge ihn unten ein und sende ab." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Avanzado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tras aprobar en tu navegador, el proveedor muestra un código. Pégalo abajo y envíalo." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Avancé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Après avoir approuvé dans votre navigateur, le fournisseur affiche un code. Collez-le ci-dessous et soumettez." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "詳細" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ブラウザで承認するとプロバイダーがコードを表示します。下に貼り付けて送信してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Avançado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Após aprovar no navegador, o provedor mostra um código. Cole abaixo e envie." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "高级" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在浏览器中批准后,提供方会显示一个代码。将其粘贴到下方并提交。" } } } }, - "After approving in your browser, the provider shows a code. Paste it below and submit.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nach der Genehmigung im Browser zeigt der Anbieter einen Code. Füge ihn unten ein und sende ab." + "Agent" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agent" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Tras aprobar en tu navegador, el proveedor muestra un código. Pégalo abajo y envíalo." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agent" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Après avoir approuvé dans votre navigateur, le fournisseur affiche un code. Collez-le ci-dessous et soumettez." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agent" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ブラウザで承認するとプロバイダーがコードを表示します。下に貼り付けて送信してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agent" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Após aprovar no navegador, o provedor mostra um código. Cole abaixo e envie." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agent" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在浏览器中批准后,提供方会显示一个代码。将其粘贴到下方并提交。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agent" } } } }, - "Agent": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Agent" + "Agent-specific instructions (optional)" : { + + }, + "All" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Agent" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Todos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Agent" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tous" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Agent" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すべて" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Agent" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Todos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Agent" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "全部" } } } }, - "All": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Alle" + "All installed hub skills are up to date." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle installierten Hub-Skills sind aktuell." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Todos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Todas las habilidades instaladas desde el hub están al día." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tous" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toutes les compétences installées depuis le hub sont à jour." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "すべて" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "インストールされているハブスキルはすべて最新です。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Todos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Todas as habilidades instaladas do hub estão atualizadas." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "全部" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "所有已安装的 Hub 技能均为最新。" } } } }, - "All Levels": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Alle Ebenen" + "All Levels" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle Ebenen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Todos los niveles" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Todos los niveles" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tous les niveaux" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tous les niveaux" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "すべてのレベル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すべてのレベル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Todos os níveis" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Todos os níveis" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "所有级别" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "所有级别" } } } }, - "All Sessions": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Alle Sitzungen" + "All Sessions" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle Sitzungen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Todas las sesiones" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Todas las sesiones" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Toutes les sessions" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toutes les sessions" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "すべてのセッション" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すべてのセッション" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Todas as sessões" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Todas as sessões" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "所有会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "所有会话" } } } }, - "All Time": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Gesamter Zeitraum" + "All Time" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gesamter Zeitraum" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Todo el tiempo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Todo el tiempo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tout le temps" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tout le temps" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "全期間" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "全期間" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Todo o período" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Todo o período" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "全部时间" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "全部时间" } } } }, - "All installed hub skills are up to date.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Alle installierten Hub-Skills sind aktuell." + "already gone" : { + "comment" : "A tag for a file that is already gone (no longer in the template).", + "isCommentAutoGenerated" : true + }, + "Also works: %@" : { + + }, + "API Key" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "API-Schlüssel" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Todas las habilidades instaladas desde el hub están al día." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clave de API" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Toutes les compétences installées depuis le hub sont à jour." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clé API" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "インストールされているハブスキルはすべて最新です。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "API キー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Todas as habilidades instaladas do hub estão atualizadas." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chave de API" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "所有已安装的 Hub 技能均为最新。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "API 密钥" } } } }, - "App Credentials": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "App-Anmeldedaten" + "API keys are never displayed in full. Scarf only shows the last 4 characters for identification. Full key values are stored by hermes in ~/.hermes/auth.json." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "API-Schlüssel werden nie vollständig angezeigt. Scarf zeigt nur die letzten 4 Zeichen zur Identifikation. Die vollständigen Werte speichert hermes in ~/.hermes/auth.json." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Credenciales de la aplicación" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Las claves de API nunca se muestran completas. Scarf solo muestra los últimos 4 caracteres para identificación. Los valores completos los guarda hermes en ~/.hermes/auth.json." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Identifiants de l'application" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les clés API ne sont jamais affichées en entier. Scarf n'affiche que les 4 derniers caractères pour identification. Les valeurs complètes sont stockées par hermes dans ~/.hermes/auth.json." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アプリ資格情報" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "API キーは完全な形では表示されません。Scarf は識別用に末尾 4 文字のみを表示します。完全なキーの値は hermes が ~/.hermes/auth.json に保存します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Credenciais do app" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chaves de API nunca são exibidas por completo. O Scarf mostra apenas os últimos 4 caracteres para identificação. Os valores completos são armazenados pelo hermes em ~/.hermes/auth.json." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "应用凭证" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "API 密钥永远不会完整显示。Scarf 仅显示后 4 位用于识别。完整密钥由 hermes 存储在 ~/.hermes/auth.json。" } } } }, - "Approval": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Genehmigung" + "App Credentials" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "App-Anmeldedaten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Aprobación" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Credenciales de la aplicación" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Approbation" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Identifiants de l'application" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "承認" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アプリ資格情報" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Aprovação" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Credenciais do app" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "审批" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "应用凭证" } } } }, - "Approvals": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Genehmigungen" + "Approval" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Genehmigung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Aprobaciones" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aprobación" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Approbations" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Approbation" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "承認" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "承認" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Aprovações" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aprovação" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "审批" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "审批" } } } }, - "Approve": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Genehmigen" + "Approvals" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Genehmigungen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Aprobar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aprobaciones" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Approuver" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Approbations" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "承認" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "承認" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Aprovar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aprovações" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "批准" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "审批" } } } }, - "Archive": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Archivieren" + "Approve" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Genehmigen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Archivar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aprobar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Archiver" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Approuver" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アーカイブ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "承認" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Arquivar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aprovar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "归档" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "批准" } } } }, - "Args (one per line)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Argumente (eines pro Zeile)" + "Archive" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Archivieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Argumentos (uno por línea)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Archivar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Arguments (un par ligne)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Archiver" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "引数(1 行に 1 つ)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アーカイブ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Argumentos (um por linha)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arquivar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "参数(每行一个)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "归档" } } } }, - "Arguments": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Argumente" + "Args (one per line)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Argumente (eines pro Zeile)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Argumentos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Argumentos (uno por línea)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Arguments" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arguments (un par ligne)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "引数" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "引数(1 行に 1 つ)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Argumentos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Argumentos (um por linha)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "参数" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "参数(每行一个)" } } } }, - "Assistant Message": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Assistentennachricht" + "Arguments" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Argumente" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Mensaje del asistente" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Argumentos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Message de l'assistant" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arguments" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アシスタントメッセージ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "引数" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Mensagem do assistente" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Argumentos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "助手消息" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "参数" } } } }, - "Auth": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Auth" + "Assistant Message" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Assistentennachricht" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Auth" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mensaje del asistente" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Auth" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Message de l'assistant" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "認証" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アシスタントメッセージ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Auth" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mensagem do assistente" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "认证" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "助手消息" } } } }, - "Authentication": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Authentifizierung" + "Auth" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auth" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Autenticación" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auth" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Authentification" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auth" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "認証" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "認証" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Autenticação" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auth" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "认证" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "认证" } } } }, - "Authentication uses ssh-agent": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Authentifizierung nutzt ssh-agent" + "Authentication" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Authentifizierung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "La autenticación usa ssh-agent" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Autenticación" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "L'authentification utilise ssh-agent" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Authentification" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "認証には ssh-agent を使用します" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "認証" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "A autenticação usa ssh-agent" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Autenticação" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "使用 ssh-agent 进行认证" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "认证" } } } }, - "Authorization Code": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Autorisierungscode" + "Authentication uses ssh-agent" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Authentifizierung nutzt ssh-agent" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Código de autorización" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La autenticación usa ssh-agent" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Code d'autorisation" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'authentification utilise ssh-agent" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "認可コード" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "認証には ssh-agent を使用します" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Código de autorização" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "A autenticação usa ssh-agent" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "授权码" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用 ssh-agent 进行认证" } } } }, - "Authorization URL": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Autorisierungs-URL" + "Author" : { + "comment" : "A label for the author field in the template export sheet.", + "isCommentAutoGenerated" : true + }, + "Author URL" : { + "comment" : "A label for the author's URL field in the template export sheet.", + "isCommentAutoGenerated" : true + }, + "Authorization Code" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Autorisierungscode" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "URL de autorización" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Código de autorización" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "URL d'autorisation" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Code d'autorisation" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "認可 URL" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "認可コード" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "URL de autorização" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Código de autorização" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "授权 URL" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "授权码" } } } }, - "Aux Models": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hilfsmodelle" + "Authorization URL" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Autorisierungs-URL" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Modelos auxiliares" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "URL de autorización" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Modèles auxiliaires" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "URL d'autorisation" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "補助モデル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "認可 URL" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Modelos auxiliares" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "URL de autorização" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "辅助模型" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "授权 URL" } } } }, - "Auxiliary tasks use separate, typically cheaper models. Leave Provider as `auto` to inherit the main provider.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hilfsaufgaben nutzen separate, typischerweise günstigere Modelle. Provider auf `auto` lassen, um den Hauptanbieter zu übernehmen." + "Aux Models" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hilfsmodelle" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Las tareas auxiliares usan modelos separados, normalmente más baratos. Deja Proveedor en `auto` para heredar el proveedor principal." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modelos auxiliares" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les tâches auxiliaires utilisent des modèles distincts, généralement moins coûteux. Laissez Fournisseur sur `auto` pour hériter du fournisseur principal." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modèles auxiliaires" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "補助タスクには別の、通常はより安価なモデルを使用します。メインプロバイダーを継承するには Provider を `auto` のままにしてください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "補助モデル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Tarefas auxiliares usam modelos separados, geralmente mais baratos. Deixe Provedor em `auto` para herdar o provedor principal." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modelos auxiliares" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "辅助任务使用独立的、通常更便宜的模型。将 Provider 保持为 `auto` 以继承主提供方。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "辅助模型" } } } }, - "Back": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Zurück" + "Auxiliary tasks use separate, typically cheaper models. Leave Provider as `auto` to inherit the main provider." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hilfsaufgaben nutzen separate, typischerweise günstigere Modelle. Provider auf `auto` lassen, um den Hauptanbieter zu übernehmen." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Atrás" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Las tareas auxiliares usan modelos separados, normalmente más baratos. Deja Proveedor en `auto` para heredar el proveedor principal." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Retour" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les tâches auxiliaires utilisent des modèles distincts, généralement moins coûteux. Laissez Fournisseur sur `auto` pour hériter du fournisseur principal." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "戻る" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "補助タスクには別の、通常はより安価なモデルを使用します。メインプロバイダーを継承するには Provider を `auto` のままにしてください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Voltar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tarefas auxiliares usam modelos separados, geralmente mais baratos. Deixe Provedor em `auto` para herdar o provedor principal." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "返回" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "辅助任务使用独立的、通常更便宜的模型。将 Provider 保持为 `auto` 以继承主提供方。" } } } }, - "Back to Catalog": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Zurück zum Katalog" + "Back" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zurück" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Volver al catálogo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atrás" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Retour au catalogue" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retour" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "カタログに戻る" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "戻る" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Voltar ao catálogo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voltar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "返回目录" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "返回" } } } }, - "Backend": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Backend" + "Back to Catalog" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zurück zum Katalog" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Backend" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Volver al catálogo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Backend" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retour au catalogue" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "バックエンド" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "カタログに戻る" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Backend" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voltar ao catálogo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "后端" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "返回目录" } } } }, - "Backup & Restore": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sicherung & Wiederherstellung" + "Backend" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Backend" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Copia de seguridad y restauración" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Backend" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sauvegarde et restauration" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Backend" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "バックアップと復元" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "バックエンド" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Backup e restauração" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Backend" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "备份与恢复" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "后端" } } } }, - "Backup Now": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Jetzt sichern" + "Backup & Restore" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sicherung & Wiederherstellung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Copia de seguridad ahora" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copia de seguridad y restauración" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sauvegarder maintenant" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sauvegarde et restauration" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "今すぐバックアップ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "バックアップと復元" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Fazer backup agora" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Backup e restauração" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "立即备份" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "备份与恢复" } } } }, - "Becomes the key under mcp_servers: in config.yaml.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wird zum Schlüssel unter mcp_servers: in config.yaml." + "Backup Now" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jetzt sichern" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Se convierte en la clave bajo mcp_servers: en config.yaml." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copia de seguridad ahora" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Devient la clé sous mcp_servers : dans config.yaml." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sauvegarder maintenant" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "config.yaml の mcp_servers: 下のキーになります。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今すぐバックアップ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Vira a chave sob mcp_servers: em config.yaml." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fazer backup agora" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "将作为 config.yaml 中 mcp_servers: 下的键。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "立即备份" } } } }, - "Behavior": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Verhalten" + "Becomes the key under mcp_servers: in config.yaml." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wird zum Schlüssel unter mcp_servers: in config.yaml." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comportamiento" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se convierte en la clave bajo mcp_servers: en config.yaml." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Comportement" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Devient la clé sous mcp_servers : dans config.yaml." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "動作" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "config.yaml の mcp_servers: 下のキーになります。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Comportamento" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vira a chave sob mcp_servers: em config.yaml." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "行为" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "将作为 config.yaml 中 mcp_servers: 下的键。" } } } }, - "BlueBubbles Docs": {}, - "BlueBubbles Server": {}, - "Browse": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Durchsuchen" + "Behavior" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verhalten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Examinar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comportamiento" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Parcourir" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comportement" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "参照" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "動作" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Explorar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comportamento" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "浏览" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "行为" } } } }, - "Browse Hub": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hub durchsuchen" + "BlueBubbles Docs" : { + + }, + "BlueBubbles Server" : { + + }, + "Browse" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Durchsuchen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Examinar el hub" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Examinar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Parcourir le hub" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parcourir" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ハブを参照" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "参照" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Explorar hub" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Explorar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "浏览 Hub" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "浏览" } } } }, - "Browse the Hub": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hub durchsuchen" + "Browse Hub" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hub durchsuchen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Examinar el hub" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Examinar el hub" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Parcourir le hub" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parcourir le hub" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ハブを参照" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ハブを参照" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Explorar o hub" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Explorar hub" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "浏览 Hub" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "浏览 Hub" } } } }, - "Browse...": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Durchsuchen..." + "Browse the Hub" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hub durchsuchen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Examinar..." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Examinar el hub" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Parcourir..." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parcourir le hub" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "参照..." + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ハブを参照" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Explorar..." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Explorar o hub" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "浏览..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "浏览 Hub" } } } }, - "Browser": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Browser" + "Browse..." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Durchsuchen..." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Navegador" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Examinar..." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Navigateur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parcourir..." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ブラウザ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "参照..." } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Navegador" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Explorar..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "浏览器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "浏览..." } } } }, - "Built-in Memory": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Integrierter Speicher" + "Browser" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Browser" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Memoria integrada" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Navegador" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Mémoire intégrée" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Navigateur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ビルトインメモリ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ブラウザ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Memória integrada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Navegador" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "内置记忆" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "浏览器" } } } }, - "By Day": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nach Tag" + "Building template…" : { + + }, + "Built-in Memory" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Integrierter Speicher" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Por día" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Memoria integrada" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Par jour" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mémoire intégrée" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "日別" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ビルトインメモリ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Por dia" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Memória integrada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "按天" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "内置记忆" } } } }, - "By Hour": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nach Stunde" + "By Day" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nach Tag" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Por hora" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Por día" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Par heure" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Par jour" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "時間別" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "日別" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Por hora" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Por dia" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "按小时" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按天" } } } }, - "CLI": {}, - "Call timeout": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aufruf-Timeout" + "By Hour" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nach Stunde" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Tiempo de espera de llamada" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Por hora" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Délai d'appel" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Par heure" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "呼び出しタイムアウト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "時間別" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Tempo limite da chamada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Por hora" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "调用超时" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按小时" } } } }, - "Camofox": {}, - "Can't read Hermes state on %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hermes-Status auf %@ nicht lesbar" + "Call timeout" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aufruf-Timeout" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "No se puede leer el estado de Hermes en %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tiempo de espera de llamada" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Impossible de lire l'état de Hermes sur %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Délai d'appel" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ 上の Hermes 状態を読み取れません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "呼び出しタイムアウト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Não é possível ler o estado do Hermes em %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tempo limite da chamada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无法读取 %@ 上的 Hermes 状态" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "调用超时" } } } }, - "Cancel": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Abbrechen" + "Camofox" : { + + }, + "Can't read Hermes state on %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes-Status auf %@ nicht lesbar" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cancelar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se puede leer el estado de Hermes en %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Annuler" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossible de lire l'état de Hermes sur %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "キャンセル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 上の Hermes 状態を読み取れません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Cancelar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não é possível ler o estado do Hermes em %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "取消" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法读取 %@ 上的 Hermes 状态" } } } }, - "Changes won't take effect until Hermes reloads the config.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Änderungen werden erst wirksam, wenn Hermes die Konfiguration neu lädt." + "Cancel" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abbrechen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Los cambios no surtirán efecto hasta que Hermes recargue la configuración." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancelar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les modifications ne prendront effet qu'au rechargement de la configuration par Hermes." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Annuler" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Hermes が設定を再読み込みするまで変更は反映されません。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "キャンセル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "As alterações só têm efeito quando o Hermes recarregar a configuração." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancelar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "更改在 Hermes 重新加载配置前不会生效。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "取消" } } } }, - "Chat": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Chat" + "Category" : { + "comment" : "A label for the category of a template.", + "isCommentAutoGenerated" : true + }, + "Changes won't take effect until Hermes reloads the config." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Änderungen werden erst wirksam, wenn Hermes die Konfiguration neu lädt." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Chat" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Los cambios no surtirán efecto hasta que Hermes recargue la configuración." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Chat" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les modifications ne prendront effet qu'au rechargement de la configuration par Hermes." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "チャット" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes が設定を再読み込みするまで変更は反映されません。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Chat" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "As alterações só têm efeito quando o Hermes recarregar a configuração." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "聊天" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "更改在 Hermes 重新加载配置前不会生效。" } } } }, - "Chat Messages": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Chat-Nachrichten" + "Chat" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chat" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Mensajes de chat" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chat" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Messages de chat" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chat" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "チャットメッセージ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "チャット" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Mensagens do chat" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chat" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "聊天消息" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "聊天" } } } }, - "Check": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Prüfen" + "Chat Messages" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chat-Nachrichten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comprobar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mensajes de chat" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Vérifier" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Messages de chat" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "確認" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "チャットメッセージ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Verificar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mensagens do chat" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "检查" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "聊天消息" } } } }, - "Check Now": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Jetzt prüfen" + "Check" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prüfen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comprobar ahora" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comprobar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Vérifier maintenant" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vérifier" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "今すぐ確認" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "確認" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Verificar agora" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verificar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "立即检查" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检查" } } } }, - "Check for Updates": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nach Updates suchen" + "Check for Updates" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nach Updates suchen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Buscar actualizaciones" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buscar actualizaciones" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Vérifier les mises à jour" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vérifier les mises à jour" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アップデートを確認" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アップデートを確認" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Verificar atualizações" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verificar atualizações" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "检查更新" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检查更新" } } } }, - "Check for Updates…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nach Updates suchen…" + "Check for Updates…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nach Updates suchen…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Buscar actualizaciones…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buscar actualizaciones…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Vérifier les mises à jour…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vérifier les mises à jour…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アップデートを確認…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アップデートを確認…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Verificar atualizações…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verificar atualizações…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "检查更新…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检查更新…" } } } }, - "Checking…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Prüfe…" + "Check Now" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jetzt prüfen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comprobando…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comprobar ahora" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Vérification…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vérifier maintenant" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "確認中…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今すぐ確認" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Verificando…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verificar agora" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "检查中…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "立即检查" } } } }, - "Checkpoints": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Checkpoints" + "Checking…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prüfe…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Puntos de control" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comprobando…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Points de contrôle" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vérification…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "チェックポイント" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "確認中…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Checkpoints" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verificando…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "检查点" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检查中…" } } } }, - "Choose a cron job from the list": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wähle einen Cron-Job aus der Liste" + "Checkpoints" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Checkpoints" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige una tarea cron de la lista" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Puntos de control" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez une tâche cron dans la liste" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Points de contrôle" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リストから cron ジョブを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "チェックポイント" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Escolha uma tarefa cron na lista" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Checkpoints" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从列表中选择一个定时任务" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "检查点" } } } }, - "Choose a profile to inspect.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wähle ein Profil zur Ansicht." + "Choose a cron job from the list" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wähle einen Cron-Job aus der Liste" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige un perfil para inspeccionar." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elige una tarea cron de la lista" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez un profil à inspecter." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez une tâche cron dans la liste" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "検査するプロファイルを選択してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リストから cron ジョブを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Escolha um perfil para inspecionar." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Escolha uma tarefa cron na lista" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择一个配置进行查看。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从列表中选择一个定时任务" } } } }, - "Choose a project from the sidebar to view its dashboard.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wähle ein Projekt aus der Seitenleiste, um sein Dashboard zu sehen." + "Choose a profile to inspect." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wähle ein Profil zur Ansicht." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige un proyecto en la barra lateral para ver su panel." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elige un perfil para inspeccionar." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez un projet dans la barre latérale pour voir son tableau de bord." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez un profil à inspecter." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サイドバーからプロジェクトを選択してダッシュボードを表示します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "検査するプロファイルを選択してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Escolha um projeto na barra lateral para ver o painel." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Escolha um perfil para inspecionar." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从侧边栏选择项目以查看其仪表盘。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择一个配置进行查看。" } } } }, - "Choose a session from the list": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wähle eine Sitzung aus der Liste" + "Choose a project from the sidebar to view its dashboard." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wähle ein Projekt aus der Seitenleiste, um sein Dashboard zu sehen." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige una sesión de la lista" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elige un proyecto en la barra lateral para ver su panel." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez une session dans la liste" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez un projet dans la barre latérale pour voir son tableau de bord." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リストからセッションを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サイドバーからプロジェクトを選択してダッシュボードを表示します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Escolha uma sessão na lista" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Escolha um projeto na barra lateral para ver o painel." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从列表中选择一个会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从侧边栏选择项目以查看其仪表盘。" } } } }, - "Choose a skill from the list": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wähle einen Skill aus der Liste" + "Choose a session from the list" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wähle eine Sitzung aus der Liste" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige una habilidad de la lista" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elige una sesión de la lista" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez une compétence dans la liste" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez une session dans la liste" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リストからスキルを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リストからセッションを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Escolha uma habilidade na lista" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Escolha uma sessão na lista" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从列表中选择一个技能" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从列表中选择一个会话" } } } }, - "Choose an entry from the list": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wähle einen Eintrag aus der Liste" + "Choose a skill from the list" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wähle einen Skill aus der Liste" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige una entrada de la lista" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elige una habilidad de la lista" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez une entrée dans la liste" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez une compétence dans la liste" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リストからエントリを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リストからスキルを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Escolha uma entrada na lista" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Escolha uma habilidade na lista" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从列表中选择一个条目" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从列表中选择一个技能" } } } }, - "Choose…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Auswählen…" + "Choose an entry from the list" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wähle einen Eintrag aus der Liste" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elegir…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elige una entrada de la lista" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisir…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez une entrée dans la liste" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "選択…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リストからエントリを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Escolher…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Escolha uma entrada na lista" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从列表中选择一个条目" } } } }, - "Clear Token": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Token löschen" + "Choose Folder…" : { + "comment" : "A button that opens a dialog for choosing a folder.", + "isCommentAutoGenerated" : true + }, + "Choose Parent Folder" : { + + }, + "Choose…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auswählen…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Borrar token" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elegir…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Effacer le jeton" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisir…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "トークンをクリア" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "選択…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Limpar token" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Escolher…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "清除令牌" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择…" } } } }, - "Clear all skills on save": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Alle Skills beim Speichern löschen" + "Clear all skills on save" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle Skills beim Speichern löschen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Borrar todas las habilidades al guardar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Borrar todas las habilidades al guardar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Effacer toutes les compétences à l'enregistrement" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Effacer toutes les compétences à l'enregistrement" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "保存時にすべてのスキルをクリア" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存時にすべてのスキルをクリア" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Limpar todas as habilidades ao salvar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limpar todas as habilidades ao salvar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "保存时清除所有技能" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存时清除所有技能" } } } }, - "Click Add to connect to a remote Hermes installation over SSH.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Klicke auf Hinzufügen, um dich per SSH mit einer entfernten Hermes-Installation zu verbinden." + "Clear Token" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Token löschen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Haz clic en Añadir para conectarte a una instalación remota de Hermes mediante SSH." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Borrar token" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cliquez sur Ajouter pour vous connecter à une installation Hermes distante via SSH." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Effacer le jeton" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "追加をクリックして SSH 経由でリモートの Hermes インストールに接続します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "トークンをクリア" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Clique em Adicionar para se conectar a uma instalação remota do Hermes via SSH." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limpar token" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "点击添加以通过 SSH 连接到远程 Hermes 安装。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "清除令牌" } } } }, - "Click for details": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Für Details klicken" + "CLI" : { + + }, + "Click Add to connect to a remote Hermes installation over SSH." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Klicke auf Hinzufügen, um dich per SSH mit einer entfernten Hermes-Installation zu verbinden." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Haz clic para ver detalles" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Haz clic en Añadir para conectarte a una instalación remota de Hermes mediante SSH." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cliquez pour les détails" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cliquez sur Ajouter pour vous connecter à une installation Hermes distante via SSH." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "クリックして詳細" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "追加をクリックして SSH 経由でリモートの Hermes インストールに接続します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Clique para ver detalhes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clique em Adicionar para se conectar a uma instalação remota do Hermes via SSH." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "点击查看详情" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "点击添加以通过 SSH 连接到远程 Hermes 安装。" } } } }, - "Clicking Start OAuth opens the provider's authorization page in your browser. After you approve, copy the code the provider displays and paste it back into the terminal that appears next.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ein Klick auf OAuth starten öffnet die Autorisierungsseite des Anbieters im Browser. Nach der Genehmigung kopierst du den angezeigten Code und fügst ihn in das erscheinende Terminal ein." + "Click for details" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Für Details klicken" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Al hacer clic en Iniciar OAuth se abre la página de autorización del proveedor en tu navegador. Tras aprobar, copia el código mostrado y pégalo en el terminal que aparecerá a continuación." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Haz clic para ver detalles" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cliquer sur Démarrer OAuth ouvre la page d'autorisation du fournisseur dans votre navigateur. Après approbation, copiez le code affiché par le fournisseur et collez-le dans le terminal qui apparaît ensuite." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cliquez pour les détails" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "OAuth を開始をクリックすると、ブラウザでプロバイダーの認可ページが開きます。承認後、プロバイダーが表示したコードをコピーし、次に表示されるターミナルに貼り付けてください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クリックして詳細" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Clicar em Iniciar OAuth abre a página de autorização do provedor no navegador. Após aprovar, copie o código exibido pelo provedor e cole no terminal que aparecerá em seguida." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clique para ver detalhes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "点击开始 OAuth 会在浏览器中打开提供方的授权页面。批准后,复制提供方显示的代码并粘贴到接下来出现的终端中。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "点击查看详情" } } } }, - "Clone config, .env, SOUL.md from active profile": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "config, .env, SOUL.md aus aktivem Profil klonen" + "Clicking Start OAuth opens the provider's authorization page in your browser. After you approve, copy the code the provider displays and paste it back into the terminal that appears next." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ein Klick auf OAuth starten öffnet die Autorisierungsseite des Anbieters im Browser. Nach der Genehmigung kopierst du den angezeigten Code und fügst ihn in das erscheinende Terminal ein." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Clonar config, .env, SOUL.md del perfil activo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Al hacer clic en Iniciar OAuth se abre la página de autorización del proveedor en tu navegador. Tras aprobar, copia el código mostrado y pégalo en el terminal que aparecerá a continuación." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cloner config, .env, SOUL.md depuis le profil actif" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cliquer sur Démarrer OAuth ouvre la page d'autorisation du fournisseur dans votre navigateur. Après approbation, copiez le code affiché par le fournisseur et collez-le dans le terminal qui apparaît ensuite." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティブなプロファイルから config、.env、SOUL.md を複製" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "OAuth を開始をクリックすると、ブラウザでプロバイダーの認可ページが開きます。承認後、プロバイダーが表示したコードをコピーし、次に表示されるターミナルに貼り付けてください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Clonar config, .env, SOUL.md do perfil ativo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clicar em Iniciar OAuth abre a página de autorização do provedor no navegador. Após aprovar, copie o código exibido pelo provedor e cole no terminal que aparecerá em seguida." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从当前配置克隆 config、.env、SOUL.md" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "点击开始 OAuth 会在浏览器中打开提供方的授权页面。批准后,复制提供方显示的代码并粘贴到接下来出现的终端中。" } } } }, - "Close": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Schließen" + "Clone config, .env, SOUL.md from active profile" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "config, .env, SOUL.md aus aktivem Profil klonen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cerrar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clonar config, .env, SOUL.md del perfil activo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Fermer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cloner config, .env, SOUL.md depuis le profil actif" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "閉じる" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティブなプロファイルから config、.env、SOUL.md を複製" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Fechar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clonar config, .env, SOUL.md do perfil ativo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "关闭" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从当前配置克隆 config、.env、SOUL.md" } } } }, - "Close Window": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Fenster schließen" + "Close" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schließen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cerrar ventana" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cerrar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Fermer la fenêtre" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fermer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ウィンドウを閉じる" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "閉じる" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Fechar janela" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fechar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "关闭窗口" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭" } } } }, - "Code: %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Code: %@" + "Close Window" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fenster schließen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Código: %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cerrar ventana" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Code : %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fermer la fenêtre" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コード: %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ウィンドウを閉じる" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Código: %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fechar janela" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "代码:%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭窗口" } } } }, - "Command": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Befehl" + "Code: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Code: %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comando" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Código: %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Commande" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Code : %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コマンド" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "コード: %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Comando" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Código: %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "代码:%@" } } } }, - "Command Allowlist": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Befehls-Allowlist" + "Command" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Befehl" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Lista de comandos permitidos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comando" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Liste d'autorisations de commandes" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Commande" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コマンド許可リスト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "コマンド" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Lista de comandos permitidos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comando" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "命令白名单" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "命令" } } } }, - "Command looks destructive. Double-check before saving.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Der Befehl wirkt destruktiv. Vor dem Speichern noch einmal prüfen." + "Command Allowlist" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Befehls-Allowlist" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "El comando parece destructivo. Revísalo antes de guardar." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lista de comandos permitidos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "La commande semble destructive. Vérifiez avant d'enregistrer." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Liste d'autorisations de commandes" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コマンドが破壊的に見えます。保存前に再確認してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "コマンド許可リスト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O comando parece destrutivo. Revise antes de salvar." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lista de comandos permitidos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "该命令看起来具有破坏性。保存前请仔细确认。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "命令白名单" } } } }, - "Component": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Komponente" + "Command looks destructive. Double-check before saving." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Der Befehl wirkt destruktiv. Vor dem Speichern noch einmal prüfen." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Componente" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "El comando parece destructivo. Revísalo antes de guardar." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Composant" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "La commande semble destructive. Vérifiez avant d'enregistrer." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コンポーネント" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "コマンドが破壊的に見えます。保存前に再確認してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Componente" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O comando parece destrutivo. Revise antes de salvar." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "组件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "该命令看起来具有破坏性。保存前请仔细确认。" } } } }, - "Compress": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Komprimieren" + "Component" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Komponente" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comprimir" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Componente" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Compresser" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Composant" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "圧縮" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "コンポーネント" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Compactar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Componente" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "压缩" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "组件" } } } }, - "Compress Conversation": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Unterhaltung komprimieren" + "Compress" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Komprimieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comprimir conversación" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comprimir" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Compresser la conversation" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compresser" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "会話を圧縮" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "圧縮" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Compactar conversa" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compactar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "压缩对话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "压缩" } } } }, - "Compress conversation (/compress)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Unterhaltung komprimieren (/compress)" + "Compress Conversation" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unterhaltung komprimieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comprimir conversación (/compress)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comprimir conversación" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Compresser la conversation (/compress)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compresser la conversation" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "会話を圧縮 (/compress)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "会話を圧縮" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Compactar conversa (/compress)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compactar conversa" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "压缩对话 (/compress)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "压缩对话" } } } }, - "Compression": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Komprimierung" + "Compress conversation (/compress)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unterhaltung komprimieren (/compress)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Compresión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comprimir conversación (/compress)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Compression" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compresser la conversation (/compress)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "圧縮" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "会話を圧縮 (/compress)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Compactação" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compactar conversa (/compress)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "压缩" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "压缩对话 (/compress)" } } } }, - "Config Diagnostics": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Konfigurations-Diagnose" + "Compression" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Komprimierung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Diagnóstico de configuración" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compresión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Diagnostics de configuration" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compression" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "設定診断" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "圧縮" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Diagnóstico de configuração" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compactação" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "配置诊断" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "压缩" } } } }, - "Configure": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Konfigurieren" + "Config Diagnostics" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfigurations-Diagnose" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Configurar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnóstico de configuración" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Configurer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnostics de configuration" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "設定" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "設定診断" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Configurar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnóstico de configuração" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置诊断" } } } }, - "Connect timeout": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Verbindungs-Timeout" + "Configuration saved" : { + "comment" : "A title displayed when a configuration is saved.", + "isCommentAutoGenerated" : true + }, + "Configuration…" : { + "comment" : "A contextual menu item that opens a configuration editor for a project.", + "isCommentAutoGenerated" : true + }, + "Configure" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Konfigurieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Tiempo de espera de conexión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configurar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Délai de connexion" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configurer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "接続タイムアウト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "設定" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Tempo limite de conexão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configurar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "连接超时" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置" } } } }, - "Connected": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Verbunden" + "Configure %@" : { + "comment" : "The title of the configuration sheet. The argument is the name of the template.", + "isCommentAutoGenerated" : true + }, + "Connect timeout" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbindungs-Timeout" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Conectado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tiempo de espera de conexión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Connecté" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Délai de connexion" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "接続済み" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "接続タイムアウト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Conectado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tempo limite de conexão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已连接" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "连接超时" } } } }, - "Connected — can't read Hermes state": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Verbunden — Hermes-Status nicht lesbar" + "Connected" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbunden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Conectado — no se puede leer el estado de Hermes" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Conectado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Connecté — impossible de lire l'état de Hermes" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connecté" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "接続済み — Hermes 状態を読み取れません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "接続済み" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Conectado — não é possível ler o estado do Hermes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Conectado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已连接 — 无法读取 Hermes 状态" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已连接" } } } }, - "Connection": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Verbindung" + "Connected — can't read Hermes state" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbunden — Hermes-Status nicht lesbar" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Conexión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Conectado — no se puede leer el estado de Hermes" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Connexion" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connecté — impossible de lire l'état de Hermes" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "接続" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "接続済み — Hermes 状態を読み取れません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Conexão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Conectado — não é possível ler o estado do Hermes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "连接" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已连接 — 无法读取 Hermes 状态" } } } }, - "Container Limits": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Container-Limits" + "Connection" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbindung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Límites del contenedor" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Conexión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Limites du conteneur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connexion" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コンテナ制限" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "接続" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Limites do contêiner" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Conexão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "容器限制" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "连接" } } } }, - "Context & Compression": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Kontext & Komprimierung" + "Container Limits" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Container-Limits" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Contexto y compresión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Límites del contenedor" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Contexte et compression" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limites du conteneur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コンテキストと圧縮" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "コンテナ制限" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Contexto e compactação" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limites do contêiner" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "上下文与压缩" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "容器限制" } } } }, - "Continue Last Session": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Letzte Sitzung fortsetzen" + "Context & Compression" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kontext & Komprimierung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Continuar última sesión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contexto y compresión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Continuer la dernière session" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contexte et compression" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "前回のセッションを続ける" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "コンテキストと圧縮" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Continuar última sessão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Contexto e compactação" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "继续上次会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "上下文与压缩" } } } }, - "Copied": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Kopiert" + "Continue" : { + "comment" : "Button label for continuing with the template configuration.", + "isCommentAutoGenerated" : true + }, + "Continue Last Session" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Letzte Sitzung fortsetzen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Copiado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continuar última sesión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Copié" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continuer la dernière session" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コピー済み" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "前回のセッションを続ける" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Copiado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continuar última sessão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已复制" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "继续上次会话" } } } }, - "Copy": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Kopieren" + "Copied" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kopiert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Copiar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Copier" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copié" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コピー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "コピー済み" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Copiar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "复制" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已复制" } } } }, - "Copy Full Report": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Vollständigen Bericht kopieren" + "Copy" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kopieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Copiar informe completo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Copier le rapport complet" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copier" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "完全なレポートをコピー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "コピー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Copiar relatório completo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "复制完整报告" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制" } } } }, - "Copy a plain-text summary of every check (passes and fails) — paste into GitHub issues so we can see everything at once.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Kopiert eine Klartextzusammenfassung jeder Prüfung (bestanden und fehlgeschlagen) — in GitHub-Issues einfügen, damit wir alles auf einmal sehen." + "Copy a plain-text summary of every check (passes and fails) — paste into GitHub issues so we can see everything at once." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kopiert eine Klartextzusammenfassung jeder Prüfung (bestanden und fehlgeschlagen) — in GitHub-Issues einfügen, damit wir alles auf einmal sehen." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Copia un resumen en texto plano de cada comprobación (éxitos y fallos) — pégalo en issues de GitHub para que veamos todo a la vez." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copia un resumen en texto plano de cada comprobación (éxitos y fallos) — pégalo en issues de GitHub para que veamos todo a la vez." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Copie un résumé en texte brut de chaque vérification (succès et échecs) — à coller dans les issues GitHub pour tout voir d'un coup." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copie un résumé en texte brut de chaque vérification (succès et échecs) — à coller dans les issues GitHub pour tout voir d'un coup." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "各チェック(成功・失敗)の概要をプレーンテキストでコピーします — GitHub issue に貼り付けて一度に確認できるようにします。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "各チェック(成功・失敗)の概要をプレーンテキストでコピーします — GitHub issue に貼り付けて一度に確認できるようにします。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Copia um resumo em texto simples de cada verificação (aprovadas e falhas) — cole em issues do GitHub para vermos tudo de uma vez." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copia um resumo em texto simples de cada verificação (aprovadas e falhas) — cole em issues do GitHub para vermos tudo de uma vez." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "复制每项检查(通过和失败)的纯文本摘要 — 粘贴到 GitHub issue 中以便我们一次查看所有内容。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制每项检查(通过和失败)的纯文本摘要 — 粘贴到 GitHub issue 中以便我们一次查看所有内容。" } } } }, - "Copy code": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Code kopieren" + "Copy code" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Code kopieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Copiar código" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiar código" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Copier le code" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copier le code" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "コードをコピー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "コードをコピー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Copiar código" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiar código" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "复制代码" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制代码" } } } }, - "Copy error details": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Fehlerdetails kopieren" + "Copy error details" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fehlerdetails kopieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Copiar detalles de error" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiar detalles de error" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Copier les détails de l'erreur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copier les détails de l'erreur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エラー詳細をコピー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "エラー詳細をコピー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Copiar detalhes do erro" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiar detalhes do erro" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "复制错误详情" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制错误详情" } } } }, - "Create": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erstellen" + "Copy Full Report" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vollständigen Bericht kopieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Crear" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiar informe completo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Créer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copier le rapport complet" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "作成" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "完全なレポートをコピー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Criar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copiar relatório completo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "创建" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "复制完整报告" } } } }, - "Create Profile": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Profil erstellen" + "Couldn't save" : { + "comment" : "A title displayed when a configuration save fails.", + "isCommentAutoGenerated" : true + }, + "Create" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erstellen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Crear perfil" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crear" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Créer un profil" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロファイルを作成" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "作成" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Criar perfil" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Criar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "创建配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "创建" } } } }, - "Create Subscription": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Abonnement erstellen" + "Create a bot via @BotFather and get your numeric user ID from @userinfobot. Paste the token and your user ID below — the bot will only respond to allowed users." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erstelle einen Bot via @BotFather und hole dir deine numerische User-ID von @userinfobot. Füge Token und User-ID unten ein — der Bot antwortet nur erlaubten Nutzern." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Crear suscripción" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crea un bot con @BotFather y obtén tu ID numérico en @userinfobot. Pega el token y tu ID de usuario abajo — el bot solo responderá a usuarios permitidos." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Créer un abonnement" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créez un bot via @BotFather et obtenez votre ID numérique via @userinfobot. Collez le jeton et votre ID ci-dessous — le bot ne répondra qu'aux utilisateurs autorisés." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サブスクリプションを作成" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "@BotFather でボットを作成し、@userinfobot から数値ユーザー ID を取得してください。トークンとユーザー ID を下に貼り付けてください — ボットは許可されたユーザーにのみ応答します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Criar assinatura" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crie um bot via @BotFather e obtenha seu ID numérico em @userinfobot. Cole o token e seu ID de usuário abaixo — o bot só responde a usuários permitidos." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "创建订阅" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过 @BotFather 创建机器人,并从 @userinfobot 获取你的数字用户 ID。将令牌和用户 ID 粘贴到下方 — 机器人只会响应允许的用户。" } } } }, - "Create a Slack app at api.slack.com/apps, enable Socket Mode, grant bot scopes (chat:write, app_mentions:read, channels:history, etc.), then copy the Bot User OAuth Token (xoxb-) and the App-Level Token (xapp-).": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erstelle eine Slack-App auf api.slack.com/apps, aktiviere Socket Mode, vergib Bot-Scopes (chat:write, app_mentions:read, channels:history usw.) und kopiere dann das Bot User OAuth Token (xoxb-) sowie das App-Level Token (xapp-)." + "Create a long-lived access token in Home Assistant (Profile → Security → Long-Lived Access Tokens). By default, no events are forwarded — enable Watch All Changes, or add entity filters below." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erstelle ein Long-Lived Access Token in Home Assistant (Profil → Sicherheit → Long-Lived Access Tokens). Standardmäßig werden keine Ereignisse weitergeleitet — aktiviere Watch All Changes oder füge unten Entity-Filter hinzu." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Crea una app de Slack en api.slack.com/apps, activa Socket Mode, concede los scopes de bot (chat:write, app_mentions:read, channels:history, etc.) y copia el Bot User OAuth Token (xoxb-) y el App-Level Token (xapp-)." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crea un token de acceso de larga duración en Home Assistant (Perfil → Seguridad → Long-Lived Access Tokens). Por defecto no se reenvían eventos — activa Watch All Changes o añade filtros de entidades abajo." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Créez une application Slack sur api.slack.com/apps, activez Socket Mode, accordez les scopes bot (chat:write, app_mentions:read, channels:history, etc.), puis copiez le Bot User OAuth Token (xoxb-) et l'App-Level Token (xapp-)." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créez un jeton d'accès à longue durée dans Home Assistant (Profil → Sécurité → Long-Lived Access Tokens). Par défaut, aucun événement n'est transféré — activez Watch All Changes ou ajoutez des filtres d'entités ci-dessous." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "api.slack.com/apps で Slack アプリを作成し、Socket Mode を有効にし、bot スコープ(chat:write、app_mentions:read、channels:history など)を付与してから Bot User OAuth Token(xoxb-)と App-Level Token(xapp-)をコピーしてください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Home Assistant で長期アクセストークンを作成してください(プロファイル → セキュリティ → Long-Lived Access Tokens)。デフォルトではイベントは転送されません — Watch All Changes を有効にするか、下でエンティティフィルタを追加してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Crie um app Slack em api.slack.com/apps, habilite o Socket Mode, conceda os escopos de bot (chat:write, app_mentions:read, channels:history etc.) e copie o Bot User OAuth Token (xoxb-) e o App-Level Token (xapp-)." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crie um token de acesso de longa duração no Home Assistant (Perfil → Segurança → Long-Lived Access Tokens). Por padrão, nenhum evento é encaminhado — ative Watch All Changes ou adicione filtros de entidades abaixo." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在 api.slack.com/apps 创建 Slack 应用,启用 Socket Mode,授予 bot 权限(chat:write、app_mentions:read、channels:history 等),然后复制 Bot User OAuth Token(xoxb-)和 App-Level Token(xapp-)。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 Home Assistant 中创建长期访问令牌(配置 → 安全 → 长期访问令牌)。默认不转发任何事件 — 启用 Watch All Changes 或在下方添加实体过滤器。" } } } }, - "Create a bot via @BotFather and get your numeric user ID from @userinfobot. Paste the token and your user ID below — the bot will only respond to allowed users.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erstelle einen Bot via @BotFather und hole dir deine numerische User-ID von @userinfobot. Füge Token und User-ID unten ein — der Bot antwortet nur erlaubten Nutzern." + "Create a personal access token under Profile → Security → Personal Access Tokens, or create a bot account. Use the token as the MATTERMOST_TOKEN value." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erstelle ein persönliches Access Token unter Profil → Sicherheit → Personal Access Tokens oder ein Bot-Konto. Verwende das Token als MATTERMOST_TOKEN-Wert." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Crea un bot con @BotFather y obtén tu ID numérico en @userinfobot. Pega el token y tu ID de usuario abajo — el bot solo responderá a usuarios permitidos." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crea un token de acceso personal en Perfil → Seguridad → Personal Access Tokens, o crea una cuenta de bot. Usa el token como valor de MATTERMOST_TOKEN." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Créez un bot via @BotFather et obtenez votre ID numérique via @userinfobot. Collez le jeton et votre ID ci-dessous — le bot ne répondra qu'aux utilisateurs autorisés." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créez un jeton d'accès personnel sous Profil → Sécurité → Personal Access Tokens, ou créez un compte bot. Utilisez le jeton comme valeur de MATTERMOST_TOKEN." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "@BotFather でボットを作成し、@userinfobot から数値ユーザー ID を取得してください。トークンとユーザー ID を下に貼り付けてください — ボットは許可されたユーザーにのみ応答します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロファイル → セキュリティ → Personal Access Tokens でパーソナルアクセストークンを作成するか、ボットアカウントを作成してください。そのトークンを MATTERMOST_TOKEN の値として使用します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Crie um bot via @BotFather e obtenha seu ID numérico em @userinfobot. Cole o token e seu ID de usuário abaixo — o bot só responde a usuários permitidos." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crie um token de acesso pessoal em Perfil → Segurança → Personal Access Tokens ou crie uma conta de bot. Use o token como valor de MATTERMOST_TOKEN." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "通过 @BotFather 创建机器人,并从 @userinfobot 获取你的数字用户 ID。将令牌和用户 ID 粘贴到下方 — 机器人只会响应允许的用户。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 配置 → 安全 → 个人访问令牌 下创建个人访问令牌,或创建机器人账号。将该令牌用作 MATTERMOST_TOKEN 的值。" } } } }, - "Create a long-lived access token in Home Assistant (Profile → Security → Long-Lived Access Tokens). By default, no events are forwarded — enable Watch All Changes, or add entity filters below.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erstelle ein Long-Lived Access Token in Home Assistant (Profil → Sicherheit → Long-Lived Access Tokens). Standardmäßig werden keine Ereignisse weitergeleitet — aktiviere Watch All Changes oder füge unten Entity-Filter hinzu." + "Create a profile to isolate config and skills." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erstelle ein Profil, um Konfiguration und Skills zu isolieren." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Crea un token de acceso de larga duración en Home Assistant (Perfil → Seguridad → Long-Lived Access Tokens). Por defecto no se reenvían eventos — activa Watch All Changes o añade filtros de entidades abajo." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crea un perfil para aislar configuración y habilidades." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Créez un jeton d'accès à longue durée dans Home Assistant (Profil → Sécurité → Long-Lived Access Tokens). Par défaut, aucun événement n'est transféré — activez Watch All Changes ou ajoutez des filtres d'entités ci-dessous." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créez un profil pour isoler configuration et compétences." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Home Assistant で長期アクセストークンを作成してください(プロファイル → セキュリティ → Long-Lived Access Tokens)。デフォルトではイベントは転送されません — Watch All Changes を有効にするか、下でエンティティフィルタを追加してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "設定とスキルを分離するプロファイルを作成します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Crie um token de acesso de longa duração no Home Assistant (Perfil → Segurança → Long-Lived Access Tokens). Por padrão, nenhum evento é encaminhado — ative Watch All Changes ou adicione filtros de entidades abaixo." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crie um perfil para isolar configuração e habilidades." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在 Home Assistant 中创建长期访问令牌(配置 → 安全 → 长期访问令牌)。默认不转发任何事件 — 启用 Watch All Changes 或在下方添加实体过滤器。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "创建配置以隔离 config 和技能。" } } } }, - "Create a personal access token under Profile → Security → Personal Access Tokens, or create a bot account. Use the token as the MATTERMOST_TOKEN value.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erstelle ein persönliches Access Token unter Profil → Sicherheit → Personal Access Tokens oder ein Bot-Konto. Verwende das Token als MATTERMOST_TOKEN-Wert." + "Create a Slack app at api.slack.com/apps, enable Socket Mode, grant bot scopes (chat:write, app_mentions:read, channels:history, etc.), then copy the Bot User OAuth Token (xoxb-) and the App-Level Token (xapp-)." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erstelle eine Slack-App auf api.slack.com/apps, aktiviere Socket Mode, vergib Bot-Scopes (chat:write, app_mentions:read, channels:history usw.) und kopiere dann das Bot User OAuth Token (xoxb-) sowie das App-Level Token (xapp-)." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Crea un token de acceso personal en Perfil → Seguridad → Personal Access Tokens, o crea una cuenta de bot. Usa el token como valor de MATTERMOST_TOKEN." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crea una app de Slack en api.slack.com/apps, activa Socket Mode, concede los scopes de bot (chat:write, app_mentions:read, channels:history, etc.) y copia el Bot User OAuth Token (xoxb-) y el App-Level Token (xapp-)." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Créez un jeton d'accès personnel sous Profil → Sécurité → Personal Access Tokens, ou créez un compte bot. Utilisez le jeton comme valeur de MATTERMOST_TOKEN." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créez une application Slack sur api.slack.com/apps, activez Socket Mode, accordez les scopes bot (chat:write, app_mentions:read, channels:history, etc.), puis copiez le Bot User OAuth Token (xoxb-) et l'App-Level Token (xapp-)." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロファイル → セキュリティ → Personal Access Tokens でパーソナルアクセストークンを作成するか、ボットアカウントを作成してください。そのトークンを MATTERMOST_TOKEN の値として使用します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "api.slack.com/apps で Slack アプリを作成し、Socket Mode を有効にし、bot スコープ(chat:write、app_mentions:read、channels:history など)を付与してから Bot User OAuth Token(xoxb-)と App-Level Token(xapp-)をコピーしてください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Crie um token de acesso pessoal em Perfil → Segurança → Personal Access Tokens ou crie uma conta de bot. Use o token como valor de MATTERMOST_TOKEN." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crie um app Slack em api.slack.com/apps, habilite o Socket Mode, conceda os escopos de bot (chat:write, app_mentions:read, channels:history etc.) e copie o Bot User OAuth Token (xoxb-) e o App-Level Token (xapp-)." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在 配置 → 安全 → 个人访问令牌 下创建个人访问令牌,或创建机器人账号。将该令牌用作 MATTERMOST_TOKEN 的值。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 api.slack.com/apps 创建 Slack 应用,启用 Socket Mode,授予 bot 权限(chat:write、app_mentions:read、channels:history 等),然后复制 Bot User OAuth Token(xoxb-)和 App-Level Token(xapp-)。" } } } }, - "Create a profile to isolate config and skills.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erstelle ein Profil, um Konfiguration und Skills zu isolieren." + "Create an app in Discord's Developer Portal, enable Message Content and Server Members intents, and copy the bot token. Invite the bot to your server via the OAuth2 URL generator." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erstelle eine App im Discord Developer Portal, aktiviere die Intents Message Content und Server Members und kopiere das Bot-Token. Lade den Bot über den OAuth2-URL-Generator auf deinen Server ein." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Crea un perfil para aislar configuración y habilidades." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crea una app en el Developer Portal de Discord, activa los intents Message Content y Server Members y copia el token del bot. Invita al bot a tu servidor con el generador de URLs OAuth2." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Créez un profil pour isoler configuration et compétences." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créez une application dans le Developer Portal de Discord, activez les intents Message Content et Server Members, puis copiez le jeton du bot. Invitez le bot sur votre serveur via le générateur d'URL OAuth2." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "設定とスキルを分離するプロファイルを作成します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Discord の Developer Portal でアプリを作成し、Message Content と Server Members の intents を有効にしてから、ボットトークンをコピーしてください。OAuth2 URL ジェネレーターでボットをサーバーに招待します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Crie um perfil para isolar configuração e habilidades." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crie um app no Developer Portal do Discord, ative os intents Message Content e Server Members e copie o token do bot. Convide o bot para o seu servidor via gerador de URL OAuth2." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "创建配置以隔离 config 和技能。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 Discord 开发者门户创建应用,启用 Message Content 和 Server Members intents,然后复制机器人令牌。通过 OAuth2 URL 生成器将机器人邀请到你的服务器。" } } } }, - "Create an app in Discord's Developer Portal, enable Message Content and Server Members intents, and copy the bot token. Invite the bot to your server via the OAuth2 URL generator.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erstelle eine App im Discord Developer Portal, aktiviere die Intents Message Content und Server Members und kopiere das Bot-Token. Lade den Bot über den OAuth2-URL-Generator auf deinen Server ein." + "Create an app in the Feishu/Lark Developer Console, enable Interactive Card if you need button responses, and copy the App ID and App Secret. WebSocket mode (recommended) doesn't need a public endpoint." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erstelle eine App in der Feishu/Lark Developer Console, aktiviere Interactive Card bei Bedarf für Button-Antworten und kopiere App ID und App Secret. Der WebSocket-Modus (empfohlen) braucht keinen öffentlichen Endpunkt." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Crea una app en el Developer Portal de Discord, activa los intents Message Content y Server Members y copia el token del bot. Invita al bot a tu servidor con el generador de URLs OAuth2." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crea una app en la Feishu/Lark Developer Console, activa Interactive Card si necesitas respuestas por botón y copia el App ID y el App Secret. El modo WebSocket (recomendado) no requiere un endpoint público." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Créez une application dans le Developer Portal de Discord, activez les intents Message Content et Server Members, puis copiez le jeton du bot. Invitez le bot sur votre serveur via le générateur d'URL OAuth2." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créez une application dans la Feishu/Lark Developer Console, activez Interactive Card si vous avez besoin de réponses par bouton, et copiez l'App ID et l'App Secret. Le mode WebSocket (recommandé) ne nécessite pas de point d'accès public." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Discord の Developer Portal でアプリを作成し、Message Content と Server Members の intents を有効にしてから、ボットトークンをコピーしてください。OAuth2 URL ジェネレーターでボットをサーバーに招待します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feishu/Lark Developer Console でアプリを作成し、ボタン応答が必要であれば Interactive Card を有効にして、App ID と App Secret をコピーしてください。WebSocket モード(推奨)ではパブリックエンドポイントは不要です。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Crie um app no Developer Portal do Discord, ative os intents Message Content e Server Members e copie o token do bot. Convide o bot para o seu servidor via gerador de URL OAuth2." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crie um app no Feishu/Lark Developer Console, habilite Interactive Card se precisar de respostas por botão e copie o App ID e o App Secret. O modo WebSocket (recomendado) não requer endpoint público." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在 Discord 开发者门户创建应用,启用 Message Content 和 Server Members intents,然后复制机器人令牌。通过 OAuth2 URL 生成器将机器人邀请到你的服务器。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在飞书/Lark 开发者后台创建应用,如需按钮交互请启用 Interactive Card,然后复制 App ID 和 App Secret。推荐使用 WebSocket 模式,无需公网地址。" } } } }, - "Create an app in the Feishu/Lark Developer Console, enable Interactive Card if you need button responses, and copy the App ID and App Secret. WebSocket mode (recommended) doesn't need a public endpoint.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erstelle eine App in der Feishu/Lark Developer Console, aktiviere Interactive Card bei Bedarf für Button-Antworten und kopiere App ID und App Secret. Der WebSocket-Modus (empfohlen) braucht keinen öffentlichen Endpunkt." + "Create Profile" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil erstellen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Crea una app en la Feishu/Lark Developer Console, activa Interactive Card si necesitas respuestas por botón y copia el App ID y el App Secret. El modo WebSocket (recomendado) no requiere un endpoint público." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crear perfil" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Créez une application dans la Feishu/Lark Developer Console, activez Interactive Card si vous avez besoin de réponses par bouton, et copiez l'App ID et l'App Secret. Le mode WebSocket (recommandé) ne nécessite pas de point d'accès public." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créer un profil" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Feishu/Lark Developer Console でアプリを作成し、ボタン応答が必要であれば Interactive Card を有効にして、App ID と App Secret をコピーしてください。WebSocket モード(推奨)ではパブリックエンドポイントは不要です。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロファイルを作成" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Crie um app no Feishu/Lark Developer Console, habilite Interactive Card se precisar de respostas por botão e copie o App ID e o App Secret. O modo WebSocket (recomendado) não requer endpoint público." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Criar perfil" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在飞书/Lark 开发者后台创建应用,如需按钮交互请启用 Interactive Card,然后复制 App ID 和 App Secret。推荐使用 WebSocket 模式,无需公网地址。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "创建配置" } } } }, - "Credential Pools": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Credential-Pools" + "Create Subscription" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abonnement erstellen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Grupos de credenciales" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Crear suscripción" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Pools d'identifiants" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créer un abonnement" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "資格情報プール" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サブスクリプションを作成" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Pools de credenciais" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Criar assinatura" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "凭证池" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "创建订阅" } } } }, - "Credential Type": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Anmeldedaten-Typ" + "Credential Pools" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Credential-Pools" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Tipo de credencial" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Grupos de credenciales" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Type d'identifiants" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pools d'identifiants" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "資格情報の種類" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "資格情報プール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Tipo de credencial" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pools de credenciais" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "凭证类型" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "凭证池" } } } }, - "Credentials": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Anmeldedaten" + "Credential Type" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anmeldedaten-Typ" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Credenciales" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tipo de credencial" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Identifiants" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Type d'identifiants" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "資格情報" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "資格情報の種類" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Credenciais" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tipo de credencial" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "凭证" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "凭证类型" } } } }, - "Cron": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Cron" + "Credentials" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anmeldedaten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cron" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Credenciales" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cron" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Identifiants" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Cron" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "資格情報" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Cron" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Credenciais" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "定时任务" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "凭证" } } } }, - "Cron Jobs": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Cron-Jobs" + "Cron" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cron" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Tareas cron" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cron" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tâches cron" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cron" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Cron ジョブ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cron" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Tarefas cron" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cron" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "定时任务" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "定时任务" } } } }, - "Current: %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktuell: %@" + "Cron jobs" : { + "comment" : "Section title for a list of cron jobs that will be removed.", + "isCommentAutoGenerated" : true + }, + "Cron Jobs" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cron-Jobs" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Actual: %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tareas cron" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Actuel : %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tâches cron" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "現在: %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cron ジョブ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Atual: %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tarefas cron" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "当前:%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "定时任务" } } } }, - "Custom…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Benutzerdefiniert…" + "Current: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktuell: %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Personalizado…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actual: %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Personnalisé…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actuel : %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "カスタム…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "現在: %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Personalizado…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atual: %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "自定义…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当前:%@" } } } }, - "Daemon Endpoint": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Daemon-Endpunkt" + "Custom…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Benutzerdefiniert…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Endpoint del demonio" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalizado…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Endpoint du démon" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personnalisé…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "デーモンエンドポイント" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "カスタム…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Endpoint do daemon" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalizado…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "守护进程端点" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自定义…" } } } }, - "Daemon running": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Daemon läuft" + "Daemon Endpoint" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daemon-Endpunkt" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Demonio en ejecución" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Endpoint del demonio" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Démon en cours d'exécution" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Endpoint du démon" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "デーモン実行中" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "デーモンエンドポイント" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Daemon em execução" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Endpoint do daemon" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "守护进程运行中" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "守护进程端点" } } } }, - "Dashboard": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Dashboard" + "Daemon running" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daemon läuft" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Panel" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Demonio en ejecución" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tableau de bord" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Démon en cours d'exécution" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ダッシュボード" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "デーモン実行中" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Painel" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daemon em execução" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "仪表盘" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "守护进程运行中" } } } }, - "Daytona": {}, - "Default": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Standard" + "dangerous" : { + + }, + "Dashboard" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dashboard" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Predeterminado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Panel" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Par défaut" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tableau de bord" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "デフォルト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ダッシュボード" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Padrão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Painel" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "默认" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "仪表盘" } } } }, - "Default: ~/.hermes": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Standard: ~/.hermes" + "Daytona" : { + + }, + "default" : { + + }, + "Default" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Predeterminado: ~/.hermes" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predeterminado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Par défaut : ~/.hermes" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Par défaut" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "デフォルト: ~/.hermes" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "デフォルト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Padrão: ~/.hermes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Padrão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "默认:~/.hermes" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "默认" } } } }, - "Defaults to ~/.ssh/config or current user": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Standard ist ~/.ssh/config oder der aktuelle Benutzer" + "Default: ~/.hermes" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard: ~/.hermes" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Por defecto ~/.ssh/config o el usuario actual" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predeterminado: ~/.hermes" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Par défaut : ~/.ssh/config ou utilisateur courant" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Par défaut : ~/.hermes" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "デフォルトは ~/.ssh/config または現在のユーザー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "デフォルト: ~/.hermes" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Padrão é ~/.ssh/config ou usuário atual" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Padrão: ~/.hermes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "默认使用 ~/.ssh/config 或当前用户" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "默认:~/.hermes" } } } }, - "Defined Personalities": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Definierte Persönlichkeiten" + "Defaults to ~/.ssh/config or current user" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Standard ist ~/.ssh/config oder der aktuelle Benutzer" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Personalidades definidas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Por defecto ~/.ssh/config o el usuario actual" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Personnalités définies" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Par défaut : ~/.ssh/config ou utilisateur courant" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "定義済みパーソナリティ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "デフォルトは ~/.ssh/config または現在のユーザー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Personalidades definidas" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Padrão é ~/.ssh/config ou usuário atual" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已定义人格" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "默认使用 ~/.ssh/config 或当前用户" } } } }, - "Delegation": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Delegation" + "Defined Personalities" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definierte Persönlichkeiten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Delegación" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalidades definidas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Délégation" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personnalités définies" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "委譲" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "定義済みパーソナリティ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Delegação" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalidades definidas" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "委派" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已定义人格" } } } }, - "Delete": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Löschen" + "Delegation" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delegation" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Eliminar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delegación" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Supprimer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Délégation" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "削除" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "委譲" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Excluir" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delegação" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "删除" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "委派" } } } }, - "Delete %@?": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%@ löschen?" + "Delete" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Löschen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "¿Eliminar %@?" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eliminar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Supprimer %@ ?" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Supprimer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ を削除しますか?" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "削除" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Excluir %@?" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Excluir" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "删除 %@?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除" } } } }, - "Delete Session?": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sitzung löschen?" + "Delete %@ from Finder if you don't need these files anymore." : { + "comment" : "A note that lets the user delete", + "isCommentAutoGenerated" : true + }, + "Delete %@?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ löschen?" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "¿Eliminar sesión?" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Eliminar %@?" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Supprimer la session ?" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Supprimer %@ ?" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッションを削除しますか?" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ を削除しますか?" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Excluir sessão?" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Excluir %@?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "删除会话?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除 %@?" } } } }, - "Delete profile '%@'?": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Profil '%@' löschen?" + "Delete profile '%@'?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil '%@' löschen?" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "¿Eliminar perfil '%@'?" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Eliminar perfil '%@'?" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Supprimer le profil « %@ » ?" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Supprimer le profil « %@ » ?" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロファイル '%@' を削除しますか?" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロファイル '%@' を削除しますか?" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Excluir perfil '%@'?" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Excluir perfil '%@'?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "删除配置 '%@'?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除配置 '%@'?" } } } }, - "Delete...": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Löschen..." + "Delete Session?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sitzung löschen?" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Eliminar..." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Eliminar sesión?" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Supprimer..." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Supprimer la session ?" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "削除..." + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッションを削除しますか?" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Excluir..." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Excluir sessão?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "删除..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除会话?" } } } }, - "Deliver: %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Zustellen: %@" + "Delete..." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Löschen..." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Entregar: %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eliminar..." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Livrer : %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Supprimer..." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "配信: %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "削除..." } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Entregar: %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Excluir..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "投递:%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除..." } } } }, - "Details": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Details" + "Deliver: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zustellen: %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Detalles" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entregar: %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Détails" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Livrer : %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "詳細" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "配信: %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Detalhes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entregar: %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "详情" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "投递:%@" } } } }, - "Diagnostic Output": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Diagnose-Ausgabe" + "Description" : { + + }, + "Details" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Details" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Salida de diagnóstico" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Detalles" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sortie de diagnostic" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Détails" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "診断出力" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "詳細" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Saída de diagnóstico" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Detalhes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "诊断输出" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "详情" } } } }, - "Diagnostics": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Diagnose" + "Diagnostic Output" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnose-Ausgabe" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Diagnósticos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Salida de diagnóstico" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Diagnostics" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sortie de diagnostic" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "診断" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "診断出力" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Diagnóstico" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Saída de diagnóstico" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "诊断" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "诊断输出" } } } }, - "Disable": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Deaktivieren" + "Diagnostics" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnose" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Desactivar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnósticos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Désactiver" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnostics" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "無効化" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "診断" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Desativar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnóstico" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "禁用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "诊断" } } } }, - "Disabled": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Deaktiviert" + "Disable" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deaktivieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Desactivado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desactivar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Désactivé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Désactiver" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "無効" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "無効化" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Desativado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desativar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已禁用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "禁用" } } } }, - "Discord Setup Docs": {}, - "Display": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Anzeige" + "Disabled" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deaktiviert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Pantalla" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desactivado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Affichage" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Désactivé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "無効" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Exibição" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desativado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已禁用" } } } }, - "Docker": {}, - "Docs": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Dokumentation" + "Discord Setup Docs" : { + + }, + "Display" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anzeige" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Docs" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pantalla" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Docs" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Affichage" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ドキュメント" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Docs" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exibição" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "文档" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示" } } } }, - "Done": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Fertig" + "Display Name" : { + + }, + "Docker" : { + + }, + "Docs" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dokumentation" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Listo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Docs" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Terminé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Docs" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "完了" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ドキュメント" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Concluído" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Docs" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "完成" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "文档" } } } }, - "Edit": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Bearbeiten" + "Done" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fertig" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Editar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Listo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Modifier" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terminé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "編集" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "完了" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Editar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Concluído" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "完成" } } } }, - "Edit %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%@ bearbeiten" + "Downloading from %@…" : { + "comment" : "A description of an action that is in progress. The argument is the source of the action.", + "isCommentAutoGenerated" : true + }, + "e.g. anthropic" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "z. B. anthropic" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Editar %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "p. ej. anthropic" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Modifier %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "par ex. anthropic" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ を編集" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "例: anthropic" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Editar %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ex: anthropic" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑 %@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "例如 anthropic" } } } }, - "Edit /%@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "/%@ bearbeiten" + "e.g. deploy" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "z. B. deploy" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Editar /%@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "p. ej. deploy" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Modifier /%@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "par ex. deploy" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "/%@ を編集" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "例: deploy" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Editar /%@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ex: deploy" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑 /%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "例如 deploy" } } } }, - "Edit Agent Memory": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Agent-Speicher bearbeiten" + "e.g. experimental" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "z. B. experimental" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Editar memoria del agente" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "p. ej. experimental" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Modifier la mémoire de l'agent" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "par ex. experimental" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エージェントメモリを編集" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "例: experimental" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Editar memória do agente" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ex: experimental" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑 Agent 记忆" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "例如 experimental" } } } }, - "Edit User Profile": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nutzerprofil bearbeiten" + "e.g. github" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "z. B. github" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Editar perfil de usuario" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "p. ej. github" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Modifier le profil utilisateur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "par ex. github" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ユーザープロファイルを編集" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "例: github" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Editar perfil de usuário" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ex: github" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑用户配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "例如 github" } } } }, - "Edit config.yaml": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "config.yaml bearbeiten" + "e.g. openai" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "z. B. openai" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Editar config.yaml" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "p. ej. openai" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Modifier config.yaml" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "par ex. openai" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "config.yaml を編集" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "例: openai" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Editar config.yaml" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ex: openai" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "编辑 config.yaml" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "例如 openai" } } } }, - "Email Setup Docs": {}, - "Empty": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Leer" + "e.g. openai/gpt-4o" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "z. B. openai/gpt-4o" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Vacío" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "p. ej. openai/gpt-4o" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Vide" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "par ex. openai/gpt-4o" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "空" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "例: openai/gpt-4o" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Vazio" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ex: openai/gpt-4o" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "空" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "例如 openai/gpt-4o" } } } }, - "Enable": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktivieren" + "e.g. productivity" : { + "comment" : "A description of what a template's category should look like.", + "isCommentAutoGenerated" : true + }, + "e.g. team-prod" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "z. B. team-prod" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Activar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "p. ej. team-prod" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Activer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "par ex. team-prod" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "有効化" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "例: team-prod" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ativar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ex: team-prod" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "启用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "例如 team-prod" } } } }, - "Enable 2FA on your email account and generate an app password. Regular account passwords will fail. Always set allowed senders — otherwise anyone knowing the address can message the agent.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktiviere 2FA für dein E-Mail-Konto und erzeuge ein App-Passwort. Normale Kontopasswörter funktionieren nicht. Setze immer erlaubte Absender — sonst kann jeder, der die Adresse kennt, dem Agent Nachrichten schicken." + "Edit" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bearbeiten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Activa 2FA en tu cuenta de correo y genera una contraseña de aplicación. Las contraseñas normales no funcionarán. Establece siempre remitentes permitidos — de lo contrario, cualquiera que conozca la dirección podrá enviar mensajes al agente." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Activez la 2FA sur votre compte email et générez un mot de passe d'application. Les mots de passe de compte classiques ne fonctionneront pas. Définissez toujours les expéditeurs autorisés — sinon toute personne connaissant l'adresse pourra envoyer des messages à l'agent." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modifier" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "メールアカウントで 2FA を有効にし、アプリパスワードを生成してください。通常のアカウントパスワードは使用できません。許可された送信者を必ず設定してください — そうしないと、アドレスを知っている誰もがエージェントにメッセージを送れます。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "編集" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ative o 2FA na sua conta de e-mail e gere uma senha de aplicativo. Senhas normais da conta não funcionam. Sempre defina remetentes permitidos — caso contrário, qualquer um que saiba o endereço pode enviar mensagens para o agente." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "为你的邮箱账户启用双重验证并生成应用专用密码。普通账户密码将无法使用。务必设置允许的发件人 — 否则任何知道邮件地址的人都可以向 agent 发消息。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑" } } } }, - "Enable the webhook platform to accept event-driven agent triggers. The HMAC secret is used as a fallback when individual routes don't provide their own.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktiviere die Webhook-Plattform, um ereignisgesteuerte Agent-Trigger zu akzeptieren. Das HMAC-Secret dient als Fallback, wenn einzelne Routen keines liefern." + "Edit /%@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "/%@ bearbeiten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Activa la plataforma de webhooks para aceptar disparadores de agente dirigidos por eventos. El secreto HMAC se usa como respaldo cuando las rutas individuales no aportan el suyo." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar /%@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Activez la plateforme webhook pour accepter les déclencheurs d'agent pilotés par événements. Le secret HMAC est utilisé en repli quand les routes individuelles n'en fournissent pas." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modifier /%@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "webhook プラットフォームを有効化してイベント駆動のエージェントトリガーを受け付けます。個別のルートが独自のものを提供しない場合、HMAC シークレットがフォールバックとして使用されます。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "/%@ を編集" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ative a plataforma de webhook para aceitar gatilhos de agente orientados a eventos. O segredo HMAC é usado como fallback quando rotas individuais não fornecem o próprio." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar /%@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "启用 webhook 平台以接受事件驱动的 agent 触发。当单独的路由未提供自己的密钥时,使用 HMAC 密钥作为回退。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑 /%@" } } } }, - "Enabled": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktiviert" + "Edit %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ bearbeiten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Activado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Activé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modifier %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "有効" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ を編集" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ativado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已启用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑 %@" } } } }, - "End-to-End Encryption (experimental)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ende-zu-Ende-Verschlüsselung (experimentell)" + "Edit Agent Memory" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agent-Speicher bearbeiten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cifrado de extremo a extremo (experimental)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar memoria del agente" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Chiffrement de bout en bout (expérimental)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modifier la mémoire de l'agent" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エンドツーエンド暗号化(実験的)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "エージェントメモリを編集" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Criptografia ponta a ponta (experimental)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar memória do agente" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "端到端加密(实验性)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑 Agent 记忆" } } } }, - "Entity Filters (config.yaml only)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Entity-Filter (nur config.yaml)" + "Edit config.yaml" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "config.yaml bearbeiten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Filtros de entidades (solo config.yaml)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar config.yaml" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Filtres d'entités (config.yaml uniquement)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modifier config.yaml" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エンティティフィルタ(config.yaml のみ)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "config.yaml を編集" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Filtros de entidade (somente config.yaml)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar config.yaml" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "实体过滤器(仅限 config.yaml)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑 config.yaml" } } } }, - "Env vars, headers, and tool filters can be edited after the server is added.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Umgebungsvariablen, Header und Tool-Filter können nach dem Hinzufügen des Servers bearbeitet werden." + "Edit configuration" : { + "comment" : "A button that opens a configuration editor for a project.", + "isCommentAutoGenerated" : true + }, + "Edit User Profile" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nutzerprofil bearbeiten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Las variables de entorno, cabeceras y filtros de herramientas se pueden editar después de añadir el servidor." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar perfil de usuario" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les variables d'environnement, en-têtes et filtres d'outils peuvent être modifiés après l'ajout du serveur." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modifier le profil utilisateur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "環境変数、ヘッダー、ツールフィルタはサーバー追加後に編集できます。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ユーザープロファイルを編集" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Variáveis de ambiente, cabeçalhos e filtros de ferramentas podem ser editados após o servidor ser adicionado." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Editar perfil de usuário" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "添加服务器后可以编辑环境变量、请求头和工具过滤器。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "编辑用户配置" } } } }, - "Environment Variables": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Umgebungsvariablen" + "Email Setup Docs" : { + + }, + "Empty" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Leer" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Variables de entorno" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vacío" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Variables d'environnement" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vide" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "環境変数" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "空" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Variáveis de ambiente" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vazio" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "环境变量" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "空" } } } }, - "Error": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Fehler" + "Enable" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktivieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Error" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Erreur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エラー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "有効化" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Erro" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ativar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "错误" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用" } } } }, - "Errors": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Fehler" + "Enable 2FA on your email account and generate an app password. Regular account passwords will fail. Always set allowed senders — otherwise anyone knowing the address can message the agent." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktiviere 2FA für dein E-Mail-Konto und erzeuge ein App-Passwort. Normale Kontopasswörter funktionieren nicht. Setze immer erlaubte Absender — sonst kann jeder, der die Adresse kennt, dem Agent Nachrichten schicken." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Errores" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activa 2FA en tu cuenta de correo y genera una contraseña de aplicación. Las contraseñas normales no funcionarán. Establece siempre remitentes permitidos — de lo contrario, cualquiera que conozca la dirección podrá enviar mensajes al agente." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Erreurs" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activez la 2FA sur votre compte email et générez un mot de passe d'application. Les mots de passe de compte classiques ne fonctionneront pas. Définissez toujours les expéditeurs autorisés — sinon toute personne connaissant l'adresse pourra envoyer des messages à l'agent." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エラー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "メールアカウントで 2FA を有効にし、アプリパスワードを生成してください。通常のアカウントパスワードは使用できません。許可された送信者を必ず設定してください — そうしないと、アドレスを知っている誰もがエージェントにメッセージを送れます。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Erros" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ative o 2FA na sua conta de e-mail e gere uma senha de aplicativo. Senhas normais da conta não funcionam. Sempre defina remetentes permitidos — caso contrário, qualquer um que saiba o endereço pode enviar mensagens para o agente." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "错误" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "为你的邮箱账户启用双重验证并生成应用专用密码。普通账户密码将无法使用。务必设置允许的发件人 — 否则任何知道邮件地址的人都可以向 agent 发消息。" } } } }, - "Event Filters": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ereignisfilter" + "Enable the webhook platform to accept event-driven agent triggers. The HMAC secret is used as a fallback when individual routes don't provide their own." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktiviere die Webhook-Plattform, um ereignisgesteuerte Agent-Trigger zu akzeptieren. Das HMAC-Secret dient als Fallback, wenn einzelne Routen keines liefern." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Filtros de eventos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activa la plataforma de webhooks para aceptar disparadores de agente dirigidos por eventos. El secreto HMAC se usa como respaldo cuando las rutas individuales no aportan el suyo." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Filtres d'événements" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activez la plateforme webhook pour accepter les déclencheurs d'agent pilotés par événements. Le secret HMAC est utilisé en repli quand les routes individuelles n'en fournissent pas." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "イベントフィルタ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "webhook プラットフォームを有効化してイベント駆動のエージェントトリガーを受け付けます。個別のルートが独自のものを提供しない場合、HMAC シークレットがフォールバックとして使用されます。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Filtros de eventos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ative a plataforma de webhook para aceitar gatilhos de agente orientados a eventos. O segredo HMAC é usado como fallback quando rotas individuais não fornecem o próprio." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "事件过滤器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用 webhook 平台以接受事件驱动的 agent 触发。当单独的路由未提供自己的密钥时,使用 HMAC 密钥作为回退。" } } } }, - "Exclude": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ausschließen" + "Enabled" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktiviert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Excluir" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Exclure" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "除外" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "有効" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Excluir" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ativado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "排除" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已启用" } } } }, - "Execute": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ausführen" + "End-to-End Encryption (experimental)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ende-zu-Ende-Verschlüsselung (experimentell)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ejecutar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cifrado de extremo a extremo (experimental)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Exécuter" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chiffrement de bout en bout (expérimental)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "実行" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "エンドツーエンド暗号化(実験的)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Executar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Criptografia ponta a ponta (experimental)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "执行" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "端到端加密(实验性)" } } } }, - "Expected at %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erwartet unter %@" + "Entity Filters (config.yaml only)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entity-Filter (nur config.yaml)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esperado en %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtros de entidades (solo config.yaml)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Attendu à %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtres d'entités (config.yaml uniquement)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ に期待" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "エンティティフィルタ(config.yaml のみ)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Esperado em %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtros de entidade (somente config.yaml)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "预期位于 %@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "实体过滤器(仅限 config.yaml)" } } } }, - "Export All": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Alle exportieren" + "Env vars, headers, and tool filters can be edited after the server is added." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Umgebungsvariablen, Header und Tool-Filter können nach dem Hinzufügen des Servers bearbeitet werden." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Exportar todo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Las variables de entorno, cabeceras y filtros de herramientas se pueden editar después de añadir el servidor." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tout exporter" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les variables d'environnement, en-têtes et filtres d'outils peuvent être modifiés après l'ajout du serveur." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "すべてエクスポート" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "環境変数、ヘッダー、ツールフィルタはサーバー追加後に編集できます。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Exportar tudo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Variáveis de ambiente, cabeçalhos e filtros de ferramentas podem ser editados após o servidor ser adicionado." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "全部导出" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "添加服务器后可以编辑环境变量、请求头和工具过滤器。" } } } }, - "Export...": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Exportieren..." + "Environment Variables" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Umgebungsvariablen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Exportar..." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Variables de entorno" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Exporter..." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Variables d'environnement" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エクスポート..." + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "環境変数" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Exportar..." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Variáveis de ambiente" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导出..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "环境变量" } } } }, - "Export…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Exportieren…" + "Error" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fehler" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Exportar…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Exporter…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erreur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エクスポート…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "エラー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Exportar…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erro" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导出…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "错误" } } } }, - "Expose prompts": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Prompts verfügbar machen" + "Errors" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fehler" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Exponer prompts" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Errores" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Exposer les prompts" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erreurs" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロンプトを公開" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "エラー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Expor prompts" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erros" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "暴露提示" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "错误" } } } }, - "Expose resources": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ressourcen verfügbar machen" + "Event Filters" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ereignisfilter" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Exponer recursos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtros de eventos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Exposer les ressources" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtres d'événements" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リソースを公開" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "イベントフィルタ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Expor recursos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtros de eventos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "暴露资源" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "事件过滤器" } } } }, - "External Provider": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Externer Anbieter" + "Exclude" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausschließen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Proveedor externo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Excluir" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Fournisseur externe" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exclure" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "外部プロバイダー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "除外" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Provedor externo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Excluir" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "外部提供方" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "排除" } } } }, - "Feedback": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Feedback" + "Execute" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausführen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comentarios" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ejecutar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Retour" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exécuter" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "フィードバック" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "実行" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Feedback" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Executar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "反馈" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "执行" } } } }, - "Feishu Setup Docs": {}, - "Fetch": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Abrufen" + "exit code: %d" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exit-Code: %d" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Obtener" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "código de salida: %d" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Récupérer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "code de sortie : %d" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "取得" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "終了コード: %d" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Buscar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "código de saída: %d" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "拉取" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "退出码:%d" } } } }, - "Files": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Dateien" + "Expected at %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erwartet unter %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Archivos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esperado en %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Fichiers" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Attendu à %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ファイル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ に期待" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Arquivos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esperado em %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "文件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "预期位于 %@" } } } }, - "Filter logs...": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Logs filtern..." + "Export \"%@\" as Template" : { + "comment" : "A title for the export form, showing the name of the project being exported.", + "isCommentAutoGenerated" : true + }, + "Export \"%@\" as Template…" : { + "comment" : "A button that exports a project as a Template. The argument is the name of the project.", + "isCommentAutoGenerated" : true + }, + "Export All" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle exportieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Filtrar registros..." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportar todo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Filtrer les journaux..." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tout exporter" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ログをフィルタ..." + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すべてエクスポート" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Filtrar logs..." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportar tudo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "筛选日志..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "全部导出" } } } }, - "Filter servers...": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Server filtern..." + "Export as Template…" : { + "comment" : "A button that exports a template.", + "isCommentAutoGenerated" : true + }, + "Export Failed" : { + "comment" : "A title for the error screen when exporting a template fails.", + "isCommentAutoGenerated" : true + }, + "Export..." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportieren..." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Filtrar servidores..." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportar..." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Filtrer les serveurs..." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exporter..." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サーバーをフィルタ..." + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "エクスポート..." } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Filtrar servidores..." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportar..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "筛选服务器..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导出..." } } } }, - "Filter skills...": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Skills filtern..." + "Export…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportieren…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Filtrar habilidades..." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportar…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Filtrer les compétences..." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exporter…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "スキルをフィルタ..." + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "エクスポート…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Filtrar habilidades..." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportar…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "筛选技能..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导出…" } } } }, - "Filter to session %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Auf Sitzung %@ filtern" + "Exported" : { + "comment" : "A title of the sheet that appears after exporting a template.", + "isCommentAutoGenerated" : true + }, + "Expose prompts" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prompts verfügbar machen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Filtrar a la sesión %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exponer prompts" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Filtrer sur la session %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exposer les prompts" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッション %@ にフィルタ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロンプトを公開" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Filtrar para a sessão %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Expor prompts" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "筛选到会话 %@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "暴露提示" } } } }, - "Flush Memories": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Speicher leeren" + "Expose resources" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ressourcen verfügbar machen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Vaciar memorias" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exponer recursos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Vider les mémoires" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exposer les ressources" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "メモリをフラッシュ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リソースを公開" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Limpar memórias" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Expor recursos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "清空记忆" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "暴露资源" } } } }, - "Focus topic (optional)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Fokusthema (optional)" + "External Provider" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Externer Anbieter" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Tema de enfoque (opcional)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proveedor externo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sujet ciblé (optionnel)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fournisseur externe" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "フォーカストピック(任意)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "外部プロバイダー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Tópico de foco (opcional)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Provedor externo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "聚焦主题(可选)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "外部提供方" } } } }, - "Full copy of active profile (all state)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Vollständige Kopie des aktiven Profils (gesamter Zustand)" + "Feedback" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feedback" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Copia completa del perfil activo (todo el estado)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comentarios" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Copie complète du profil actif (tout l'état)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retour" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティブなプロファイルの完全コピー(すべての状態)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "フィードバック" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Cópia completa do perfil ativo (todo o estado)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feedback" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "当前配置的完整副本(所有状态)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "反馈" } } } }, - "Gateway": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Gateway" + "Feishu Setup Docs" : { + + }, + "Fetch" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrufen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Gateway" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obtener" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Gateway" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Récupérer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Gateway" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "取得" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Gateway" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buscar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Gateway" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "拉取" } } } }, - "Gateway Running": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Gateway läuft" + "Files" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dateien" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Gateway en ejecución" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Archivos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Gateway en cours" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fichiers" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Gateway 実行中" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ファイル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Gateway em execução" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arquivos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Gateway 运行中" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "文件" } } } }, - "Gateway Stopped": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Gateway gestoppt" + "Filter logs..." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logs filtern..." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Gateway detenido" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrar registros..." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Gateway arrêté" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrer les journaux..." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Gateway 停止" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ログをフィルタ..." } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Gateway parado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrar logs..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Gateway 已停止" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "筛选日志..." } } } }, - "Gateway restart required": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Gateway-Neustart erforderlich" + "Filter servers..." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server filtern..." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Se requiere reiniciar el gateway" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrar servidores..." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Redémarrage du gateway requis" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrer les serveurs..." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Gateway の再起動が必要です" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サーバーをフィルタ..." } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Reinicialização do gateway necessária" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrar servidores..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "需要重启 Gateway" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "筛选服务器..." } } } }, - "General": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Allgemein" + "Filter skills..." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skills filtern..." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "General" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrar habilidades..." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Général" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrer les compétences..." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "一般" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スキルをフィルタ..." } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Geral" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrar habilidades..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "常规" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "筛选技能..." } } } }, - "Global Settings": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Globale Einstellungen" + "Filter to session %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auf Sitzung %@ filtern" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ajustes globales" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrar a la sesión %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Paramètres globaux" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrer sur la session %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "グローバル設定" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッション %@ にフィルタ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Configurações globais" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtrar para a sessão %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "全局设置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "筛选到会话 %@" } } } }, - "Header": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Header" + "Flush Memories" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Speicher leeren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cabecera" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vaciar memorias" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "En-tête" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vider les mémoires" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ヘッダー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "メモリをフラッシュ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Cabeçalho" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limpar memórias" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "请求头" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "清空记忆" } } } }, - "Headers": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Header" + "Focus topic (optional)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fokusthema (optional)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cabeceras" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tema de enfoque (opcional)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "En-têtes" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sujet ciblé (optionnel)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ヘッダー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "フォーカストピック(任意)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Cabeçalhos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tópico de foco (opcional)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "请求头" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "聚焦主题(可选)" } } } }, - "Health": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Zustand" + "focus, timer" : { + "comment" : "A placeholder for a comma-separated list of tags.", + "isCommentAutoGenerated" : true + }, + "Full copy of active profile (all state)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vollständige Kopie des aktiven Profils (gesamter Zustand)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Salud" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copia completa del perfil activo (todo el estado)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Santé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Copie complète du profil actif (tout l'état)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "状態" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティブなプロファイルの完全コピー(すべての状態)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Saúde" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cópia completa do perfil ativo (todo o estado)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "健康" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当前配置的完整副本(所有状态)" } } } }, - "Hermes": {}, - "Hermes Not Found": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hermes nicht gefunden" + "Gateway" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Hermes no encontrado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Hermes introuvable" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Hermes が見つかりません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Hermes não encontrado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未找到 Hermes" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway" } } } }, - "Hermes Running": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hermes läuft" + "Gateway restart required" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway-Neustart erforderlich" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Hermes en ejecución" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se requiere reiniciar el gateway" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Hermes en cours" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redémarrage du gateway requis" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Hermes 実行中" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway の再起動が必要です" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Hermes em execução" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reinicialização do gateway necessária" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Hermes 运行中" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "需要重启 Gateway" } } } }, - "Hermes Stopped": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hermes gestoppt" + "Gateway Running" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway läuft" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Hermes detenido" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway en ejecución" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Hermes arrêté" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway en cours" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Hermes 停止" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway 実行中" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Hermes parado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway em execução" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Hermes 已停止" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway 运行中" } } } }, - "Hermes binary not found": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hermes-Binary nicht gefunden" + "Gateway Stopped" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway gestoppt" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Binario de Hermes no encontrado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway detenido" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Binaire Hermes introuvable" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway arrêté" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Hermes バイナリが見つかりません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway 停止" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Binário do Hermes não encontrado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway parado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未找到 Hermes 可执行文件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway 已停止" } } } }, - "Hermes needs a global webhook secret and port before subscriptions can receive traffic. Run the gateway setup wizard or edit ~/.hermes/config.yaml manually.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hermes braucht ein globales Webhook-Secret und einen Port, bevor Abonnements Traffic empfangen können. Starte den Gateway-Einrichtungsassistenten oder bearbeite ~/.hermes/config.yaml manuell." + "General" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Allgemein" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Hermes necesita un secreto y puerto globales de webhook antes de que las suscripciones reciban tráfico. Ejecuta el asistente de configuración del gateway o edita ~/.hermes/config.yaml manualmente." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "General" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Hermes a besoin d'un secret webhook global et d'un port avant que les abonnements puissent recevoir du trafic. Lancez l'assistant de configuration du gateway ou éditez ~/.hermes/config.yaml manuellement." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Général" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サブスクリプションがトラフィックを受信する前に、Hermes にはグローバルな webhook シークレットとポートが必要です。ゲートウェイセットアップウィザードを実行するか、~/.hermes/config.yaml を手動で編集してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "一般" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O Hermes precisa de um segredo global de webhook e uma porta antes que assinaturas possam receber tráfego. Execute o assistente de configuração do gateway ou edite ~/.hermes/config.yaml manualmente." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geral" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "订阅接收流量前,Hermes 需要全局 webhook 密钥和端口。运行 gateway 设置向导或手动编辑 ~/.hermes/config.yaml。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "常规" } } } }, - "Hide": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ausblenden" + "github.com/owner/plugin-repo or owner/repo" : { + + }, + "Global Settings" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Globale Einstellungen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ocultar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajustes globales" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Masquer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Paramètres globaux" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "非表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "グローバル設定" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ocultar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configurações globais" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "隐藏" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "全局设置" } } } }, - "Hide Output": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ausgabe ausblenden" + "Header" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Header" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ocultar salida" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cabecera" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Masquer la sortie" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "En-tête" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "出力を非表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ヘッダー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ocultar saída" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cabeçalho" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "隐藏输出" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "请求头" } } } }, - "Hide details": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Details ausblenden" + "Headers" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Header" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ocultar detalles" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cabeceras" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Masquer les détails" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "En-têtes" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "詳細を非表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ヘッダー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ocultar detalhes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cabeçalhos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "隐藏详情" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "请求头" } } } }, - "Home Assistant Docs": {}, - "Home Channel": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Home-Kanal" + "Health" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zustand" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Canal principal" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Salud" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Canal principal" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Santé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ホームチャンネル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "状態" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Canal principal" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Saúde" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "主频道" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "健康" } } } }, - "Homeserver": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Homeserver" + "Hermes" : { + + }, + "hermes at %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "hermes auf %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Homeserver" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "hermes en %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Homeserver" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "hermes sur %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ホームサーバー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 上の hermes" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Homeserver" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "hermes em %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "主服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 上的 hermes" } } } }, - "Host key changed": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Host-Schlüssel geändert" + "Hermes binary not found" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes-Binary nicht gefunden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Clave de host cambiada" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Binario de Hermes no encontrado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Clé d'hôte modifiée" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Binaire Hermes introuvable" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ホストキーが変更されました" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes バイナリが見つかりません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Chave do host alterada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Binário do Hermes não encontrado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "主机密钥已变更" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未找到 Hermes 可执行文件" } } } }, - "Human Delay": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Menschliche Verzögerung" + "Hermes needs a global webhook secret and port before subscriptions can receive traffic. Run the gateway setup wizard or edit ~/.hermes/config.yaml manually." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes braucht ein globales Webhook-Secret und einen Port, bevor Abonnements Traffic empfangen können. Starte den Gateway-Einrichtungsassistenten oder bearbeite ~/.hermes/config.yaml manuell." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Retraso humano" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes necesita un secreto y puerto globales de webhook antes de que las suscripciones reciban tráfico. Ejecuta el asistente de configuración del gateway o edita ~/.hermes/config.yaml manualmente." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Délai humain" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes a besoin d'un secret webhook global et d'un port avant que les abonnements puissent recevoir du trafic. Lancez l'assistant de configuration du gateway ou éditez ~/.hermes/config.yaml manuellement." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ヒューマンディレイ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サブスクリプションがトラフィックを受信する前に、Hermes にはグローバルな webhook シークレットとポートが必要です。ゲートウェイセットアップウィザードを実行するか、~/.hermes/config.yaml を手動で編集してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Atraso humano" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Hermes precisa de um segredo global de webhook e uma porta antes que assinaturas possam receber tráfego. Execute o assistente de configuração do gateway ou edite ~/.hermes/config.yaml manualmente." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "人类延迟" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "订阅接收流量前,Hermes 需要全局 webhook 密钥和端口。运行 gateway 设置向导或手动编辑 ~/.hermes/config.yaml。" } } } }, - "ID: %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "ID: %@" + "Hermes Not Found" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes nicht gefunden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "ID: %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes no encontrado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "ID : %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes introuvable" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ID: %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes が見つかりません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "ID: %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes não encontrado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "ID:%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未找到 Hermes" } } } }, - "If this is the first connection, ensure your key is loaded with `ssh-add` and that the remote accepts it.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wenn dies die erste Verbindung ist, stelle sicher, dass dein Schlüssel mit `ssh-add` geladen wurde und der Remote ihn akzeptiert." + "hermes profile show" : { + + }, + "Hermes Running" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes läuft" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Si es la primera conexión, asegúrate de que tu clave esté cargada con `ssh-add` y de que el remoto la acepte." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes en ejecución" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "S'il s'agit de la première connexion, assurez-vous que votre clé est chargée avec `ssh-add` et que l'hôte distant l'accepte." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes en cours" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "初回接続の場合は、`ssh-add` でキーがロードされていることと、リモートがそれを受け付けていることを確認してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes 実行中" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Se esta for a primeira conexão, garanta que sua chave esteja carregada com `ssh-add` e que o remoto a aceite." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes em execução" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "如果这是首次连接,请确保已通过 `ssh-add` 加载你的密钥,并且远端接受该密钥。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes 运行中" } } } }, - "If you trust the change, remove the stale entry and reconnect:": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wenn du der Änderung vertraust, entferne den veralteten Eintrag und verbinde dich erneut:" + "Hermes Stopped" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes gestoppt" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Si confías en el cambio, elimina la entrada obsoleta y reconéctate:" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes detenido" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Si vous faites confiance au changement, supprimez l'entrée obsolète et reconnectez-vous :" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes arrêté" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "変更を信頼する場合、古いエントリを削除して再接続してください:" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes 停止" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Se você confia na mudança, remova a entrada antiga e reconecte:" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes parado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "如果你信任此变更,请移除过期条目并重新连接:" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes 已停止" } } } }, - "Import": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Importieren" + "hermes.example.com or a ~/.ssh/config alias" : { + + }, + "Hide" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausblenden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Importar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ocultar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Importer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Masquer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "インポート" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "非表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Importar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ocultar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "导入" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐藏" } } } }, - "Inactive": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Inaktiv" + "Hide details" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Details ausblenden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Inactivo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ocultar detalles" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Inactif" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Masquer les détails" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "非アクティブ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "詳細を非表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Inativo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ocultar detalhes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未激活" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐藏详情" } } } }, - "Include (comma-separated — if set, only these are exposed)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Einschließen (kommagetrennt — wenn gesetzt, werden nur diese freigegeben)" + "Hide Output" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausgabe ausblenden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Incluir (separados por comas — si se define, solo estos se exponen)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ocultar salida" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Inclure (séparés par des virgules — si défini, seuls ceux-ci sont exposés)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Masquer la sortie" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "含める(カンマ区切り — 設定した場合これらのみが公開されます)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "出力を非表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Incluir (separados por vírgula — se definido, apenas estes são expostos)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ocultar saída" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "包含(逗号分隔 — 若设置,仅暴露这些)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "隐藏输出" } } } }, - "Insights": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Einsichten" + "Home Assistant Docs" : { + + }, + "Home Channel" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Home-Kanal" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Analíticas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Canal principal" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Analyses" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Canal principal" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "インサイト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ホームチャンネル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Insights" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Canal principal" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "洞察" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "主频道" } } } }, - "Install": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Installieren" + "Homeserver" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Homeserver" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Instalar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Homeserver" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Installer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Homeserver" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "インストール" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ホームサーバー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Instalar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Homeserver" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "安装" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "主服务器" } } } }, - "Install BlueBubbles Server": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "BlueBubbles Server installieren" + "Host key changed" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Host-Schlüssel geändert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Instalar BlueBubbles Server" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clave de host cambiada" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Installer BlueBubbles Server" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clé d'hôte modifiée" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "BlueBubbles Server をインストール" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ホストキーが変更されました" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Instalar BlueBubbles Server" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chave do host alterada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "安装 BlueBubbles 服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "主机密钥已变更" } } } }, - "Install Plugin": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Plugin installieren" + "https://..." : { + + }, + "https://…" : { + "comment" : "A placeholder for a website URL.", + "isCommentAutoGenerated" : true + }, + "https://example.com/my.scarftemplate" : { + "comment" : "A placeholder URL for a template.", + "isCommentAutoGenerated" : true + }, + "Human Delay" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Menschliche Verzögerung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Instalar plugin" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retraso humano" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Installer le plugin" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Délai humain" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プラグインをインストール" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ヒューマンディレイ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Instalar plugin" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atraso humano" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "安装插件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "人类延迟" } } } }, - "Install a Plugin": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Plugin installieren" + "ID: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID: %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Instalar un plugin" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID: %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Installer un plugin" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID : %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プラグインをインストール" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID: %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Instalar um plugin" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID: %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "安装插件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID:%@" } } } }, - "Install signal-cli": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "signal-cli installieren" + "If this is the first connection, ensure your key is loaded with `ssh-add` and that the remote accepts it." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wenn dies die erste Verbindung ist, stelle sicher, dass dein Schlüssel mit `ssh-add` geladen wurde und der Remote ihn akzeptiert." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Instalar signal-cli" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si es la primera conexión, asegúrate de que tu clave esté cargada con `ssh-add` y de que el remoto la acepte." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Installer signal-cli" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "S'il s'agit de la première connexion, assurez-vous que votre clé est chargée avec `ssh-add` et que l'hôte distant l'accepte." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "signal-cli をインストール" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "初回接続の場合は、`ssh-add` でキーがロードされていることと、リモートがそれを受け付けていることを確認してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Instalar signal-cli" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se esta for a primeira conexão, garanta que sua chave esteja carregada com `ssh-add` e que o remoto a aceite." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "安装 signal-cli" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果这是首次连接,请确保已通过 `ssh-add` 加载你的密钥,并且远端接受该密钥。" } } } }, - "Installed": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Installiert" + "If you trust the change, remove the stale entry and reconnect:" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wenn du der Änderung vertraust, entferne den veralteten Eintrag und verbinde dich erneut:" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Instalado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si confías en el cambio, elimina la entrada obsoleta y reconéctate:" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Installé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si vous faites confiance au changement, supprimez l'entrée obsolète et reconnectez-vous :" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "インストール済み" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "変更を信頼する場合、古いエントリを削除して再接続してください:" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Instalado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se você confia na mudança, remova a entrada antiga e reconecte:" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已安装" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果你信任此变更,请移除过期条目并重新连接:" } } } }, - "Interact": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Interagieren" + "iMessage integration runs through BlueBubbles Server. You need a Mac that stays on with Messages.app signed in — install BlueBubbles Server on it, then point hermes at that server's URL." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Die iMessage-Integration läuft über BlueBubbles Server. Du brauchst einen Mac, der eingeschaltet bleibt und in Messages.app angemeldet ist — installiere BlueBubbles Server darauf und richte hermes auf die URL dieses Servers aus." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Interactuar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La integración de iMessage usa BlueBubbles Server. Necesitas un Mac encendido con Messages.app iniciado — instala BlueBubbles Server ahí y apunta hermes a la URL de ese servidor." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Interagir" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'intégration iMessage passe par BlueBubbles Server. Il vous faut un Mac qui reste allumé avec Messages.app connecté — installez BlueBubbles Server dessus, puis pointez hermes vers l'URL de ce serveur." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "操作" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "iMessage 連携は BlueBubbles Server を経由します。メッセージ App にサインインしたまま動作し続ける Mac が必要です — そこに BlueBubbles Server をインストールし、そのサーバーの URL を hermes に指定してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Interagir" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "A integração com iMessage usa o BlueBubbles Server. Você precisa de um Mac ligado e com Messages.app conectado — instale o BlueBubbles Server nele e aponte o hermes para a URL desse servidor." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "交互" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "iMessage 集成通过 BlueBubbles Server 运行。你需要一台保持开机且登录 Messages.app 的 Mac — 在其上安装 BlueBubbles Server,然后将 hermes 指向该服务器的 URL。" } } } }, - "Invalid URL": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ungültige URL" + "Import" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Importieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "URL no válida" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Importar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "URL invalide" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Importer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "無効な URL" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "インポート" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "URL inválida" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Importar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无效 URL" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导入" } } } }, - "KEY": {}, - "Keep typing to send as a message, or press Esc.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Weitertippen, um als Nachricht zu senden, oder Esc drücken." + "Inactive" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inaktiv" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sigue escribiendo para enviar como mensaje, o pulsa Esc." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inactivo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Continuez à taper pour envoyer en tant que message, ou appuyez sur Échap." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inactif" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "入力を続けるとメッセージとして送信されます。キャンセルするには Esc を押してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "非アクティブ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Continue digitando para enviar como mensagem, ou pressione Esc." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inativo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "继续输入作为消息发送,或按 Esc 取消。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未激活" } } } }, - "Label (optional)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Label (optional)" + "Include (comma-separated — if set, only these are exposed)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einschließen (kommagetrennt — wenn gesetzt, werden nur diese freigegeben)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Etiqueta (opcional)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Incluir (separados por comas — si se define, solo estos se exponen)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Étiquette (optionnel)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inclure (séparés par des virgules — si défini, seuls ceux-ci sont exposés)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ラベル(任意)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "含める(カンマ区切り — 設定した場合これらのみが公開されます)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Rótulo (opcional)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Incluir (separados por vírgula — se definido, apenas estes são expostos)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "标签(可选)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "包含(逗号分隔 — 若设置,仅暴露这些)" } } } }, - "Last Output": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Letzte Ausgabe" + "Include Cron Jobs" : { + + }, + "Include Skills" : { + "comment" : "A heading for a section of a template export sheet that lets the user select which skills to include in the generated template.", + "isCommentAutoGenerated" : true + }, + "Insights" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einsichten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Última salida" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Analíticas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Dernière sortie" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Analyses" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "最終出力" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "インサイト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Última saída" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Insights" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "最后输出" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "洞察" } } } }, - "Last probe: %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Letzte Prüfung: %@" + "Inspecting template…" : { + + }, + "Install" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Installieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Última comprobación: %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Dernière sonde : %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Installer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "最終確認: %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "インストール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Última verificação: %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "上次探测:%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安装" } } } }, - "Last run: %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Letzter Lauf: %@" + "Install a Plugin" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plugin installieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Última ejecución: %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalar un plugin" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Dernière exécution : %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Installer un plugin" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "最終実行: %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プラグインをインストール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Última execução: %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalar um plugin" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "上次运行:%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安装插件" } } } }, - "Last updated: %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Zuletzt aktualisiert: %@" + "Install BlueBubbles Server" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "BlueBubbles Server installieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Última actualización: %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalar BlueBubbles Server" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Mis à jour : %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Installer BlueBubbles Server" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "最終更新: %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "BlueBubbles Server をインストール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Atualizado em: %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalar BlueBubbles Server" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "最后更新:%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安装 BlueBubbles 服务器" } } } }, - "Layout": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Layout" + "Install Failed" : { + + }, + "Install from File…" : { + "comment" : "A button that opens a file picker to install a template.", + "isCommentAutoGenerated" : true + }, + "Install from URL…" : { + "comment" : "A button that opens a dialog to enter a URL for installing a template.", + "isCommentAutoGenerated" : true + }, + "Install Plugin" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plugin installieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Diseño" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalar plugin" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Mise en page" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Installer le plugin" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "レイアウト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プラグインをインストール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Layout" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalar plugin" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "布局" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安装插件" } } } }, - "Leave blank to infer from the model ID's prefix (\"openai/...\" → openai).": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Leer lassen, um aus dem Präfix der Modell-ID abzuleiten (\"openai/...\" → openai)." + "Install signal-cli" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli installieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Déjalo vacío para deducirlo del prefijo del ID del modelo (\"openai/...\" → openai)." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalar signal-cli" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Laissez vide pour déduire du préfixe de l'ID du modèle (« openai/... » → openai)." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Installer signal-cli" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "モデル ID のプレフィックスから推定するには空のままにします(\"openai/...\" → openai)。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli をインストール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Deixe em branco para inferir pelo prefixo do ID do modelo (\"openai/...\" → openai)." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalar signal-cli" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "留空则从模型 ID 的前缀推断(\"openai/...\" → openai)。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安装 signal-cli" } } } }, - "Leave blank unless Hermes is installed at a non-default path (systemd services often live at /var/lib/hermes/.hermes; Docker sidecars vary). Test Connection auto-suggests a value when it detects one of the known alternates.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Leer lassen, außer Hermes liegt nicht am Standardpfad (systemd-Dienste oft unter /var/lib/hermes/.hermes; Docker-Sidecars variieren). Test Connection schlägt automatisch einen Wert vor, wenn es eine bekannte Alternative erkennt." + "Install Template" : { + "comment" : "Button prompt to install a template from a file.", + "isCommentAutoGenerated" : true + }, + "Install Template from URL" : { + "comment" : "A title for a dialog that allows the user to install a template from a URL.", + "isCommentAutoGenerated" : true + }, + "Installed" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Installiert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Déjalo vacío salvo que Hermes esté instalado en una ruta no predeterminada (los servicios systemd suelen estar en /var/lib/hermes/.hermes; los sidecars Docker varían). Probar conexión sugiere un valor automáticamente si detecta una alternativa conocida." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Laissez vide sauf si Hermes est installé à un chemin non standard (les services systemd résident souvent dans /var/lib/hermes/.hermes ; les sidecars Docker varient). Tester la connexion suggère automatiquement une valeur lorsqu'un des chemins alternatifs connus est détecté." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Installé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Hermes がデフォルト以外のパスにインストールされていない限り空のままにしてください(systemd サービスはしばしば /var/lib/hermes/.hermes にあり、Docker サイドカーは様々です)。テスト接続は既知の代替パスを検出すると自動的に値を提案します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "インストール済み" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Deixe em branco a menos que o Hermes esteja instalado em um caminho não padrão (serviços systemd geralmente ficam em /var/lib/hermes/.hermes; sidecars Docker variam). Testar conexão sugere automaticamente um valor ao detectar um dos caminhos alternativos conhecidos." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Instalado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "除非 Hermes 安装在非默认路径,否则请留空(systemd 服务通常在 /var/lib/hermes/.hermes,Docker sidecar 则各不相同)。检测到已知替代路径时,测试连接会自动建议值。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已安装" } } } }, - "Level": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ebene" + "Installed %@" : { + "comment" : "A title that says that a template has been successfully installed. The argument is the name of the template.", + "isCommentAutoGenerated" : true + }, + "Installing…" : { + + }, + "Interact" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Interagieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Nivel" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Interactuar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Niveau" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Interagir" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "レベル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "操作" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nível" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Interagir" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "级别" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "交互" } } } }, - "Link Device": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Gerät koppeln" + "Internal state inconsistency — please close and re-open." : { + + }, + "Invalid URL" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ungültige URL" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Vincular dispositivo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "URL no válida" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Associer un appareil" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "URL invalide" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "デバイスをリンク" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "無効な URL" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Vincular dispositivo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "URL inválida" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "关联设备" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无效 URL" } } } }, - "Link the device first to generate and scan a QR code. Once linked, start the daemon — it must keep running for hermes to send/receive messages.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Kopple zuerst das Gerät, um einen QR-Code zu erzeugen und zu scannen. Nach dem Koppeln starte den Daemon — er muss laufen, damit hermes Nachrichten senden/empfangen kann." + "keep (not installed by template)" : { + "comment" : "A description of a file that is not part of the template's installation.", + "isCommentAutoGenerated" : true + }, + "Keep typing to send as a message, or press Esc." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Weitertippen, um als Nachricht zu senden, oder Esc drücken." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Vincula primero el dispositivo para generar y escanear un código QR. Una vez vinculado, inicia el demonio — debe seguir ejecutándose para que hermes envíe/reciba mensajes." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sigue escribiendo para enviar como mensaje, o pulsa Esc." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Associez d'abord l'appareil pour générer et scanner un QR code. Une fois associé, démarrez le démon — il doit continuer à fonctionner pour que hermes puisse envoyer/recevoir des messages." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continuez à taper pour envoyer en tant que message, ou appuyez sur Échap." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "まずデバイスをリンクして QR コードを生成・スキャンしてください。リンク後、デーモンを起動してください — hermes がメッセージを送受信するには動作し続ける必要があります。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "入力を続けるとメッセージとして送信されます。キャンセルするには Esc を押してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Vincule o dispositivo primeiro para gerar e escanear um QR code. Após vincular, inicie o daemon — ele precisa continuar rodando para o hermes enviar/receber mensagens." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continue digitando para enviar como mensagem, ou pressione Esc." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "首先关联设备以生成并扫描二维码。关联后启动守护进程 — 必须保持运行以便 hermes 收发消息。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "继续输入作为消息发送,或按 Esc 取消。" } } } }, - "Linking…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Kopple…" + "KEY" : { + + }, + "Label (optional)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Label (optional)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Vinculando…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Etiqueta (opcional)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Association…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Étiquette (optionnel)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リンク中…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ラベル(任意)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Vinculando…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rótulo (opcional)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "关联中…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "标签(可选)" } } } }, - "Loaded": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Geladen" + "Last Output" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Letzte Ausgabe" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cargado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Última salida" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Chargé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dernière sortie" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "読み込み済み" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "最終出力" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Carregado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Última saída" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已加载" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最后输出" } } } }, - "Loading session…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Lade Sitzung…" + "Last probe: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Letzte Prüfung: %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cargando sesión…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Última comprobación: %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Chargement de la session…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dernière sonde : %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッションを読み込み中…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "最終確認: %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Carregando sessão…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Última verificação: %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "正在加载会话…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "上次探测:%@" } } } }, - "Local": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Lokal" + "Last run: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Letzter Lauf: %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Local" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Última ejecución: %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Local" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dernière exécution : %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ローカル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "最終実行: %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Local" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Última execução: %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "本地" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "上次运行:%@" } } } }, - "Local (stdio)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Lokal (stdio)" + "Last updated: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zuletzt aktualisiert: %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Local (stdio)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Última actualización: %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Local (stdio)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mis à jour : %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ローカル (stdio)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "最終更新: %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Local (stdio)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atualizado em: %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "本地 (stdio)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最后更新:%@" } } } }, - "Locale": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sprache & Region" + "Layout" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Layout" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Configuración regional" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diseño" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Locale" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mise en page" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ロケール" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "レイアウト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Localidade" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Layout" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "区域" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "布局" } } } }, - "Log File": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Log-Datei" + "Leave blank to infer from the model ID's prefix (\"openai/...\" → openai)." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Leer lassen, um aus dem Präfix der Modell-ID abzuleiten (\"openai/...\" → openai)." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Archivo de registro" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Déjalo vacío para deducirlo del prefijo del ID del modelo (\"openai/...\" → openai)." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Fichier journal" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Laissez vide pour déduire du préfixe de l'ID du modèle (« openai/... » → openai)." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ログファイル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "モデル ID のプレフィックスから推定するには空のままにします(\"openai/...\" → openai)。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Arquivo de log" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deixe em branco para inferir pelo prefixo do ID do modelo (\"openai/...\" → openai)." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "日志文件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "留空则从模型 ID 的前缀推断(\"openai/...\" → openai)。" } } } }, - "Logging": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Logging" + "Leave blank unless Hermes is installed at a non-default path (systemd services often live at /var/lib/hermes/.hermes; Docker sidecars vary). Test Connection auto-suggests a value when it detects one of the known alternates." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Leer lassen, außer Hermes liegt nicht am Standardpfad (systemd-Dienste oft unter /var/lib/hermes/.hermes; Docker-Sidecars variieren). Test Connection schlägt automatisch einen Wert vor, wenn es eine bekannte Alternative erkennt." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Registro" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Déjalo vacío salvo que Hermes esté instalado en una ruta no predeterminada (los servicios systemd suelen estar en /var/lib/hermes/.hermes; los sidecars Docker varían). Probar conexión sugiere un valor automáticamente si detecta una alternativa conocida." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Journalisation" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Laissez vide sauf si Hermes est installé à un chemin non standard (les services systemd résident souvent dans /var/lib/hermes/.hermes ; les sidecars Docker varient). Tester la connexion suggère automatiquement une valeur lorsqu'un des chemins alternatifs connus est détecté." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ログ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes がデフォルト以外のパスにインストールされていない限り空のままにしてください(systemd サービスはしばしば /var/lib/hermes/.hermes にあり、Docker サイドカーは様々です)。テスト接続は既知の代替パスを検出すると自動的に値を提案します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Registro" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deixe em branco a menos que o Hermes esteja instalado em um caminho não padrão (serviços systemd geralmente ficam em /var/lib/hermes/.hermes; sidecars Docker variam). Testar conexão sugere automaticamente um valor ao detectar um dos caminhos alternativos conhecidos." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "日志" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "除非 Hermes 安装在非默认路径,否则请留空(systemd 服务通常在 /var/lib/hermes/.hermes,Docker sidecar 则各不相同)。检测到已知替代路径时,测试连接会自动建议值。" } } } }, - "Logs": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Logs" + "Level" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ebene" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Registros" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nivel" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Journaux" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Niveau" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ログ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "レベル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Logs" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nível" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "日志" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "级别" } } } }, - "MCP": {}, - "MCP Servers": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "MCP-Server" + "Link Device" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gerät koppeln" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Servidores MCP" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vincular dispositivo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Serveurs MCP" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Associer un appareil" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "MCP サーバー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "デバイスをリンク" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Servidores MCP" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vincular dispositivo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "MCP 服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关联设备" } } } }, - "MCP Servers (%lld)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "MCP-Server (%lld)" + "Link the device first to generate and scan a QR code. Once linked, start the daemon — it must keep running for hermes to send/receive messages." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kopple zuerst das Gerät, um einen QR-Code zu erzeugen und zu scannen. Nach dem Koppeln starte den Daemon — er muss laufen, damit hermes Nachrichten senden/empfangen kann." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Servidores MCP (%lld)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vincula primero el dispositivo para generar y escanear un código QR. Una vez vinculado, inicia el demonio — debe seguir ejecutándose para que hermes envíe/reciba mensajes." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Serveurs MCP (%lld)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Associez d'abord l'appareil pour générer et scanner un QR code. Une fois associé, démarrez le démon — il doit continuer à fonctionner pour que hermes puisse envoyer/recevoir des messages." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "MCP サーバー (%lld)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "まずデバイスをリンクして QR コードを生成・スキャンしてください。リンク後、デーモンを起動してください — hermes がメッセージを送受信するには動作し続ける必要があります。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Servidores MCP (%lld)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vincule o dispositivo primeiro para gerar e escanear um QR code. Após vincular, inicie o daemon — ele precisa continuar rodando para o hermes enviar/receber mensagens." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "MCP 服务器 (%lld)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "首先关联设备以生成并扫描二维码。关联后启动守护进程 — 必须保持运行以便 hermes 收发消息。" } } } }, - "Manage": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Verwalten" + "Linking…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kopple…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Administrar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vinculando…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Gérer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Association…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "管理" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リンク中…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Gerenciar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vinculando…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "管理" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关联中…" } } } }, - "Manage Servers…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Server verwalten…" + "Loaded" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Geladen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Administrar servidores…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cargado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Gérer les serveurs…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chargé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サーバーを管理…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "読み込み済み" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Gerenciar servidores…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carregado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "管理服务器…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已加载" } } } }, - "Manage in Credential Pools": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "In Credential-Pools verwalten" + "Loading configuration…" : { + "comment" : "A message displayed while loading the configuration.", + "isCommentAutoGenerated" : true + }, + "Loading session…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lade Sitzung…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Administrar en grupos de credenciales" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cargando sesión…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Gérer dans les pools d'identifiants" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chargement de la session…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "資格情報プールで管理" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッションを読み込み中…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Gerenciar nos pools de credenciais" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Carregando sessão…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在凭证池中管理" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "正在加载会话…" } } } }, - "Matrix Setup Docs": {}, - "Matrix uses either an access token (preferred) or username/password. Get an access token from Element: Settings → Help & About → Access Token.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Matrix nutzt entweder ein Access Token (bevorzugt) oder Benutzername/Passwort. Hol dir ein Access Token in Element: Einstellungen → Hilfe & Info → Access Token." + "Local" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lokal" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Matrix usa un token de acceso (preferido) o usuario/contraseña. Obtén un token de acceso en Element: Ajustes → Ayuda y acerca de → Access Token." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Local" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Matrix utilise soit un jeton d'accès (préféré), soit un nom d'utilisateur/mot de passe. Obtenez un jeton d'accès dans Element : Paramètres → Aide & À propos → Access Token." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Local" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Matrix はアクセストークン(推奨)かユーザー名/パスワードを使用します。Element からアクセストークンを取得してください: 設定 → ヘルプ & 情報 → アクセストークン。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ローカル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O Matrix usa um token de acesso (preferido) ou usuário/senha. Obtenha um token de acesso no Element: Configurações → Ajuda e sobre → Access Token." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Local" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Matrix 使用访问令牌(推荐)或用户名/密码。从 Element 获取访问令牌:设置 → 帮助与关于 → 访问令牌。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "本地" } } } }, - "Mattermost Setup Docs": {}, - "Memory": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Speicher" + "Local (stdio)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lokal (stdio)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Memoria" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Local (stdio)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Mémoire" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Local (stdio)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "メモリ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ローカル (stdio)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Memória" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Local (stdio)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "记忆" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "本地 (stdio)" } } } }, - "Memory is managed by %@. File contents shown here may be stale.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Der Speicher wird von %@ verwaltet. Die hier angezeigten Dateiinhalte können veraltet sein." + "Locale" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sprache & Region" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "La memoria la gestiona %@. El contenido de archivos mostrado aquí puede estar desactualizado." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuración regional" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "La mémoire est gérée par %@. Le contenu des fichiers affichés ici peut être obsolète." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Locale" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "メモリは %@ が管理します。ここに表示されるファイル内容は古い場合があります。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ロケール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "A memória é gerenciada por %@. O conteúdo dos arquivos exibido aqui pode estar desatualizado." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Localidade" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "记忆由 %@ 管理。此处显示的文件内容可能已过时。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "区域" } } } }, - "Message Hermes...": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hermes Nachricht senden..." + "Log File" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Log-Datei" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Mensaje a Hermes..." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Archivo de registro" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Envoyer un message à Hermes..." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fichier journal" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Hermes にメッセージを送信..." + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ログファイル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Enviar mensagem ao Hermes..." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arquivo de log" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "向 Hermes 发送消息..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日志文件" } } } }, - "Messages will appear here as the conversation progresses.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nachrichten erscheinen hier im Laufe der Unterhaltung." + "Logging" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logging" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Los mensajes aparecerán aquí a medida que avance la conversación." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registro" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les messages apparaîtront ici au fur et à mesure de la conversation." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Journalisation" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "会話が進むにつれてメッセージがここに表示されます。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ログ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "As mensagens aparecerão aqui conforme a conversa progride." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registro" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "消息将随对话进行显示在此处。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日志" } } } }, - "Migrate": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Migrieren" + "Logs" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logs" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Migrar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registros" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Migrer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Journaux" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "移行" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ログ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Migrar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Logs" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "迁移" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日志" } } } }, - "Missing required config:": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Fehlende erforderliche Konfiguration:" + "Manage" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwalten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Falta configuración requerida:" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Administrar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Configuration requise manquante :" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gérer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "必須設定が不足しています:" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Configuração obrigatória ausente:" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gerenciar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "缺少必需的配置:" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理" } } } }, - "Modal": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Modal" + "Manage in Credential Pools" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "In Credential-Pools verwalten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Modal" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Administrar en grupos de credenciales" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Modale" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gérer dans les pools d'identifiants" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "モーダル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "資格情報プールで管理" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Modal" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gerenciar nos pools de credenciais" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "模态" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在凭证池中管理" } } } }, - "Model": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Modell" + "Manage Servers…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server verwalten…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Modelo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Administrar servidores…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Modèle" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gérer les serveurs…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "モデル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サーバーを管理…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Modelo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gerenciar servidores…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "模型" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "管理服务器…" } } } }, - "Model ID": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Modell-ID" + "Markdown that will be appended to the installer's MEMORY.md, wrapped in template-specific markers so it can be removed cleanly later." : { + "comment" : "A description of the memory appendix field.", + "isCommentAutoGenerated" : true + }, + "Matrix Setup Docs" : { + + }, + "Matrix uses either an access token (preferred) or username/password. Get an access token from Element: Settings → Help & About → Access Token." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Matrix nutzt entweder ein Access Token (bevorzugt) oder Benutzername/Passwort. Hol dir ein Access Token in Element: Einstellungen → Hilfe & Info → Access Token." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "ID del modelo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Matrix usa un token de acceso (preferido) o usuario/contraseña. Obtén un token de acceso en Element: Ajustes → Ayuda y acerca de → Access Token." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "ID du modèle" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Matrix utilise soit un jeton d'accès (préféré), soit un nom d'utilisateur/mot de passe. Obtenez un jeton d'accès dans Element : Paramètres → Aide & À propos → Access Token." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "モデル ID" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Matrix はアクセストークン(推奨)かユーザー名/パスワードを使用します。Element からアクセストークンを取得してください: 設定 → ヘルプ & 情報 → アクセストークン。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "ID do modelo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Matrix usa um token de acesso (preferido) ou usuário/senha. Obtenha um token de acesso no Element: Configurações → Ajuda e sobre → Access Token." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "模型 ID" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Matrix 使用访问令牌(推荐)或用户名/密码。从 Element 获取访问令牌:设置 → 帮助与关于 → 访问令牌。" } } } }, - "Models": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Modelle" + "Mattermost Setup Docs" : { + + }, + "MCP" : { + + }, + "MCP Servers" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "MCP-Server" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Modelos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servidores MCP" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Modèles" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serveurs MCP" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "モデル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "MCP サーバー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Modelos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servidores MCP" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "模型" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "MCP 服务器" } } } }, - "Monitor": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Überwachen" + "MCP Servers (%lld)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "MCP-Server (%lld)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Monitor" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servidores MCP (%lld)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Surveiller" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serveurs MCP (%lld)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "モニター" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "MCP サーバー (%lld)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Monitor" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servidores MCP (%lld)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "监控" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "MCP 服务器 (%lld)" } } } }, - "Name": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Name" + "Memory" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Speicher" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Nombre" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Memoria" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Nom" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mémoire" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "名前" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "メモリ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nome" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Memória" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "名称" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "记忆" } } } }, - "Name (no leading slash)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Name (ohne führenden Schrägstrich)" + "Memory Appendix (optional)" : { + + }, + "Memory block" : { + "comment" : "Section of the uninstall sheet that describes the memory block.", + "isCommentAutoGenerated" : true + }, + "Memory is managed by %@. File contents shown here may be stale." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Der Speicher wird von %@ verwaltet. Die hier angezeigten Dateiinhalte können veraltet sein." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Nombre (sin barra inicial)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La memoria la gestiona %@. El contenido de archivos mostrado aquí puede estar desactualizado." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Nom (sans barre oblique initiale)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "La mémoire est gérée par %@. Le contenu des fichiers affichés ici peut être obsolète." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "名前(先頭のスラッシュなし)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "メモリは %@ が管理します。ここに表示されるファイル内容は古い場合があります。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nome (sem barra inicial)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "A memória é gerenciada por %@. O conteúdo dos arquivos exibido aqui pode estar desatualizado." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "名称(不带前导斜杠)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "记忆由 %@ 管理。此处显示的文件内容可能已过时。" } } } }, - "Network": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Netzwerk" + "Message Hermes..." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes Nachricht senden..." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Red" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mensaje a Hermes..." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Réseau" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Envoyer un message à Hermes..." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ネットワーク" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes にメッセージを送信..." } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Rede" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enviar mensagem ao Hermes..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "网络" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "向 Hermes 发送消息..." } } } }, - "New Session": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Neue Sitzung" + "Messages will appear here as the conversation progresses." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachrichten erscheinen hier im Laufe der Unterhaltung." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Nueva sesión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Los mensajes aparecerán aquí a medida que avance la conversación." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Nouvelle session" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les messages apparaîtront ici au fur et à mesure de la conversation." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "新しいセッション" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "会話が進むにつれてメッセージがここに表示されます。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nova sessão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "As mensagens aparecerão aqui conforme a conversa progride." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "新建会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "消息将随对话进行显示在此处。" } } } }, - "New Webhook Subscription": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Neues Webhook-Abonnement" + "Metadata" : { + "comment" : "A heading for the metadata section of the template export sheet.", + "isCommentAutoGenerated" : true + }, + "Migrate" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Migrieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Nueva suscripción de webhook" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Migrar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Nouvel abonnement webhook" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Migrer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "新しい Webhook サブスクリプション" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "移行" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nova assinatura de webhook" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Migrar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "新建 Webhook 订阅" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "迁移" } } } }, - "New name for '%@'": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Neuer Name für '%@'" + "Missing required config:" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fehlende erforderliche Konfiguration:" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Nuevo nombre para '%@'" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Falta configuración requerida:" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Nouveau nom pour « %@ »" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuration requise manquante :" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "'%@' の新しい名前" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "必須設定が不足しています:" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Novo nome para '%@'" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuração obrigatória ausente:" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "'%@' 的新名称" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "缺少必需的配置:" } } } }, - "Next run: %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nächster Lauf: %@" + "Modal" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modal" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Próxima ejecución: %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modal" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Prochaine exécution : %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modale" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "次回実行: %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "モーダル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Próxima execução: %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modal" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "下次运行:%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "模态" } } } }, - "No AI provider credentials detected": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Anmeldedaten für KI-Anbieter erkannt" + "Model" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modell" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "No se detectaron credenciales de proveedor de IA" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modelo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun identifiant de fournisseur d'IA détecté" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modèle" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "AI プロバイダーの資格情報が検出されません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "モデル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhuma credencial de provedor de IA detectada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modelo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未检测到 AI 提供方凭证" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "模型" } } } }, - "No Active Session": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine aktive Sitzung" + "Model ID" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modell-ID" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin sesión activa" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID del modelo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune session active" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID du modèle" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティブなセッションなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "モデル ID" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem sessão ativa" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID do modelo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无活跃会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "模型 ID" } } } }, - "No Activity": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Aktivität" + "Models" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modelle" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin actividad" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modelos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune activité" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modèles" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティビティなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "モデル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem atividade" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modelos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无活动" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "模型" } } } }, - "No Cron Jobs": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Cron-Jobs" + "Monitor" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Überwachen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin tareas cron" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Monitor" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune tâche cron" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Surveiller" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Cron ジョブなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "モニター" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem tarefas cron" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Monitor" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无定时任务" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "监控" } } } }, - "No Dashboard": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Kein Dashboard" + "my_server" : { + + }, + "Name" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Name" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin panel" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nombre" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun tableau de bord" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nom" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ダッシュボードなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "名前" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem painel" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nome" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无仪表盘" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "名称" } } } }, - "No MCP servers configured": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine MCP-Server konfiguriert" + "Name (no leading slash)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Name (ohne führenden Schrägstrich)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "No hay servidores MCP configurados" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nombre (sin barra inicial)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun serveur MCP configuré" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nom (sans barre oblique initiale)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "MCP サーバーが設定されていません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "名前(先頭のスラッシュなし)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhum servidor MCP configurado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nome (sem barra inicial)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未配置 MCP 服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "名称(不带前导斜杠)" } } } }, - "No Models": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Modelle" + "Network" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Netzwerk" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin modelos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Red" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun modèle" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réseau" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "モデルなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ネットワーク" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem modelos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rede" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无模型" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "网络" } } } }, - "No Profiles": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Profile" + "New name for '%@'" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neuer Name für '%@'" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin perfiles" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nuevo nombre para '%@'" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun profil" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nouveau nom pour « %@ »" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロファイルなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "'%@' の新しい名前" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem perfis" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Novo nome para '%@'" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "'%@' 的新名称" } } } }, - "No Projects": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Projekte" + "New Session" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neue Sitzung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin proyectos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nueva sesión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun projet" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nouvelle session" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロジェクトなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "新しいセッション" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem projetos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nova sessão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无项目" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新建会话" } } } }, - "No Updates": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Updates" + "New Webhook Subscription" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neues Webhook-Abonnement" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin actualizaciones" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nueva suscripción de webhook" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune mise à jour" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nouvel abonnement webhook" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アップデートなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "新しい Webhook サブスクリプション" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem atualizações" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nova assinatura de webhook" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无更新" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "新建 Webhook 订阅" } } } }, - "No active session": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine aktive Sitzung" + "new-name" : { + + }, + "Next run: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nächster Lauf: %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin sesión activa" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Próxima ejecución: %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune session active" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prochaine exécution : %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティブなセッションなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "次回実行: %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem sessão ativa" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Próxima execução: %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无活跃会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "下次运行:%@" } } } }, - "No additional output. Check ~/.ssh/config and ssh-agent.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine weitere Ausgabe. Prüfe ~/.ssh/config und ssh-agent." + "No active session" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine aktive Sitzung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin salida adicional. Revisa ~/.ssh/config y ssh-agent." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin sesión activa" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune sortie supplémentaire. Vérifiez ~/.ssh/config et ssh-agent." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune session active" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "追加出力はありません。~/.ssh/config と ssh-agent を確認してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティブなセッションなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem saída adicional. Verifique ~/.ssh/config e ssh-agent." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem sessão ativa" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无额外输出。请检查 ~/.ssh/config 和 ssh-agent。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无活跃会话" } } } }, - "No commands available": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Befehle verfügbar" + "No Active Session" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine aktive Sitzung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "No hay comandos disponibles" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin sesión activa" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune commande disponible" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune session active" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "利用可能なコマンドはありません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティブなセッションなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem comandos disponíveis" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem sessão ativa" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无可用命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无活跃会话" } } } }, - "No credential pools configured": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Credential-Pools konfiguriert" + "No Activity" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Aktivität" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "No hay grupos de credenciales configurados" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin actividad" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun pool d'identifiants configuré" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune activité" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "資格情報プールが設定されていません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティビティなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhum pool de credenciais configurado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem atividade" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未配置凭证池" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无活动" } } } }, - "No data": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Daten" + "No additional output. Check ~/.ssh/config and ssh-agent." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine weitere Ausgabe. Prüfe ~/.ssh/config und ssh-agent." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin datos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin salida adicional. Revisa ~/.ssh/config y ssh-agent." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune donnée" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune sortie supplémentaire. Vérifiez ~/.ssh/config et ssh-agent." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "データなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "追加出力はありません。~/.ssh/config と ssh-agent を確認してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem dados" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem saída adicional. Verifique ~/.ssh/config e ssh-agent." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无数据" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无额外输出。请检查 ~/.ssh/config 和 ssh-agent。" } } } }, - "No env vars configured.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Umgebungsvariablen konfiguriert." + "No AI provider credentials detected" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Anmeldedaten für KI-Anbieter erkannt" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin variables de entorno configuradas." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se detectaron credenciales de proveedor de IA" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune variable d'environnement configurée." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun identifiant de fournisseur d'IA détecté" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "環境変数が設定されていません。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "AI プロバイダーの資格情報が検出されません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhuma variável de ambiente configurada." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhuma credencial de provedor de IA detectada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未配置环境变量。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未检测到 AI 提供方凭证" } } } }, - "No env vars. Add one with the button below.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Umgebungsvariablen. Füge eine mit der Schaltfläche unten hinzu." + "No commands available" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Befehle verfügbar" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin variables de entorno. Añade una con el botón de abajo." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No hay comandos disponibles" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune variable d'environnement. Ajoutez-en une avec le bouton ci-dessous." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune commande disponible" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "環境変数がありません。下のボタンで追加してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "利用可能なコマンドはありません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem variáveis de ambiente. Adicione uma com o botão abaixo." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem comandos disponíveis" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无环境变量。点击下方按钮添加。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无可用命令" } } } }, - "No headers configured.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Header konfiguriert." + "No configuration" : { + + }, + "No credential pools configured" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Credential-Pools konfiguriert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin cabeceras configuradas." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No hay grupos de credenciales configurados" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun en-tête configuré." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun pool d'identifiants configuré" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ヘッダーが設定されていません。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "資格情報プールが設定されていません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhum cabeçalho configurado." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhum pool de credenciais configurado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未配置请求头。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未配置凭证池" } } } }, - "No headers. Add one with the button below.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Header. Füge einen mit der Schaltfläche unten hinzu." + "No Cron Jobs" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Cron-Jobs" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin cabeceras. Añade una con el botón de abajo." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin tareas cron" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun en-tête. Ajoutez-en un avec le bouton ci-dessous." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune tâche cron" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ヘッダーがありません。下のボタンで追加してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cron ジョブなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem cabeçalhos. Adicione um com o botão abaixo." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem tarefas cron" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无请求头。点击下方按钮添加。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无定时任务" } } } }, - "No matching commands": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine passenden Befehle" + "No cron jobs found." : { + "comment" : "A message displayed when a user has not selected any cron jobs to include in their export.", + "isCommentAutoGenerated" : true + }, + "No Dashboard" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kein Dashboard" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin comandos coincidentes" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin panel" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune commande correspondante" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun tableau de bord" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "一致するコマンドなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ダッシュボードなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem comandos correspondentes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem painel" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无匹配命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无仪表盘" } } } }, - "No paired users": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine gekoppelten Nutzer" + "No data" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Daten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin usuarios vinculados" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin datos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun utilisateur appairé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune donnée" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ペア済みユーザーなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "データなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem usuários pareados" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem dados" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无配对用户" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无数据" } } } }, - "No platforms connected": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Plattformen verbunden" + "No env vars configured." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Umgebungsvariablen konfiguriert." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin plataformas conectadas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin variables de entorno configuradas." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune plateforme connectée" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune variable d'environnement configurée." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "接続されているプラットフォームなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "環境変数が設定されていません。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhuma plataforma conectada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhuma variável de ambiente configurada." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未连接平台" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未配置环境变量。" } } } }, - "No plugins installed": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Plugins installiert" + "No env vars. Add one with the button below." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Umgebungsvariablen. Füge eine mit der Schaltfläche unten hinzu." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin plugins instalados" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin variables de entorno. Añade una con el botón de abajo." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun plugin installé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune variable d'environnement. Ajoutez-en une avec le bouton ci-dessous." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プラグインがインストールされていません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "環境変数がありません。下のボタンで追加してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhum plugin instalado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem variáveis de ambiente. Adicione uma com o botão abaixo." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未安装插件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无环境变量。点击下方按钮添加。" } } } }, - "No quick commands configured": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Schnellbefehle konfiguriert" + "No fields" : { + "comment" : "A label that describes a template with no configuration fields.", + "isCommentAutoGenerated" : true + }, + "No headers configured." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Header konfiguriert." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin comandos rápidos configurados" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin cabeceras configuradas." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune commande rapide configurée" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun en-tête configuré." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "クイックコマンドが設定されていません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ヘッダーが設定されていません。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhum comando rápido configurado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhum cabeçalho configurado." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未配置快捷命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未配置请求头。" } } } }, - "No remote servers": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Remote-Server" + "No headers. Add one with the button below." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Header. Füge einen mit der Schaltfläche unten hinzu." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin servidores remotos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin cabeceras. Añade una con el botón de abajo." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun serveur distant" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun en-tête. Ajoutez-en un avec le bouton ci-dessous." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リモートサーバーなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ヘッダーがありません。下のボタンで追加してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem servidores remotos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem cabeçalhos. Adicione um com o botão abaixo." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无远程服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无请求头。点击下方按钮添加。" } } } }, - "No scheduled jobs configured": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine geplanten Jobs konfiguriert" + "No matching commands" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine passenden Befehle" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin tareas programadas configuradas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin comandos coincidentes" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune tâche planifiée configurée" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune commande correspondante" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "スケジュールされたジョブなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "一致するコマンドなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhuma tarefa agendada configurada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem comandos correspondentes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未配置定时任务" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无匹配命令" } } } }, - "No servers configured yet": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Noch keine Server konfiguriert" + "No MCP servers configured" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine MCP-Server konfiguriert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Aún no hay servidores configurados" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No hay servidores MCP configurados" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun serveur configuré pour l'instant" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun serveur MCP configuré" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "まだサーバーが設定されていません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "MCP サーバーが設定されていません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhum servidor configurado ainda" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhum servidor MCP configurado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "尚未配置服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未配置 MCP 服务器" } } } }, - "No sessions found": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Sitzungen gefunden" + "No Models" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Modelle" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "No se encontraron sesiones" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin modelos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucune session trouvée" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun modèle" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッションが見つかりません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "モデルなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhuma sessão encontrada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem modelos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未找到会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无模型" } } } }, - "No tool calls found": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Tool-Aufrufe gefunden" + "No paired users" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine gekoppelten Nutzer" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "No se encontraron llamadas a herramientas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin usuarios vinculados" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun appel d'outil trouvé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun utilisateur appairé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ツール呼び出しが見つかりません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ペア済みユーザーなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhuma chamada de ferramenta encontrada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem usuários pareados" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未找到工具调用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无配对用户" } } } }, - "No webhook subscriptions": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine Webhook-Abonnements" + "No per-agent instruction files found in the project root." : { + "comment" : "A message displayed when no agent-specific", + "isCommentAutoGenerated" : true + }, + "No platforms connected" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Plattformen verbunden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sin suscripciones de webhook" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin plataformas conectadas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun abonnement webhook" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune plateforme connectée" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Webhook サブスクリプションなし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "接続されているプラットフォームなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sem assinaturas de webhook" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhuma plataforma conectada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无 Webhook 订阅" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未连接平台" } } } }, - "None": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Keine" + "No plugins installed" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Plugins installiert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ninguno" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin plugins instalados" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Aucun" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun plugin installé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "なし" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プラグインがインストールされていません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nenhum" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhum plugin instalado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "无" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未安装插件" } } } }, - "Notable Sessions": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Bemerkenswerte Sitzungen" + "No Profiles" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Profile" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sesiones destacadas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin perfiles" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sessions notables" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun profil" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "注目のセッション" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロファイルなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sessões notáveis" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem perfis" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重要会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无配置" } } } }, - "OAuth": {}, - "OAuth 2.1": {}, - "OAuth login for %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "OAuth-Anmeldung für %@" + "No Projects" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Projekte" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Inicio de sesión OAuth para %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin proyectos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Connexion OAuth pour %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun projet" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ の OAuth ログイン" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロジェクトなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Login OAuth para %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem projetos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%@ 的 OAuth 登录" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无项目" } } } }, - "OK": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "OK" + "No quick commands configured" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Schnellbefehle konfiguriert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Aceptar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin comandos rápidos configurados" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "OK" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune commande rapide configurée" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "OK" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クイックコマンドが設定されていません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "OK" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhum comando rápido configurado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "确定" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未配置快捷命令" } } } }, - "Open BotFather": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "BotFather öffnen" + "No remote servers" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Remote-Server" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir BotFather" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin servidores remotos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir BotFather" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun serveur distant" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "BotFather を開く" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リモートサーバーなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Abrir BotFather" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem servidores remotos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开 BotFather" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无远程服务器" } } } }, - "Open Developer Portal": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Developer Portal öffnen" + "No scheduled jobs configured" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine geplanten Jobs konfiguriert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir Developer Portal" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin tareas programadas configuradas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir le Developer Portal" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune tâche planifiée configurée" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Developer Portal を開く" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スケジュールされたジョブなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Abrir Developer Portal" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhuma tarefa agendada configurada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开开发者门户" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未配置定时任务" } } } }, - "Open Local": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Lokal öffnen" + "No servers configured yet" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Noch keine Server konfiguriert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir local" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aún no hay servidores configurados" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir local" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun serveur configuré pour l'instant" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ローカルを開く" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "まだサーバーが設定されていません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Abrir local" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhum servidor configurado ainda" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开本地" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "尚未配置服务器" } } } }, - "Open Other Server…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Anderen Server öffnen…" + "No sessions found" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Sitzungen gefunden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir otro servidor…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se encontraron sesiones" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir un autre serveur…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune session trouvée" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "他のサーバーを開く…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッションが見つかりません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Abrir outro servidor…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhuma sessão encontrada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开其他服务器…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未找到会话" } } } }, - "Open Scarf": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Scarf öffnen" + "No skills found." : { + + }, + "No template loaded." : { + "comment" : "A message displayed when no template is loaded.", + "isCommentAutoGenerated" : true + }, + "No tool calls found" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Tool-Aufrufe gefunden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir Scarf" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se encontraron llamadas a herramientas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir Scarf" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun appel d'outil trouvé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Scarf を開く" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ツール呼び出しが見つかりません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Abrir Scarf" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhuma chamada de ferramenta encontrada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开 Scarf" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未找到工具调用" } } } }, - "Open Server": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Server öffnen" + "No Updates" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Updates" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir servidor" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin actualizaciones" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir le serveur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucune mise à jour" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サーバーを開く" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アップデートなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Abrir servidor" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem atualizações" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无更新" } } } }, - "Open Slack API": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Slack-API öffnen" + "No webhook subscriptions" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine Webhook-Abonnements" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir Slack API" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sin suscripciones de webhook" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir l'API Slack" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun abonnement webhook" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Slack API を開く" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook サブスクリプションなし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Abrir API do Slack" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sem assinaturas de webhook" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开 Slack API" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无 Webhook 订阅" } } } }, - "Open in Browser": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Im Browser öffnen" + "None" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Keine" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir en el navegador" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ninguno" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir dans le navigateur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ブラウザで開く" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "なし" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Abrir no navegador" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nenhum" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在浏览器中打开" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "无" } } } }, - "Open in Editor": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Im Editor öffnen" + "Notable Sessions" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bemerkenswerte Sitzungen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir en el editor" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sesiones destacadas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir dans l'éditeur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sessions notables" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エディタで開く" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "注目のセッション" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Abrir no editor" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sessões notáveis" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在编辑器中打开" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重要会话" } } } }, - "Open in new window": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "In neuem Fenster öffnen" + "npx" : { + + }, + "OAuth" : { + + }, + "OAuth 2.1" : { + + }, + "OAuth login for %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "OAuth-Anmeldung für %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir en nueva ventana" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inicio de sesión OAuth para %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir dans une nouvelle fenêtre" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Connexion OAuth pour %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "新しいウィンドウで開く" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ の OAuth ログイン" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Abrir em nova janela" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Login OAuth para %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在新窗口中打开" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ 的 OAuth 登录" } } } }, - "Open session": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sitzung öffnen" + "OK" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Abrir sesión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aceptar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ouvrir la session" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッションを開く" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Abrir sessão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "确定" } } } }, - "Optional": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Optional" + "One-line pitch" : { + "comment" : "A placeholder for a template's description.", + "isCommentAutoGenerated" : true + }, + "Open BotFather" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "BotFather öffnen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Opcional" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir BotFather" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Optionnel" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir BotFather" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "任意" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "BotFather を開く" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Opcional" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir BotFather" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "可选" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开 BotFather" } } } }, - "Optional — defaults to hostname": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Optional — Standard ist der Hostname" + "Open Developer Portal" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Developer Portal öffnen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Opcional — por defecto el nombre de host" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir Developer Portal" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Optionnel — par défaut : nom d'hôte" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir le Developer Portal" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "任意 — デフォルトはホスト名" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Developer Portal を開く" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Opcional — padrão é o nome do host" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir Developer Portal" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "可选 — 默认为主机名" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开开发者门户" } } } }, - "Optionally focus the summary on a specific topic. Leave blank to compress evenly.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Fokussiere die Zusammenfassung optional auf ein bestimmtes Thema. Leer lassen, um gleichmäßig zu komprimieren." + "Open in Browser" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Im Browser öffnen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Opcionalmente, enfoca el resumen en un tema específico. Déjalo vacío para comprimir uniformemente." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir en el navegador" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Centrez éventuellement le résumé sur un sujet précis. Laissez vide pour compresser uniformément." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir dans le navigateur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "要約を特定のトピックに絞ることができます。均等に圧縮するには空のままにしてください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ブラウザで開く" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Opcionalmente, foque o resumo em um tópico específico. Deixe em branco para compactar uniformemente." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir no navegador" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "可选地将摘要聚焦到特定主题。留空则均匀压缩。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在浏览器中打开" } } } }, - "Other": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Andere" + "Open in Editor" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Im Editor öffnen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Otro" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir en el editor" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Autre" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir dans l'éditeur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "その他" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "エディタで開く" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Outro" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir no editor" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "其他" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在编辑器中打开" } } } }, - "Output": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ausgabe" + "Open in new window" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "In neuem Fenster öffnen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Salida" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir en nueva ventana" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sortie" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir dans une nouvelle fenêtre" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "出力" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "新しいウィンドウで開く" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Saída" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir em nova janela" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "输出" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在新窗口中打开" } } } }, - "Overview": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Übersicht" + "Open Local" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lokal öffnen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Resumen" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir local" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Vue d'ensemble" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir local" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "概要" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ローカルを開く" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Visão geral" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir local" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "概览" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开本地" } } } }, - "PID %d": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "PID %d" + "Open Other Server…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anderen Server öffnen…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "PID %d" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir otro servidor…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "PID %d" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir un autre serveur…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "PID %d" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "他のサーバーを開く…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "PID %d" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir outro servidor…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "PID %d" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开其他服务器…" } } } }, - "PID %lld": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "PID %lld" + "Open Project" : { + "comment" : "A button that opens a project.", + "isCommentAutoGenerated" : true + }, + "Open Scarf" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf öffnen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "PID %lld" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir Scarf" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "PID %lld" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir Scarf" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "PID %lld" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf を開く" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "PID %lld" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir Scarf" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "PID %lld" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开 Scarf" } } } }, - "Pair Device": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Gerät koppeln" + "Open Server" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server öffnen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Emparejar dispositivo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir servidor" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Appairer l'appareil" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir le serveur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "デバイスをペアリング" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サーバーを開く" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Parear dispositivo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir servidor" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "配对设备" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开服务器" } } } }, - "Paired Users": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Gekoppelte Nutzer" + "Open session" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sitzung öffnen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Usuarios emparejados" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir sesión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Utilisateurs appairés" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir la session" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ペア済みユーザー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッションを開く" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Usuários pareados" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir sessão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已配对用户" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开会话" } } } }, - "Paste code here…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Code hier einfügen…" + "Open Slack API" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Slack-API öffnen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Pega el código aquí…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir Slack API" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Collez le code ici…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir l'API Slack" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ここにコードを貼り付け…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Slack API を開く" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Cole o código aqui…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir API do Slack" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在此粘贴代码…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开 Slack API" } } } }, - "Paths": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Pfade" + "Opens on launch" : { + "comment" : "A tooltip for the star button in the Manage Servers view.", + "isCommentAutoGenerated" : true + }, + "Optional" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Optional" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Rutas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opcional" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Chemins" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Optionnel" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "パス" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "任意" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Caminhos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opcional" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "路径" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "可选" } } } }, - "Pause": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Pausieren" + "Optional — defaults to hostname" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Optional — Standard ist der Hostname" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Pausar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opcional — por defecto el nombre de host" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Pause" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Optionnel — par défaut : nom d'hôte" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "一時停止" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "任意 — デフォルトはホスト名" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Pausar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opcional — padrão é o nome do host" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "暂停" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "可选 — 默认为主机名" } } } }, - "Pending Approvals": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ausstehende Genehmigungen" + "Optionally focus the summary on a specific topic. Leave blank to compress evenly." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fokussiere die Zusammenfassung optional auf ein bestimmtes Thema. Leer lassen, um gleichmäßig zu komprimieren." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Aprobaciones pendientes" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opcionalmente, enfoca el resumen en un tema específico. Déjalo vacío para comprimir uniformemente." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Approbations en attente" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Centrez éventuellement le résumé sur un sujet précis. Laissez vide pour compresser uniformément." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "承認待ち" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "要約を特定のトピックに絞ることができます。均等に圧縮するには空のままにしてください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Aprovações pendentes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Opcionalmente, foque o resumo em um tópico específico. Deixe em branco para compactar uniformemente." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "待批准" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "可选地将摘要聚焦到特定主题。留空则均匀压缩。" } } } }, - "Per-route subscriptions (events, prompt template, delivery target) are managed in the Webhooks sidebar — not here. This panel only controls whether the webhook platform is listening at all.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Abonnements pro Route (Ereignisse, Prompt-Vorlage, Zustellziel) werden in der Webhooks-Seitenleiste verwaltet — nicht hier. Dieses Panel steuert nur, ob die Webhook-Plattform überhaupt zuhört." + "Other" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Andere" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Las suscripciones por ruta (eventos, plantilla de prompt, destino de entrega) se gestionan en la barra lateral de Webhooks — no aquí. Este panel solo controla si la plataforma de webhooks está escuchando." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Otro" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les abonnements par route (événements, modèle de prompt, cible de livraison) sont gérés dans la barre latérale Webhooks — pas ici. Ce panneau contrôle uniquement si la plateforme webhook écoute." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Autre" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ルートごとのサブスクリプション(イベント、プロンプトテンプレート、配信先)は Webhooks サイドバーで管理します — ここではありません。このパネルは webhook プラットフォームが待ち受けるかどうかのみを制御します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "その他" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "As assinaturas por rota (eventos, template de prompt, alvo de entrega) são gerenciadas na barra lateral de Webhooks — não aqui. Este painel só controla se a plataforma de webhook está escutando." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Outro" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "按路由的订阅(事件、提示模板、投递目标)在 Webhooks 侧边栏中管理 — 不在此处。本面板仅控制 webhook 平台是否监听。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "其他" } } } }, - "Period": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Zeitraum" + "Output" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausgabe" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Período" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Salida" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Période" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sortie" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "期間" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "出力" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Período" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Saída" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "周期" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "输出" } } } }, - "Personalities": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Persönlichkeiten" + "Overview" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Übersicht" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Personalidades" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resumen" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Personnalités" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vue d'ensemble" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "パーソナリティ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "概要" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Personalidades" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visão geral" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "人格" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "概览" } } } }, - "Personality": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Persönlichkeit" + "owner/name" : { + "comment" : "A label for the template's owner and name.", + "isCommentAutoGenerated" : true + }, + "Pair Device" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gerät koppeln" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Personalidad" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Emparejar dispositivo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Personnalité" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Appairer l'appareil" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "パーソナリティ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "デバイスをペアリング" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Personalidade" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parear dispositivo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "人格" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配对设备" } } } }, - "Pick an MCP server to add.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wähle einen MCP-Server zum Hinzufügen." + "Paired Users" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gekoppelte Nutzer" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige un servidor MCP para añadir." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usuarios emparejados" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez un serveur MCP à ajouter." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Utilisateurs appairés" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "追加する MCP サーバーを選択してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ペア済みユーザー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Escolha um servidor MCP para adicionar." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usuários pareados" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择要添加的 MCP 服务器。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已配对用户" } } } }, - "Pick one from the list, or add a new server from the toolbar.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wähle einen aus der Liste oder füge über die Symbolleiste einen neuen Server hinzu." + "Paste an https URL pointing at a .scarftemplate file." : { + "comment" : "A description of the URL field in the template installation prompt.", + "isCommentAutoGenerated" : true + }, + "Paste code here…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Code hier einfügen…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Elige uno de la lista o añade un nuevo servidor desde la barra de herramientas." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pega el código aquí…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Choisissez-en un dans la liste ou ajoutez un nouveau serveur depuis la barre d'outils." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Collez le code ici…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リストから選ぶか、ツールバーから新しいサーバーを追加してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ここにコードを貼り付け…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Escolha um da lista ou adicione um novo servidor pela barra de ferramentas." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cole o código aqui…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从列表中选择,或从工具栏添加新服务器。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在此粘贴代码…" } } } }, - "Platforms": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Plattformen" + "Paths" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pfade" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Plataformas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rutas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Plateformes" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chemins" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プラットフォーム" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "パス" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Plataformas" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Caminhos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "平台" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "路径" } } } }, - "Plugins": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Plugins" + "Pause" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pausieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Plugins" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pausar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Plugins" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プラグイン" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "一時停止" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Plugins" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pausar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "插件" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "暂停" } } } }, - "Plugins extend hermes with custom tools, providers, or memory backends.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Plugins erweitern hermes um eigene Tools, Anbieter oder Speicher-Backends." + "Pending Approvals" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausstehende Genehmigungen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Los plugins extienden hermes con herramientas, proveedores o backends de memoria personalizados." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aprobaciones pendientes" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les plugins étendent hermes avec des outils, fournisseurs ou backends mémoire personnalisés." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Approbations en attente" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プラグインは、カスタムツール、プロバイダー、メモリバックエンドで hermes を拡張します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "承認待ち" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Plugins estendem o hermes com ferramentas, provedores ou backends de memória personalizados." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aprovações pendentes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "插件通过自定义工具、提供方或记忆后端扩展 hermes。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "待批准" } } } }, - "Pre-Run Script": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Vorab-Skript" + "Per-route subscriptions (events, prompt template, delivery target) are managed in the Webhooks sidebar — not here. This panel only controls whether the webhook platform is listening at all." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abonnements pro Route (Ereignisse, Prompt-Vorlage, Zustellziel) werden in der Webhooks-Seitenleiste verwaltet — nicht hier. Dieses Panel steuert nur, ob die Webhook-Plattform überhaupt zuhört." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Script previo a la ejecución" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Las suscripciones por ruta (eventos, plantilla de prompt, destino de entrega) se gestionan en la barra lateral de Webhooks — no aquí. Este panel solo controla si la plataforma de webhooks está escuchando." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Script de pré-exécution" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les abonnements par route (événements, modèle de prompt, cible de livraison) sont gérés dans la barre latérale Webhooks — pas ici. Ce panneau contrôle uniquement si la plateforme webhook écoute." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "事前実行スクリプト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ルートごとのサブスクリプション(イベント、プロンプトテンプレート、配信先)は Webhooks サイドバーで管理します — ここではありません。このパネルは webhook プラットフォームが待ち受けるかどうかのみを制御します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Script de pré-execução" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "As assinaturas por rota (eventos, template de prompt, alvo de entrega) são gerenciadas na barra lateral de Webhooks — não aqui. Este painel só controla se a plataforma de webhook está escutando." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "运行前脚本" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按路由的订阅(事件、提示模板、投递目标)在 Webhooks 侧边栏中管理 — 不在此处。本面板仅控制 webhook 平台是否监听。" } } } }, - "Preset:": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Voreinstellung:" + "Period" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zeitraum" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Preajuste:" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Período" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Préréglage :" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Période" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プリセット:" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "期間" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Predefinição:" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Período" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "预设:" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "周期" } } } }, - "Probe": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Prüfen" + "Personalities" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Persönlichkeiten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Probar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalidades" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sonder" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personnalités" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プローブ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "パーソナリティ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sondar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalidades" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "探测" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "人格" } } } }, - "Profile": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Profil" + "Personality" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Persönlichkeit" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Perfil" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalidad" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Profil" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personnalité" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロファイル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "パーソナリティ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Perfil" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Personalidade" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "人格" } } } }, - "Profiles": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Profile" + "Pick an MCP server to add." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wähle einen MCP-Server zum Hinzufügen." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Perfiles" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elige un servidor MCP para añadir." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Profils" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez un serveur MCP à ajouter." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロファイル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "追加する MCP サーバーを選択してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Perfis" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Escolha um servidor MCP para adicionar." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择要添加的 MCP 服务器。" } } } }, - "Project Name": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Projektname" + "Pick one from the list, or add a new server from the toolbar." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wähle einen aus der Liste oder füge über die Symbolleiste einen neuen Server hinzu." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Nombre del proyecto" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elige uno de la lista o añade un nuevo servidor desde la barra de herramientas." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Nom du projet" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez-en un dans la liste ou ajoutez un nouveau serveur depuis la barre d'outils." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロジェクト名" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リストから選ぶか、ツールバーから新しいサーバーを追加してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nome do projeto" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Escolha um da lista ou adicione um novo servidor pela barra de ferramentas." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "项目名称" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从列表中选择,或从工具栏添加新服务器。" } } } }, - "Project Path": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Projektpfad" + "PID %d" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %d" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ruta del proyecto" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %d" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Chemin du projet" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %d" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロジェクトパス" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %d" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Caminho do projeto" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %d" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "项目路径" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %d" } } } }, - "Projects": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Projekte" + "PID %lld" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %lld" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Proyectos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %lld" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Projets" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %lld" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロジェクト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %lld" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Projetos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %lld" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "项目" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "PID %lld" } } } }, - "Prompt": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Prompt" + "Platforms" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plattformen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Prompt" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plataformas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Prompt" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plateformes" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロンプト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プラットフォーム" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Prompt" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plataformas" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "提示" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "平台" } } } }, - "Provide a Git URL (https://github.com/...) or a shorthand like `owner/repo`.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Gib eine Git-URL (https://github.com/...) oder ein Kürzel wie `owner/repo` an." + "Plugins" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plugins" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Proporciona una URL de Git (https://github.com/...) o una forma corta como `owner/repo`." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plugins" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Fournissez une URL Git (https://github.com/...) ou un raccourci comme `owner/repo`." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plugins" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Git URL(https://github.com/...)または `owner/repo` のような省略形を指定してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プラグイン" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Forneça uma URL do Git (https://github.com/...) ou um atalho como `owner/repo`." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plugins" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "提供 Git URL(https://github.com/...)或简写如 `owner/repo`。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "插件" } } } }, - "Provider": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Anbieter" + "Plugins extend hermes with custom tools, providers, or memory backends." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plugins erweitern hermes um eigene Tools, Anbieter oder Speicher-Backends." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Proveedor" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Los plugins extienden hermes con herramientas, proveedores o backends de memoria personalizados." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Fournisseur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les plugins étendent hermes avec des outils, fournisseurs ou backends mémoire personnalisés." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロバイダー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プラグインは、カスタムツール、プロバイダー、メモリバックエンドで hermes を拡張します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Provedor" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plugins estendem o hermes com ferramentas, provedores ou backends de memória personalizados." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "提供方" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "插件通过自定义工具、提供方或记忆后端扩展 hermes。" } } } }, - "Push to Talk": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Push-to-Talk" + "Pre-Run Script" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vorab-Skript" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Pulsar para hablar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Script previo a la ejecución" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Push-to-Talk" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Script de pré-exécution" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "押して話す" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "事前実行スクリプト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Pressionar para falar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Script de pré-execução" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "按住说话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "运行前脚本" } } } }, - "Push to talk (Ctrl+B)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Push-to-Talk (Strg+B)" + "Preparing…" : { + + }, + "Preset:" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voreinstellung:" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Pulsar para hablar (Ctrl+B)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preajuste:" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Push-to-Talk (Ctrl+B)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Préréglage :" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "押して話す (Ctrl+B)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プリセット:" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Pressionar para falar (Ctrl+B)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Predefinição:" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "按住说话 (Ctrl+B)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "预设:" } } } }, - "Push-to-Talk": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Push-to-Talk" + "Probe" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prüfen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Pulsar para hablar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Probar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Push-to-Talk" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sonder" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プッシュ・トゥ・トーク" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プローブ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Pressionar para falar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sondar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "按住说话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "探测" } } } }, - "Quick Commands": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Schnellbefehle" + "Profile" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comandos rápidos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Perfil" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Commandes rapides" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "クイックコマンド" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロファイル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Comandos rápidos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Perfil" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "快捷命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置" } } } }, - "Quick commands are shell shortcuts hermes exposes in chat as `/command_name`. They live under `quick_commands:` in config.yaml.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Schnellbefehle sind Shell-Shortcuts, die hermes im Chat als `/command_name` verfügbar macht. Sie stehen unter `quick_commands:` in config.yaml." + "Profiles" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profile" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Los comandos rápidos son atajos de shell que hermes expone en el chat como `/command_name`. Viven bajo `quick_commands:` en config.yaml." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Perfiles" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les commandes rapides sont des raccourcis shell que hermes expose dans le chat sous la forme `/command_name`. Elles se trouvent sous `quick_commands:` dans config.yaml." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profils" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "クイックコマンドは、hermes がチャットで `/command_name` として公開するシェルショートカットです。config.yaml の `quick_commands:` 以下に記述します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロファイル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Comandos rápidos são atalhos de shell que o hermes expõe no chat como `/command_name`. Ficam em `quick_commands:` no config.yaml." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Perfis" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "快捷命令是 hermes 在聊天中以 `/command_name` 形式暴露的 shell 快捷方式。它们位于 config.yaml 的 `quick_commands:` 下。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "配置" } } } }, - "Quit Scarf": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Scarf beenden" + "Project directory" : { + "comment" : "Title of a section in the template uninstall sheet that lists files in the project directory.", + "isCommentAutoGenerated" : true + }, + "Project directory stays — it still holds files you created after install." : { + + }, + "Project directory will also be removed (nothing user-owned left inside)." : { + + }, + "Project folder kept" : { + + }, + "Project Name" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Projektname" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Salir de Scarf" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nombre del proyecto" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Quitter Scarf" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nom du projet" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Scarf を終了" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロジェクト名" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sair do Scarf" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nome do projeto" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "退出 Scarf" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "项目名称" } } } }, - "Raw Config": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Rohkonfiguration" + "Project Path" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Projektpfad" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Configuración en bruto" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ruta del proyecto" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Configuration brute" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chemin du projet" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "生の設定" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロジェクトパス" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Configuração bruta" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Caminho do projeto" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "原始配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "项目路径" } } } }, - "Raw remote output (for debugging)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Rohdaten der Remote-Ausgabe (zum Debuggen)" + "Projects" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Projekte" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Salida remota en bruto (para depurar)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proyectos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sortie distante brute (pour le débogage)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Projets" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "生のリモート出力(デバッグ用)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロジェクト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Saída remota bruta (para depuração)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Projetos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "远程原始输出(用于调试)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "项目" } } } }, - "Re-run": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erneut ausführen" + "Projects registry" : { + "comment" : "Section title for the section that lists the projects registry.", + "isCommentAutoGenerated" : true + }, + "Prompt" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prompt" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Volver a ejecutar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prompt" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Relancer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prompt" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "再実行" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロンプト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Re-executar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prompt" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重新运行" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "提示" } } } }, - "Read": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Lesen" + "Provide a Git URL (https://github.com/...) or a shorthand like `owner/repo`." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gib eine Git-URL (https://github.com/...) oder ein Kürzel wie `owner/repo` an." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Leer" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proporciona una URL de Git (https://github.com/...) o una forma corta como `owner/repo`." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Lire" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fournissez une URL Git (https://github.com/...) ou un raccourci comme `owner/repo`." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "読み取り" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Git URL(https://github.com/...)または `owner/repo` のような省略形を指定してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ler" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Forneça uma URL do Git (https://github.com/...) ou um atalho como `owner/repo`." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "读取" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "提供 Git URL(https://github.com/...)或简写如 `owner/repo`。" } } } }, - "Reasoning": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Reasoning" + "Provider" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anbieter" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Razonamiento" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proveedor" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Raisonnement" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fournisseur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "推論" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロバイダー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Raciocínio" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Provedor" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "推理" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "提供方" } } } }, - "Recent Sessions": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Letzte Sitzungen" + "Push to Talk" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Push-to-Talk" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sesiones recientes" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pulsar para hablar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sessions récentes" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Push-to-Talk" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "最近のセッション" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "押して話す" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sessões recentes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pressionar para falar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "最近会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按住说话" } } } }, - "Reconnect": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erneut verbinden" + "Push to talk (Ctrl+B)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Push-to-Talk (Strg+B)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Reconectar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pulsar para hablar (Ctrl+B)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Reconnecter" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Push-to-Talk (Ctrl+B)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "再接続" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "押して話す (Ctrl+B)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Reconectar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pressionar para falar (Ctrl+B)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重新连接" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按住说话 (Ctrl+B)" } } } }, - "Recording…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nehme auf…" + "Push-to-Talk" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Push-to-Talk" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Grabando…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pulsar para hablar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Enregistrement…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Push-to-Talk" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "録音中…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プッシュ・トゥ・トーク" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Gravando…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pressionar para falar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "录制中…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "按住说话" } } } }, - "Redaction": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Redaktion" + "Quick Commands" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schnellbefehle" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Censura" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comandos rápidos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Rédaction" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Commandes rapides" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "秘匿化" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クイックコマンド" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Redação" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comandos rápidos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "脱敏" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷命令" } } } }, - "Refresh": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktualisieren" + "Quick commands are shell shortcuts hermes exposes in chat as `/command_name`. They live under `quick_commands:` in config.yaml." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schnellbefehle sind Shell-Shortcuts, die hermes im Chat als `/command_name` verfügbar macht. Sie stehen unter `quick_commands:` in config.yaml." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Actualizar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Los comandos rápidos son atajos de shell que hermes expone en el chat como `/command_name`. Viven bajo `quick_commands:` en config.yaml." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Actualiser" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les commandes rapides sont des raccourcis shell que hermes expose dans le chat sous la forme `/command_name`. Elles se trouvent sous `quick_commands:` dans config.yaml." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "更新" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クイックコマンドは、hermes がチャットで `/command_name` として公開するシェルショートカットです。config.yaml の `quick_commands:` 以下に記述します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Atualizar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comandos rápidos são atalhos de shell que o hermes expõe no chat como `/command_name`. Ficam em `quick_commands:` no config.yaml." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "刷新" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "快捷命令是 hermes 在聊天中以 `/command_name` 形式暴露的 shell 快捷方式。它们位于 config.yaml 的 `quick_commands:` 下。" } } } }, - "Reload": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Neu laden" + "Quit Scarf" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf beenden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Recargar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Salir de Scarf" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Recharger" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Quitter Scarf" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "再読み込み" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf を終了" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Recarregar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sair do Scarf" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重新加载" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "退出 Scarf" } } } }, - "Remote (HTTP)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Remote (HTTP)" + "Raw Config" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rohkonfiguration" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Remoto (HTTP)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuración en bruto" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Distant (HTTP)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuration brute" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リモート (HTTP)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "生の設定" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Remoto (HTTP)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuração bruta" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "远程 (HTTP)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "原始配置" } } } }, - "Remote Diagnostics — %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Remote-Diagnose — %@" + "Raw remote output (for debugging)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rohdaten der Remote-Ausgabe (zum Debuggen)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Diagnósticos remotos — %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Salida remota en bruto (para depurar)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Diagnostics distants — %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sortie distante brute (pour le débogage)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リモート診断 — %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "生のリモート出力(デバッグ用)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Diagnóstico remoto — %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Saída remota bruta (para depuração)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "远程诊断 — %@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "远程原始输出(用于调试)" } } } }, - "Remove": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Entfernen" + "Re-run" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erneut ausführen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Quitar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Volver a ejecutar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Retirer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Relancer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "削除" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "再実行" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Remover" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Re-executar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "移除" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新运行" } } } }, - "Remove %@?": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "%@ entfernen?" + "Read" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lesen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "¿Quitar %@?" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Leer" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Retirer %@ ?" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lire" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ を削除しますか?" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "読み取り" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Remover %@?" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ler" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "移除 %@?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "读取" } } } }, - "Remove credential for %@?": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Anmeldedaten für %@ entfernen?" + "Reading template.lock.json…" : { + "comment" : "Text displayed in a progress view while the template is being read.", + "isCommentAutoGenerated" : true + }, + "Reasoning" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reasoning" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "¿Quitar credencial de %@?" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Razonamiento" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Retirer les identifiants pour %@ ?" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Raisonnement" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ の資格情報を削除しますか?" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "推論" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Remover credencial de %@?" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Raciocínio" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "移除 %@ 的凭证?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "推理" } } } }, - "Remove this server from Scarf.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Diesen Server aus Scarf entfernen." + "Recent Sessions" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Letzte Sitzungen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Quitar este servidor de Scarf." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sesiones recientes" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Retirer ce serveur de Scarf." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sessions récentes" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "このサーバーを Scarf から削除します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "最近のセッション" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Remover este servidor do Scarf." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sessões recentes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从 Scarf 中移除此服务器。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "最近会话" } } } }, - "Remove this server?": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Diesen Server entfernen?" + "Recommended model" : { + "comment" : "A label that indicates a recommended model.", + "isCommentAutoGenerated" : true + }, + "Reconnect" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erneut verbinden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "¿Quitar este servidor?" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reconectar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Retirer ce serveur ?" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reconnecter" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "このサーバーを削除しますか?" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "再接続" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Remover este servidor?" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reconectar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "移除此服务器?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新连接" } } } }, - "Remove via config.yaml…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Über config.yaml entfernen…" + "Recording…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nehme auf…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Quitar vía config.yaml…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Grabando…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Retirer via config.yaml…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enregistrement…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "config.yaml 経由で削除…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "録音中…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Remover via config.yaml…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gravando…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "通过 config.yaml 移除…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "录制中…" } } } }, - "Remove webhook %@?": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Webhook %@ entfernen?" + "Redaction" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redaktion" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "¿Quitar webhook %@?" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Censura" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Retirer le webhook %@ ?" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rédaction" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "webhook %@ を削除しますか?" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "秘匿化" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Remover webhook %@?" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redação" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "移除 webhook %@?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "脱敏" } } } }, - "Rename": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Umbenennen" + "Refresh" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualisieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Renombrar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Renommer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualiser" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "名前を変更" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Renomear" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atualizar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重命名" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "刷新" } } } }, - "Rename Profile": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Profil umbenennen" + "Reload" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neu laden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Renombrar perfil" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recargar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Renommer le profil" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recharger" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロファイル名を変更" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "再読み込み" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Renomear perfil" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recarregar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重命名配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重新加载" } } } }, - "Rename Session": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sitzung umbenennen" + "Remote (HTTP)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remote (HTTP)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Renombrar sesión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remoto (HTTP)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Renommer la session" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Distant (HTTP)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッション名を変更" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リモート (HTTP)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Renomear sessão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remoto (HTTP)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重命名会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "远程 (HTTP)" } } } }, - "Rename...": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Umbenennen..." + "Remote Diagnostics — %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remote-Diagnose — %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Renombrar..." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnósticos remotos — %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Renommer..." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnostics distants — %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "名前を変更..." + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リモート診断 — %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Renomear..." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnóstico remoto — %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重命名..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "远程诊断 — %@" } } } }, - "Required": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erforderlich" + "remove" : { + + }, + "Remove" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entfernen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Obligatorio" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Quitar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Requis" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retirer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "必須" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "削除" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Obrigatório" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remover" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "必需" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "移除" } } } }, - "Required Tokens": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erforderliche Tokens" + "Remove “%@”" : { + "comment" : "A title that shows the name of a template, followed by its version and a unique identifier.", + "isCommentAutoGenerated" : true + }, + "Remove \"%@\" from Scarf's project list" : { + "comment" : "A label that instructs the user to remove a project from Scarf's list of installed projects.", + "isCommentAutoGenerated" : true + }, + "Remove %@ from Scarf's project list (files are kept on disk)" : { + "comment" : "A confirmation dialog that", + "isCommentAutoGenerated" : true + }, + "Remove %@?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ entfernen?" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Tokens requeridos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Quitar %@?" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Jetons requis" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retirer %@ ?" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "必須トークン" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ を削除しますか?" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Tokens obrigatórios" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remover %@?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "必需令牌" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "移除 %@?" } } } }, - "Requires: %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erfordert: %@" + "Remove credential for %@?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anmeldedaten für %@ entfernen?" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Requiere: %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Quitar credencial de %@?" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Nécessite : %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retirer les identifiants pour %@ ?" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "必須: %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ の資格情報を削除しますか?" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Requer: %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remover credencial de %@?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "需要:%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "移除 %@ 的凭证?" } } } }, - "Reset Cooldowns": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Cooldowns zurücksetzen" + "Remove from List" : { + "comment" : "A confirmation dialog that asks whether a user is sure they want to remove a project from Scarf's list.", + "isCommentAutoGenerated" : true + }, + "Remove from List (keep files)…" : { + "comment" : "A button that removes a project from Scarf's list, but not from disk.", + "isCommentAutoGenerated" : true + }, + "Remove from Scarf's project list?" : { + "comment" : "Title of a dialog that asks the user to confirm removing a project from Scarf's project list.", + "isCommentAutoGenerated" : true + }, + "Remove the entire namespace dir recursively" : { + "comment" : "A description of a template uninstall action.", + "isCommentAutoGenerated" : true + }, + "Remove this server from Scarf." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diesen Server aus Scarf entfernen." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Restablecer enfriamientos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Quitar este servidor de Scarf." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Réinitialiser les temps de repos" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retirer ce serveur de Scarf." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "クールダウンをリセット" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "このサーバーを Scarf から削除します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Redefinir cooldowns" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remover este servidor do Scarf." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重置冷却" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从 Scarf 中移除此服务器。" } } } }, - "Restart": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Neu starten" + "Remove this server?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diesen Server entfernen?" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Reiniciar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Quitar este servidor?" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Redémarrer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retirer ce serveur ?" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "再起動" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "このサーバーを削除しますか?" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Reiniciar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remover este servidor?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重启" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "移除此服务器?" } } } }, - "Restart Gateway": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Gateway neu starten" + "Remove via config.yaml…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Über config.yaml entfernen…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Reiniciar gateway" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Quitar vía config.yaml…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Redémarrer le gateway" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retirer via config.yaml…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Gateway を再起動" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "config.yaml 経由で削除…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Reiniciar gateway" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remover via config.yaml…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重启 Gateway" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过 config.yaml 移除…" } } } }, - "Restart Hermes": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hermes neu starten" + "Remove webhook %@?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook %@ entfernen?" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Reiniciar Hermes" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Quitar webhook %@?" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Redémarrer Hermes" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retirer le webhook %@ ?" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Hermes を再起動" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "webhook %@ を削除しますか?" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Reiniciar Hermes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remover webhook %@?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重启 Hermes" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "移除 webhook %@?" } } } }, - "Restart Now": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Jetzt neu starten" + "Removed %@" : { + "comment" : "A title that says \"Removed\" followed by the name of a project.", + "isCommentAutoGenerated" : true + }, + "Removing…" : { + "comment" : "Text displayed in a progress view when the template uninstall is in progress.", + "isCommentAutoGenerated" : true + }, + "Rename" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Umbenennen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Reiniciar ahora" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renombrar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Redémarrer maintenant" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renommer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "今すぐ再起動" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "名前を変更" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Reiniciar agora" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renomear" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "立即重启" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重命名" } } } }, - "Restore": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wiederherstellen" + "Rename Profile" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil umbenennen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Restaurar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renombrar perfil" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Restaurer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renommer le profil" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "復元" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロファイル名を変更" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Restaurar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renomear perfil" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "恢复" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重命名配置" } } } }, - "Restore from backup?": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aus Backup wiederherstellen?" + "Rename Session" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sitzung umbenennen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "¿Restaurar desde copia de seguridad?" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renombrar sesión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Restaurer depuis la sauvegarde ?" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renommer la session" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "バックアップから復元しますか?" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッション名を変更" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Restaurar a partir do backup?" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renomear sessão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从备份恢复?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重命名会话" } } } }, - "Restore…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wiederherstellen…" + "Rename..." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Umbenennen..." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Restaurar…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renombrar..." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Restaurer…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renommer..." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "復元…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "名前を変更..." } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Restaurar…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Renomear..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "恢复…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重命名..." } } } }, - "Result": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ergebnis" + "required" : { + + }, + "Required" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erforderlich" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Resultado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obligatorio" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Résultat" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Requis" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "結果" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "必須" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Resultado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obrigatório" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "结果" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "必需" } } } }, - "Resume": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Fortsetzen" + "Required Files" : { + + }, + "Required Tokens" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erforderliche Tokens" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Reanudar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tokens requeridos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Reprendre" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jetons requis" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "再開" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "必須トークン" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Retomar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tokens obrigatórios" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "继续" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "必需令牌" } } } }, - "Resume Session": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sitzung fortsetzen" + "Requires: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erfordert: %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Reanudar sesión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Requiere: %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Reprendre la session" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nécessite : %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッションを再開" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "必須: %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Retomar sessão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Requer: %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "恢复会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "需要:%@" } } } }, - "Retry": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erneut versuchen" + "Reset Cooldowns" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cooldowns zurücksetzen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Reintentar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Restablecer enfriamientos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Réessayer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réinitialiser les temps de repos" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "再試行" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "クールダウンをリセット" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Tentar novamente" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redefinir cooldowns" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "重试" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重置冷却" } } } }, - "Return to Active Session (%@...)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Zurück zur aktiven Sitzung (%@...)" + "Restart" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Neu starten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Volver a sesión activa (%@...)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reiniciar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Retour à la session active (%@...)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redémarrer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティブセッションに戻る (%@...)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "再起動" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Voltar à sessão ativa (%@...)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reiniciar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "返回活跃会话 (%@...)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重启" } } } }, - "Reveal": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Anzeigen" + "Restart Gateway" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway neu starten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Mostrar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reiniciar gateway" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Afficher" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redémarrer le gateway" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gateway を再起動" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Revelar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reiniciar gateway" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重启 Gateway" } } } }, - "Revoke": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Widerrufen" + "Restart Hermes" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes neu starten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Revocar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reiniciar Hermes" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Révoquer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redémarrer Hermes" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "取り消し" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes を再起動" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Revogar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reiniciar Hermes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "撤销" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重启 Hermes" } } } }, - "Rich Chat": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Rich Chat" + "Restart Now" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jetzt neu starten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Chat enriquecido" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reiniciar ahora" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Chat enrichi" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Redémarrer maintenant" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リッチチャット" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今すぐ再起動" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Chat avançado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reiniciar agora" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "富文本聊天" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "立即重启" } } } }, - "Run Diagnostics…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Diagnose ausführen…" + "Restore" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wiederherstellen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ejecutar diagnósticos…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Restaurar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Lancer les diagnostics…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Restaurer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "診断を実行…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "復元" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Executar diagnóstico…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Restaurar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "运行诊断…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "恢复" } } } }, - "Run Dump": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Dump ausführen" + "Restore from backup?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aus Backup wiederherstellen?" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ejecutar dump" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Restaurar desde copia de seguridad?" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Exécuter Dump" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Restaurer depuis la sauvegarde ?" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ダンプを実行" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "バックアップから復元しますか?" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Executar dump" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Restaurar a partir do backup?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "运行 Dump" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从备份恢复?" } } } }, - "Run Now": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Jetzt ausführen" + "Restore…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wiederherstellen…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ejecutar ahora" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Restaurar…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Exécuter maintenant" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Restaurer…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "今すぐ実行" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "復元…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Executar agora" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Restaurar…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "立即运行" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "恢复…" } } } }, - "Run Setup in Terminal": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Einrichtung im Terminal ausführen" + "Result" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ergebnis" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ejecutar instalación en el terminal" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resultado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Lancer l'installation dans le terminal" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Résultat" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ターミナルでセットアップを実行" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "結果" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Executar instalação no terminal" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resultado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在终端中运行设置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "结果" } } } }, - "Run `hermes memory setup` in Terminal for full provider configuration.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Führe `hermes memory setup` im Terminal aus für die vollständige Anbieter-Konfiguration." + "Resume" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fortsetzen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ejecuta `hermes memory setup` en el terminal para la configuración completa del proveedor." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reanudar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Exécutez `hermes memory setup` dans le terminal pour la configuration complète du fournisseur." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reprendre" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロバイダーの完全な設定には、ターミナルで `hermes memory setup` を実行してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "再開" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Execute `hermes memory setup` no terminal para a configuração completa do provedor." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retomar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在终端中运行 `hermes memory setup` 以完成完整的提供方配置。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "继续" } } } }, - "Run remote diagnostics — check exactly which files are readable on this server.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Remote-Diagnose ausführen — prüfe genau, welche Dateien auf diesem Server lesbar sind." + "Resume Session" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sitzung fortsetzen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ejecutar diagnósticos remotos — comprueba exactamente qué archivos se pueden leer en este servidor." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reanudar sesión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Lancer les diagnostics distants — vérifier exactement quels fichiers sont lisibles sur ce serveur." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reprendre la session" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リモート診断を実行 — このサーバーで読み取れるファイルを正確に確認します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッションを再開" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Executar diagnóstico remoto — verifica exatamente quais arquivos podem ser lidos neste servidor." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retomar sessão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "运行远程诊断 — 准确检查此服务器上哪些文件可读。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "恢复会话" } } } }, - "Running a single shell session on %@ that exercises every path Scarf reads…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Führe eine einzelne Shell-Sitzung auf %@ aus, die jeden Pfad durchläuft, den Scarf liest…" + "Retry" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erneut versuchen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ejecutando una sola sesión de shell en %@ que recorre cada ruta que lee Scarf…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reintentar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Exécution d'une session shell unique sur %@ qui teste chaque chemin que Scarf lit…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réessayer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Scarf が読み取るすべてのパスを確認する単一のシェルセッションを %@ で実行中…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "再試行" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Executando uma única sessão de shell em %@ que percorre todos os caminhos que o Scarf lê…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tentar novamente" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在 %@ 上运行单一 shell 会话,测试 Scarf 读取的每个路径…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重试" } } } }, - "Running checks…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Führe Prüfungen aus…" + "Return to Active Session (%@...)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zurück zur aktiven Sitzung (%@...)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ejecutando comprobaciones…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Volver a sesión activa (%@...)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Exécution des vérifications…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Retour à la session active (%@...)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "チェックを実行中…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティブセッションに戻る (%@...)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Executando verificações…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voltar à sessão ativa (%@...)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "正在运行检查…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "返回活跃会话 (%@...)" } } } }, - "SILENT": {}, - "SOUL.md": {}, - "SOUL.md describes the agent's voice, values, and personality at ~/.hermes/SOUL.md. It is injected into every session's context.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "SOUL.md beschreibt Stimme, Werte und Persönlichkeit des Agents unter ~/.hermes/SOUL.md. Sie wird in den Kontext jeder Sitzung injiziert." + "Reveal" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anzeigen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "SOUL.md describe la voz, valores y personalidad del agente en ~/.hermes/SOUL.md. Se inyecta en el contexto de cada sesión." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "SOUL.md décrit la voix, les valeurs et la personnalité de l'agent dans ~/.hermes/SOUL.md. Il est injecté dans le contexte de chaque session." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afficher" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "SOUL.md は ~/.hermes/SOUL.md でエージェントの話し方、価値観、パーソナリティを記述します。これはすべてのセッションのコンテキストに挿入されます。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O SOUL.md descreve a voz, os valores e a personalidade do agente em ~/.hermes/SOUL.md. Ele é injetado no contexto de cada sessão." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Revelar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "SOUL.md 位于 ~/.hermes/SOUL.md,描述 agent 的语气、价值观与人格。它会被注入到每个会话的上下文中。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示" } } } }, - "SSH works but %@. Click for diagnostics.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "SSH funktioniert, aber %@. Für Diagnose klicken." + "Revoke" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Widerrufen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "SSH funciona pero %@. Haz clic para ver diagnósticos." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Revocar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "SSH fonctionne mais %@. Cliquez pour les diagnostics." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Révoquer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "SSH は動作していますが %@。診断を表示するにはクリックしてください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "取り消し" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O SSH funciona, mas %@. Clique para ver diagnósticos." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Revogar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "SSH 正常,但 %@。点击查看诊断。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "撤销" } } } }, - "Save": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Speichern" + "Rich Chat" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rich Chat" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Guardar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chat enriquecido" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Enregistrer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chat enrichi" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "保存" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リッチチャット" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Salvar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chat avançado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "保存" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "富文本聊天" } } } }, - "Scarf": {}, - "Scarf never prompts for passphrases. Add your key to ssh-agent in Terminal, then click Retry. If your key isn't `id_ed25519`, swap the path:": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Scarf fragt nie nach Passphrasen. Füge deinen Schlüssel im Terminal zum ssh-agent hinzu und klicke dann auf Erneut versuchen. Falls dein Schlüssel nicht `id_ed25519` ist, tausche den Pfad:" + "Run `hermes memory setup` in Terminal for full provider configuration." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Führe `hermes memory setup` im Terminal aus für die vollständige Anbieter-Konfiguration." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Scarf nunca pide frases de contraseña. Añade tu clave a ssh-agent en el terminal y haz clic en Reintentar. Si tu clave no es `id_ed25519`, cambia la ruta:" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ejecuta `hermes memory setup` en el terminal para la configuración completa del proveedor." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Scarf ne demande jamais de phrases secrètes. Ajoutez votre clé à ssh-agent dans le terminal, puis cliquez sur Réessayer. Si votre clé n'est pas `id_ed25519`, changez le chemin :" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exécutez `hermes memory setup` dans le terminal pour la configuration complète du fournisseur." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Scarf はパスフレーズを要求しません。ターミナルで ssh-agent にキーを追加してから、再試行をクリックしてください。キーが `id_ed25519` でない場合はパスを変更してください:" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロバイダーの完全な設定には、ターミナルで `hermes memory setup` を実行してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O Scarf nunca pede frases secretas. Adicione sua chave ao ssh-agent no terminal e clique em Tentar novamente. Se sua chave não for `id_ed25519`, troque o caminho:" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Execute `hermes memory setup` no terminal para a configuração completa do provedor." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Scarf 永远不会提示输入密码短语。请在终端中将密钥添加到 ssh-agent,然后点击重试。如果你的密钥不是 `id_ed25519`,请替换路径:" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在终端中运行 `hermes memory setup` 以完成完整的提供方配置。" } } } }, - "Scarf runs these over a single SSH session that mirrors the shell your dashboard reads from, so a green row here means Scarf can actually read that file at runtime.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Scarf führt diese über eine einzige SSH-Sitzung aus, die der Shell entspricht, aus der dein Dashboard liest. Eine grüne Zeile hier bedeutet also, dass Scarf die Datei zur Laufzeit tatsächlich lesen kann." + "Run Diagnostics…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diagnose ausführen…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Scarf ejecuta estos comandos en una única sesión SSH idéntica al shell desde el que lee tu panel, por lo que una fila en verde significa que Scarf puede leer ese archivo en tiempo de ejecución." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ejecutar diagnósticos…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Scarf exécute ces commandes sur une session SSH unique identique au shell utilisé par votre tableau de bord. Une ligne verte signifie donc que Scarf peut réellement lire ce fichier à l'exécution." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lancer les diagnostics…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Scarf はダッシュボードが読み取るシェルと同じ単一の SSH セッションでこれらを実行します。ここで緑色の行は、Scarf が実行時にそのファイルを実際に読み取れることを意味します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "診断を実行…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O Scarf executa esses comandos em uma única sessão SSH idêntica ao shell usado pelo seu painel. Uma linha verde aqui significa que o Scarf consegue ler aquele arquivo em tempo de execução." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Executar diagnóstico…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Scarf 在单一 SSH 会话中运行这些命令,该会话与仪表盘读取的 shell 一致。因此这里绿色行表示 Scarf 在运行时确实可以读取该文件。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "运行诊断…" } } } }, - "Scarf uses ssh-agent for authentication. If your key has a passphrase, run `ssh-add` before connecting — Scarf never prompts for or stores passphrases.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Scarf nutzt ssh-agent zur Authentifizierung. Hat dein Schlüssel eine Passphrase, führe vor dem Verbinden `ssh-add` aus — Scarf fragt oder speichert keine Passphrasen." + "Run Dump" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dump ausführen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Scarf usa ssh-agent para autenticarse. Si tu clave tiene frase de contraseña, ejecuta `ssh-add` antes de conectarte — Scarf nunca pide ni almacena frases de contraseña." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ejecutar dump" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Scarf utilise ssh-agent pour l'authentification. Si votre clé a une phrase secrète, exécutez `ssh-add` avant de vous connecter — Scarf ne demande ni ne stocke jamais de phrases secrètes." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exécuter Dump" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Scarf は認証に ssh-agent を使用します。キーにパスフレーズがある場合は、接続前に `ssh-add` を実行してください — Scarf はパスフレーズを要求することも保存することもありません。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ダンプを実行" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O Scarf usa ssh-agent para autenticação. Se sua chave tiver uma frase secreta, rode `ssh-add` antes de conectar — o Scarf nunca pede nem armazena frases secretas." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Executar dump" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Scarf 使用 ssh-agent 进行认证。如果你的密钥带有密码短语,请在连接前运行 `ssh-add` — Scarf 永远不会提示或存储密码短语。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "运行 Dump" } } } }, - "Scarf — %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Scarf — %@" + "Run Now" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jetzt ausführen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Scarf — %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ejecutar ahora" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Scarf — %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exécuter maintenant" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Scarf — %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "今すぐ実行" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Scarf — %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Executar agora" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Scarf — %@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "立即运行" } } } }, - "Search": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Suchen" + "Run remote diagnostics — check exactly which files are readable on this server." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remote-Diagnose ausführen — prüfe genau, welche Dateien auf diesem Server lesbar sind." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Buscar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ejecutar diagnósticos remotos — comprueba exactamente qué archivos se pueden leer en este servidor." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Rechercher" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lancer les diagnostics distants — vérifier exactement quels fichiers sont lisibles sur ce serveur." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "検索" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リモート診断を実行 — このサーバーで読み取れるファイルを正確に確認します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Pesquisar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Executar diagnóstico remoto — verifica exatamente quais arquivos podem ser lidos neste servidor." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "搜索" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "运行远程诊断 — 准确检查此服务器上哪些文件可读。" } } } }, - "Search Results (%lld)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Suchergebnisse (%lld)" + "Run Setup in Terminal" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einrichtung im Terminal ausführen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Resultados de búsqueda (%lld)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ejecutar instalación en el terminal" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Résultats de la recherche (%lld)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lancer l'installation dans le terminal" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "検索結果 (%lld)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ターミナルでセットアップを実行" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Resultados da pesquisa (%lld)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Executar instalação no terminal" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "搜索结果 (%lld)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在终端中运行设置" } } } }, - "Search messages...": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nachrichten suchen..." + "Running a single shell session on %@ that exercises every path Scarf reads…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Führe eine einzelne Shell-Sitzung auf %@ aus, die jeden Pfad durchläuft, den Scarf liest…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Buscar mensajes..." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ejecutando una sola sesión de shell en %@ que recorre cada ruta que lee Scarf…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Rechercher des messages..." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exécution d'une session shell unique sur %@ qui teste chaque chemin que Scarf lit…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "メッセージを検索..." + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf が読み取るすべてのパスを確認する単一のシェルセッションを %@ で実行中…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Pesquisar mensagens..." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Executando uma única sessão de shell em %@ que percorre todos os caminhos que o Scarf lê…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "搜索消息..." + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在 %@ 上运行单一 shell 会话,测试 Scarf 读取的每个路径…" } } } }, - "Search or browse skills published to registries like skills.sh, GitHub, and the official hub.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Durchsuche oder browse Skills, die in Registries wie skills.sh, GitHub und dem offiziellen Hub veröffentlicht sind." + "Running checks…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Führe Prüfungen aus…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Busca o examina habilidades publicadas en registros como skills.sh, GitHub y el hub oficial." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ejecutando comprobaciones…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Recherchez ou parcourez les compétences publiées sur des registres comme skills.sh, GitHub et le hub officiel." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exécution des vérifications…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "skills.sh、GitHub、公式ハブなどのレジストリに公開されたスキルを検索または参照します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "チェックを実行中…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Pesquise ou navegue por habilidades publicadas em registros como skills.sh, GitHub e o hub oficial." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Executando verificações…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "搜索或浏览发布到 skills.sh、GitHub 和官方 Hub 等注册表的技能。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "正在运行检查…" } } } }, - "Search registries": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Registries durchsuchen" + "Save" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Speichern" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Buscar en registros" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Guardar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Rechercher dans les registres" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enregistrer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "レジストリを検索" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Pesquisar registros" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Salvar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "搜索注册表" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "保存" } } } }, - "Search…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Suchen…" + "Saved in Keychain — leave empty to keep the stored value." : { + "comment" : "A message that appears when a user has filled in a secret but has not yet saved it.", + "isCommentAutoGenerated" : true + }, + "Saving…" : { + "comment" : "A label displayed while the configuration is being saved.", + "isCommentAutoGenerated" : true + }, + "Scarf" : { + + }, + "Scarf — %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf — %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Buscar…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf — %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Rechercher…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf — %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "検索…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf — %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Pesquisar…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf — %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "搜索…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf — %@" } } } }, - "Security": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sicherheit" + "Scarf doesn't auto-switch your active model. Change it in Settings if you'd like." : { + "comment" : "A description of the warning about not switching models.", + "isCommentAutoGenerated" : true + }, + "Scarf never prompts for passphrases. Add your key to ssh-agent in Terminal, then click Retry. If your key isn't `id_ed25519`, swap the path:" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf fragt nie nach Passphrasen. Füge deinen Schlüssel im Terminal zum ssh-agent hinzu und klicke dann auf Erneut versuchen. Falls dein Schlüssel nicht `id_ed25519` ist, tausche den Pfad:" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Seguridad" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf nunca pide frases de contraseña. Añade tu clave a ssh-agent en el terminal y haz clic en Reintentar. Si tu clave no es `id_ed25519`, cambia la ruta:" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sécurité" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf ne demande jamais de phrases secrètes. Ajoutez votre clé à ssh-agent dans le terminal, puis cliquez sur Réessayer. Si votre clé n'est pas `id_ed25519`, changez le chemin :" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セキュリティ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf はパスフレーズを要求しません。ターミナルで ssh-agent にキーを追加してから、再試行をクリックしてください。キーが `id_ed25519` でない場合はパスを変更してください:" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Segurança" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Scarf nunca pede frases secretas. Adicione sua chave ao ssh-agent no terminal e clique em Tentar novamente. Se sua chave não for `id_ed25519`, troque o caminho:" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "安全" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf 永远不会提示输入密码短语。请在终端中将密钥添加到 ssh-agent,然后点击重试。如果你的密钥不是 `id_ed25519`,请替换路径:" } } } }, - "Select": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Auswählen" + "Scarf runs these over a single SSH session that mirrors the shell your dashboard reads from, so a green row here means Scarf can actually read that file at runtime." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf führt diese über eine einzige SSH-Sitzung aus, die der Shell entspricht, aus der dein Dashboard liest. Eine grüne Zeile hier bedeutet also, dass Scarf die Datei zur Laufzeit tatsächlich lesen kann." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Seleccionar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf ejecuta estos comandos en una única sesión SSH idéntica al shell desde el que lee tu panel, por lo que una fila en verde significa que Scarf puede leer ese archivo en tiempo de ejecución." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sélectionner" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf exécute ces commandes sur une session SSH unique identique au shell utilisé par votre tableau de bord. Une ligne verte signifie donc que Scarf peut réellement lire ce fichier à l'exécution." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf はダッシュボードが読み取るシェルと同じ単一の SSH セッションでこれらを実行します。ここで緑色の行は、Scarf が実行時にそのファイルを実際に読み取れることを意味します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Selecionar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Scarf executa esses comandos em uma única sessão SSH idêntica ao shell usado pelo seu painel. Uma linha verde aqui significa que o Scarf consegue ler aquele arquivo em tempo de execução." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf 在单一 SSH 会话中运行这些命令,该会话与仪表盘读取的 shell 一致。因此这里绿色行表示 Scarf 在运行时确实可以读取该文件。" } } } }, - "Select Model": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Modell auswählen" + "Scarf uses ssh-agent for authentication. If your key has a passphrase, run `ssh-add` before connecting — Scarf never prompts for or stores passphrases." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf nutzt ssh-agent zur Authentifizierung. Hat dein Schlüssel eine Passphrase, führe vor dem Verbinden `ssh-add` aus — Scarf fragt oder speichert keine Passphrasen." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Seleccionar modelo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf usa ssh-agent para autenticarse. Si tu clave tiene frase de contraseña, ejecuta `ssh-add` antes de conectarte — Scarf nunca pide ni almacena frases de contraseña." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sélectionner le modèle" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf utilise ssh-agent pour l'authentification. Si votre clé a une phrase secrète, exécutez `ssh-add` avant de vous connecter — Scarf ne demande ni ne stocke jamais de phrases secrètes." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "モデルを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf は認証に ssh-agent を使用します。キーにパスフレーズがある場合は、接続前に `ssh-add` を実行してください — Scarf はパスフレーズを要求することも保存することもありません。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Selecionar modelo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Scarf usa ssh-agent para autenticação. Se sua chave tiver uma frase secreta, rode `ssh-add` antes de conectar — o Scarf nunca pede nem armazena frases secretas." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择模型" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scarf 使用 ssh-agent 进行认证。如果你的密钥带有密码短语,请在连接前运行 `ssh-add` — Scarf 永远不会提示或存储密码短语。" } } } }, - "Select a Job": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Job auswählen" + "Scarf will create a new folder inside the directory you pick, named after the template id." : { + "comment" : "A description of how a template will be installed.", + "isCommentAutoGenerated" : true + }, + "schedule: %@" : { + "comment" : "A cron schedule, e.g. \"every 10 minutes\".", + "isCommentAutoGenerated" : true + }, + "Search" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suchen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Seleccionar una tarea" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buscar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sélectionner une tâche" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rechercher" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ジョブを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "検索" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Selecionar uma tarefa" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pesquisar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择任务" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索" } } } }, - "Select a Profile": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Profil auswählen" + "Search messages..." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachrichten suchen..." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Seleccionar un perfil" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buscar mensajes..." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sélectionner un profil" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rechercher des messages..." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロファイルを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "メッセージを検索..." } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Selecionar um perfil" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pesquisar mensagens..." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索消息..." } } } }, - "Select a Project": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Projekt auswählen" + "Search or browse skills published to registries like skills.sh, GitHub, and the official hub." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Durchsuche oder browse Skills, die in Registries wie skills.sh, GitHub und dem offiziellen Hub veröffentlicht sind." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Seleccionar un proyecto" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Busca o examina habilidades publicadas en registros como skills.sh, GitHub y el hub oficial." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sélectionner un projet" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recherchez ou parcourez les compétences publiées sur des registres comme skills.sh, GitHub et le hub officiel." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "プロジェクトを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "skills.sh、GitHub、公式ハブなどのレジストリに公開されたスキルを検索または参照します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Selecionar um projeto" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pesquise ou navegue por habilidades publicadas em registros como skills.sh, GitHub e o hub oficial." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择项目" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索或浏览发布到 skills.sh、GitHub 和官方 Hub 等注册表的技能。" } } } }, - "Select a Session": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sitzung auswählen" + "Search registries" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registries durchsuchen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Seleccionar una sesión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buscar en registros" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sélectionner une session" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rechercher dans les registres" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッションを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "レジストリを検索" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Selecionar uma sessão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pesquisar registros" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索注册表" } } } }, - "Select a Skill": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Skill auswählen" + "Search Results (%lld)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suchergebnisse (%lld)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Seleccionar una habilidad" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resultados de búsqueda (%lld)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sélectionner une compétence" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Résultats de la recherche (%lld)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "スキルを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "検索結果 (%lld)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Selecionar uma habilidade" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resultados da pesquisa (%lld)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择技能" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索结果 (%lld)" } } } }, - "Select a Tool Call": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Tool-Aufruf auswählen" + "Search…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suchen…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Seleccionar una llamada a herramienta" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Buscar…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sélectionner un appel d'outil" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rechercher…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ツール呼び出しを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "検索…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Selecionar uma chamada de ferramenta" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pesquisar…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择工具调用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "搜索…" } } } }, - "Select an MCP Server": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "MCP-Server auswählen" + "Security" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sicherheit" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Seleccionar un servidor MCP" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seguridad" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sélectionner un serveur MCP" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sécurité" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "MCP サーバーを選択" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セキュリティ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Selecionar um servidor MCP" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Segurança" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "选择 MCP 服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "安全" } } } }, - "Send message (Enter)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nachricht senden (Enter)" + "Select" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auswählen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Enviar mensaje (Intro)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seleccionar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Envoyer le message (Entrée)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sélectionner" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "メッセージを送信 (Enter)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Enviar mensagem (Enter)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Selecionar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "发送消息 (Enter)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择" } } } }, - "Series": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Serie" + "Select a Job" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Job auswählen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Serie" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seleccionar una tarea" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Série" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sélectionner une tâche" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "系列" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ジョブを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Série" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Selecionar uma tarefa" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "系列" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择任务" } } } }, - "Server": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Server" + "Select a Profile" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Profil auswählen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Servidor" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seleccionar un perfil" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Serveur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sélectionner un profil" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サーバー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロファイルを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Servidor" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Selecionar um perfil" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择配置" } } } }, - "Server No Longer Exists": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Server existiert nicht mehr" + "Select a Project" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Projekt auswählen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "El servidor ya no existe" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seleccionar un proyecto" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Le serveur n'existe plus" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sélectionner un projet" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サーバーは存在しません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "プロジェクトを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Servidor não existe mais" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Selecionar um projeto" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "服务器已不存在" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择项目" } } } }, - "Server name": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Servername" + "Select a Session" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sitzung auswählen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Nombre del servidor" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seleccionar una sesión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Nom du serveur" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sélectionner une session" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サーバー名" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッションを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Nome do servidor" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Selecionar uma sessão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "服务器名称" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择会话" } } } }, - "Servers": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Server" + "Select a Skill" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skill auswählen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Servidores" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seleccionar una habilidad" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Serveurs" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sélectionner une compétence" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サーバー" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スキルを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Servidores" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Selecionar uma habilidade" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "服务器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择技能" } } } }, - "Service": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Dienst" + "Select a Tool Call" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tool-Aufruf auswählen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Servicio" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seleccionar una llamada a herramienta" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Service" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sélectionner un appel d'outil" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サービス" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ツール呼び出しを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Serviço" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Selecionar uma chamada de ferramenta" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "服务" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择工具调用" } } } }, - "Service definition stale": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Dienstdefinition veraltet" + "Select an MCP Server" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "MCP-Server auswählen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Definición de servicio obsoleta" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seleccionar un servidor MCP" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Définition de service obsolète" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sélectionner un serveur MCP" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サービス定義が古くなっています" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "MCP サーバーを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Definição de serviço desatualizada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Selecionar um servidor MCP" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "服务定义已过期" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择 MCP 服务器" } } } }, - "Session": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sitzung" + "Select Model" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Modell auswählen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sesión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seleccionar modelo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Session" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sélectionner le modèle" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッション" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "モデルを選択" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sessão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Selecionar modelo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择模型" } } } }, - "Session Search": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sitzungssuche" + "Send message (Enter)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nachricht senden (Enter)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Búsqueda de sesiones" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enviar mensaje (Intro)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Recherche de sessions" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Envoyer le message (Entrée)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッション検索" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "メッセージを送信 (Enter)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Busca de sessões" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enviar mensagem (Enter)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "会话搜索" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "发送消息 (Enter)" } } } }, - "Session title": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sitzungstitel" + "Series" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serie" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Título de sesión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serie" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Titre de session" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Série" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッションタイトル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "系列" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Título da sessão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Série" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "会话标题" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "系列" } } } }, - "Sessions": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sitzungen" + "Server" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sesiones" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servidor" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sessions" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serveur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セッション" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サーバー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sessões" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servidor" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "会话" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "服务器" } } } }, - "Settings": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Einstellungen" + "Server name" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servername" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ajustes" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nombre del servidor" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Réglages" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nom du serveur" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "設定" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サーバー名" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Configurações" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nome do servidor" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "设置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "服务器名称" } } } }, - "Setup": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Einrichtung" + "Server No Longer Exists" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server existiert nicht mehr" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Configuración" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "El servidor ya no existe" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Configuration" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Le serveur n'existe plus" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "セットアップ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サーバーは存在しません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Configuração" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servidor não existe mais" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "设置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "服务器已不存在" } } } }, - "Share Debug Report…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Debug-Bericht teilen…" + "Servers" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Compartir informe de depuración…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servidores" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Partager le rapport de débogage…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serveurs" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "デバッグレポートを共有…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サーバー" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Compartilhar relatório de depuração…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servidores" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "分享调试报告…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "服务器" } } } }, - "Shell Command": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Shell-Befehl" + "Service" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dienst" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Comando de shell" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Servicio" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Commande shell" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Service" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "シェルコマンド" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サービス" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Comando de shell" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serviço" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Shell 命令" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "服务" } } } }, - "Show": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Anzeigen" + "Service definition stale" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dienstdefinition veraltet" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Mostrar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definición de servicio obsoleta" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Afficher" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Définition de service obsolète" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サービス定義が古くなっています" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Mostrar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definição de serviço desatualizada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "服务定义已过期" } } } }, - "Show Output": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Ausgabe anzeigen" + "Session" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sitzung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Mostrar salida" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sesión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Afficher la sortie" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Session" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "出力を表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッション" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Mostrar saída" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sessão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示输出" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "会话" } } } }, - "Show all %lld lines": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Alle %lld Zeilen anzeigen" + "Session Search" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sitzungssuche" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Mostrar las %lld líneas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Búsqueda de sesiones" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Afficher toutes les %lld lignes" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recherche de sessions" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "すべての %lld 行を表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッション検索" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Mostrar todas as %lld linhas" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Busca de sessões" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示全部 %lld 行" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "会话搜索" } } } }, - "Show details": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Details anzeigen" + "Session title" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sitzungstitel" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Mostrar detalles" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Título de sesión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Afficher les détails" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Titre de session" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "詳細を表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッションタイトル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Mostrar detalhes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Título da sessão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示详情" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "会话标题" } } } }, - "Show less": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Weniger anzeigen" + "Sessions" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sitzungen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Mostrar menos" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sesiones" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Afficher moins" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sessions" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "折りたたむ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セッション" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Mostrar menos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sessões" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "收起" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "会话" } } } }, - "Show values": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Werte anzeigen" + "Set as default — open this server when Scarf launches." : { + "comment" : "A tooltip for the star button in the Manage Servers view.", + "isCommentAutoGenerated" : true + }, + "Settings" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einstellungen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Mostrar valores" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajustes" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Afficher les valeurs" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réglages" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "値を表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "設定" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Mostrar valores" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configurações" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "显示值" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置" } } } }, - "Signal Setup Docs": {}, - "Signal integration requires signal-cli (Java-based) installed locally. Link this Mac as a Signal device, then keep the daemon running so hermes can send/receive messages.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Die Signal-Integration benötigt lokal installiertes signal-cli (Java-basiert). Kopple diesen Mac als Signal-Gerät und lasse den Daemon laufen, damit hermes Nachrichten senden/empfangen kann." + "Setup" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Einrichtung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "La integración con Signal requiere signal-cli (basado en Java) instalado localmente. Vincula este Mac como dispositivo Signal y mantén el demonio en ejecución para que hermes envíe/reciba mensajes." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuración" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "L'intégration Signal nécessite signal-cli (basé sur Java) installé localement. Associez ce Mac comme appareil Signal, puis laissez le démon fonctionner pour que hermes puisse envoyer/recevoir des messages." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuration" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Signal 連携にはローカルにインストールされた signal-cli(Java 製)が必要です。この Mac を Signal デバイスとしてリンクしてから、デーモンを動作させ続けて hermes がメッセージを送受信できるようにします。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "セットアップ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "A integração com Signal requer signal-cli (baseado em Java) instalado localmente. Vincule este Mac como dispositivo Signal e mantenha o daemon em execução para o hermes enviar/receber mensagens." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Configuração" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Signal 集成需要在本地安装 signal-cli(基于 Java)。将此 Mac 关联为 Signal 设备,然后保持守护进程运行,以便 hermes 收发消息。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置" } } } }, - "Singularity": {}, - "Site": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Seite" + "Share Debug Report…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Debug-Bericht teilen…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sitio" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compartir informe de depuración…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Site" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Partager le rapport de débogage…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サイト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "デバッグレポートを共有…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Site" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compartilhar relatório de depuração…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "站点" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "分享调试报告…" } } } }, - "Skills": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Skills" + "Shell Command" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shell-Befehl" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Habilidades" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comando de shell" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Compétences" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Commande shell" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "スキル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "シェルコマンド" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Habilidades" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Comando de shell" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "技能" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Shell 命令" } } } }, - "Skills (%lld)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Skills (%lld)" + "Show" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anzeigen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Habilidades (%lld)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Compétences (%lld)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afficher" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "スキル (%lld)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Habilidades (%lld)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "技能 (%lld)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示" } } } }, - "Skills Hub": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Skills-Hub" + "Show all %lld lines" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle %lld Zeilen anzeigen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Hub de habilidades" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar las %lld líneas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Skills Hub" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afficher toutes les %lld lignes" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "スキルハブ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すべての %lld 行を表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Hub de habilidades" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar todas as %lld linhas" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "技能 Hub" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示全部 %lld 行" } } } }, - "Slack Setup Docs": {}, - "Source": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Quelle" + "Show details" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Details anzeigen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Origen" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar detalles" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Source" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afficher les détails" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ソース" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "詳細を表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Origem" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar detalhes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "来源" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示详情" } } } }, - "Speech-to-Text": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Spracherkennung" + "Show in Finder" : { + + }, + "Show less" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Weniger anzeigen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Voz a texto" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar menos" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Reconnaissance vocale" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afficher moins" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "音声認識" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "折りたたむ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Voz para texto" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar menos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "语音转文字" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "收起" } } } }, - "Start": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Starten" + "Show Output" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ausgabe anzeigen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Iniciar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar salida" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Démarrer" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afficher la sortie" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "開始" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "出力を表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Iniciar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar saída" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "启动" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示输出" } } } }, - "Start Daemon": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Daemon starten" + "Show values" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Werte anzeigen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Iniciar demonio" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar valores" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Démarrer le démon" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Afficher les valeurs" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "デーモンを開始" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "値を表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Iniciar daemon" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostrar valores" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "启动守护进程" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示值" } } } }, - "Start Hermes": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hermes starten" + "Show while typing" : { + "comment" : "A hint for the user on how to show/hide the secret.", + "isCommentAutoGenerated" : true + }, + "Signal integration requires signal-cli (Java-based) installed locally. Link this Mac as a Signal device, then keep the daemon running so hermes can send/receive messages." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Die Signal-Integration benötigt lokal installiertes signal-cli (Java-basiert). Kopple diesen Mac als Signal-Gerät und lasse den Daemon laufen, damit hermes Nachrichten senden/empfangen kann." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Iniciar Hermes" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La integración con Signal requiere signal-cli (basado en Java) instalado localmente. Vincula este Mac como dispositivo Signal y mantén el demonio en ejecución para que hermes envíe/reciba mensajes." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Démarrer Hermes" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'intégration Signal nécessite signal-cli (basé sur Java) installé localement. Associez ce Mac comme appareil Signal, puis laissez le démon fonctionner pour que hermes puisse envoyer/recevoir des messages." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Hermes を開始" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Signal 連携にはローカルにインストールされた signal-cli(Java 製)が必要です。この Mac を Signal デバイスとしてリンクしてから、デーモンを動作させ続けて hermes がメッセージを送受信できるようにします。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Iniciar Hermes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "A integração com Signal requer signal-cli (baseado em Java) instalado localmente. Vincule este Mac como dispositivo Signal e mantenha o daemon em execução para o hermes enviar/receber mensagens." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "启动 Hermes" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Signal 集成需要在本地安装 signal-cli(基于 Java)。将此 Mac 关联为 Signal 设备,然后保持守护进程运行,以便 hermes 收发消息。" } } } }, - "Start OAuth": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "OAuth starten" + "Signal Setup Docs" : { + + }, + "signal-cli is available on PATH" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli ist im PATH verfügbar" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Iniciar OAuth" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli está disponible en el PATH" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Démarrer OAuth" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli est disponible dans le PATH" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "OAuth を開始" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli は PATH 上で利用可能です" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Iniciar OAuth" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli está disponível no PATH" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "开始 OAuth" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli 已在 PATH 中" } } } }, - "Start Pairing": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Kopplung starten" + "signal-cli not found on PATH — install it first" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli im PATH nicht gefunden — bitte zuerst installieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Iniciar emparejamiento" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli no está en el PATH — instálalo primero" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Démarrer l'appairage" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli introuvable dans le PATH — installez-le d'abord" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ペアリングを開始" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli が PATH にありません — 先にインストールしてください" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Iniciar pareamento" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "signal-cli não encontrado no PATH — instale-o primeiro" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "开始配对" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "PATH 中未找到 signal-cli — 请先安装" } } } }, - "Start a new session or resume an existing one from the Session menu above.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Starte eine neue Sitzung oder setze eine bestehende aus dem Sitzungsmenü oben fort." + "signal-cli Terminal" : { + + }, + "SILENT" : { + + }, + "Singularity" : { + + }, + "Site" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seite" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Inicia una nueva sesión o reanuda una existente desde el menú Sesión de arriba." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sitio" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Démarrez une nouvelle session ou reprenez-en une existante depuis le menu Session ci-dessus." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Site" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "上のセッションメニューから新しいセッションを開始するか、既存のセッションを再開してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サイト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Inicie uma nova sessão ou retome uma existente no menu Sessão acima." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Site" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "从上方会话菜单启动新会话或恢复已有会话。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "站点" } } } }, - "Status": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Status" + "sk-…" : { + + }, + "Skills" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skills" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Estado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Habilidades" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Statut" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compétences" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ステータス" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スキル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Status" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Habilidades" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "状态" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "技能" } } } }, - "Stop": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Stoppen" + "Skills (%lld)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skills (%lld)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Detener" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Habilidades (%lld)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Arrêter" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compétences (%lld)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "停止" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スキル (%lld)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Parar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Habilidades (%lld)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "停止" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "技能 (%lld)" } } } }, - "Stop Hermes": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hermes stoppen" + "Skills Hub" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skills-Hub" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Detener Hermes" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hub de habilidades" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Arrêter Hermes" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skills Hub" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Hermes を停止" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "スキルハブ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Parar Hermes" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hub de habilidades" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "停止 Hermes" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "技能 Hub" } } } }, - "Subagent": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sub-Agent" + "Slack Setup Docs" : { + + }, + "SOUL.md" : { + + }, + "SOUL.md describes the agent's voice, values, and personality at ~/.hermes/SOUL.md. It is injected into every session's context." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "SOUL.md beschreibt Stimme, Werte und Persönlichkeit des Agents unter ~/.hermes/SOUL.md. Sie wird in den Kontext jeder Sitzung injiziert." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Subagente" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "SOUL.md describe la voz, valores y personalidad del agente en ~/.hermes/SOUL.md. Se inyecta en el contexto de cada sesión." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sous-agent" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "SOUL.md décrit la voix, les valeurs et la personnalité de l'agent dans ~/.hermes/SOUL.md. Il est injecté dans le contexte de chaque session." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サブエージェント" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "SOUL.md は ~/.hermes/SOUL.md でエージェントの話し方、価値観、パーソナリティを記述します。これはすべてのセッションのコンテキストに挿入されます。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Subagente" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O SOUL.md descreve a voz, os valores e a personalidade do agente em ~/.hermes/SOUL.md. Ele é injetado no contexto de cada sessão." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "子 agent" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "SOUL.md 位于 ~/.hermes/SOUL.md,描述 agent 的语气、价值观与人格。它会被注入到每个会话的上下文中。" } } } }, - "Subagent Sessions (%lld)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sub-Agent-Sitzungen (%lld)" + "Source" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Quelle" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sesiones de subagente (%lld)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Origen" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sessions de sous-agent (%lld)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Source" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サブエージェントセッション (%lld)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ソース" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sessões de subagente (%lld)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Origem" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "子 agent 会话 (%lld)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "来源" } } } }, - "Submit": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Absenden" + "Speech-to-Text" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Spracherkennung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Enviar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voz a texto" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Soumettre" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reconnaissance vocale" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "送信" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "音声認識" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Enviar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voz para texto" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "提交" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "语音转文字" } } } }, - "Subscribe": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Abonnieren" + "ssh trace" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "ssh-Trace" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Suscribir" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "traza ssh" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "S'abonner" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "trace ssh" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "購読" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ssh トレース" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Assinar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "trace do ssh" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "订阅" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "ssh 跟踪" } } } }, - "Succeeded": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Erfolgreich" + "SSH works but %@. Click for diagnostics." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSH funktioniert, aber %@. Für Diagnose klicken." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Exitoso" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSH funciona pero %@. Haz clic para ver diagnósticos." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Réussi" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSH fonctionne mais %@. Cliquez pour les diagnostics." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "成功" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSH は動作していますが %@。診断を表示するにはクリックしてください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sucesso" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O SSH funciona, mas %@. Clique para ver diagnósticos." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "成功" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "SSH 正常,但 %@。点击查看诊断。" } } } }, - "Switch to This Profile": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Zu diesem Profil wechseln" + "ssh-agent (leave blank)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "ssh-agent (leer lassen)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cambiar a este perfil" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "ssh-agent (dejar vacío)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Basculer sur ce profil" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "ssh-agent (laisser vide)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "このプロファイルに切り替え" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ssh-agent(空のまま)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Mudar para este perfil" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "ssh-agent (deixe em branco)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "切换到此配置" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "ssh-agent(留空)" } } } }, - "Switching the active profile changes the `~/.hermes` directory hermes uses. Restart Scarf after switching so it re-reads from the new profile's files.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Das Wechseln des aktiven Profils ändert das von hermes verwendete `~/.hermes`-Verzeichnis. Starte Scarf nach dem Wechsel neu, damit es aus den Dateien des neuen Profils liest." + "Start" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Starten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Cambiar el perfil activo cambia el directorio `~/.hermes` que usa hermes. Reinicia Scarf tras cambiar para que relea los archivos del nuevo perfil." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iniciar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Changer de profil actif modifie le répertoire `~/.hermes` utilisé par hermes. Redémarrez Scarf après le changement pour qu'il relise les fichiers du nouveau profil." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Démarrer" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アクティブなプロファイルを切り替えると、hermes が使用する `~/.hermes` ディレクトリが変わります。切り替え後、Scarf を再起動して新しいプロファイルのファイルを読み込み直してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "開始" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Trocar o perfil ativo muda o diretório `~/.hermes` usado pelo hermes. Reinicie o Scarf após trocar para ele reler os arquivos do novo perfil." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iniciar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "切换当前配置会改变 hermes 使用的 `~/.hermes` 目录。切换后请重启 Scarf,以便它从新配置的文件中重新读取。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动" } } } }, - "TTS Off": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "TTS aus" + "Start a new session or resume an existing one from the Session menu above." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Starte eine neue Sitzung oder setze eine bestehende aus dem Sitzungsmenü oben fort." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "TTS apagado" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inicia una nueva sesión o reanuda una existente desde el menú Sesión de arriba." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "TTS désactivé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Démarrez une nouvelle session ou reprenez-en une existante depuis le menu Session ci-dessus." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "TTS オフ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "上のセッションメニューから新しいセッションを開始するか、既存のセッションを再開してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "TTS desligado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inicie uma nova sessão ou retome uma existente no menu Sessão acima." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "TTS 关" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "从上方会话菜单启动新会话或恢复已有会话。" } } } }, - "TTS On": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "TTS an" + "Start Daemon" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Daemon starten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "TTS encendido" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iniciar demonio" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "TTS activé" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Démarrer le démon" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "TTS オン" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "デーモンを開始" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "TTS ligado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iniciar daemon" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "TTS 开" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动守护进程" } } } }, - "Telegram Setup Docs": {}, - "Terminal": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Terminal" + "Start Hermes" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes starten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Terminal" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iniciar Hermes" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Terminal" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Démarrer Hermes" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ターミナル" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes を開始" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Terminal" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iniciar Hermes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "终端" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动 Hermes" } } } }, - "Test": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Testen" + "Start OAuth" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "OAuth starten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Probar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iniciar OAuth" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tester" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Démarrer OAuth" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "テスト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "OAuth を開始" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Testar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iniciar OAuth" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "测试" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开始 OAuth" } } } }, - "Test All": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Alle testen" + "Start Pairing" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kopplung starten" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Probar todo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iniciar emparejamiento" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tout tester" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Démarrer l'appairage" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "すべてテスト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ペアリングを開始" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Testar todos" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Iniciar pareamento" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "测试全部" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "开始配对" } } } }, - "Test Connection": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Verbindung testen" + "state.db not found at the configured path. Either Hermes hasn't run yet on this server, or it's installed at a non-default location — set the Hermes data directory field above." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db wurde am konfigurierten Pfad nicht gefunden. Entweder lief Hermes auf diesem Server noch nicht, oder es ist an einem nicht standardmäßigen Ort installiert — setze oben das Feld für das Hermes-Datenverzeichnis." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Probar conexión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se encontró state.db en la ruta configurada. O bien Hermes no se ha ejecutado aún en este servidor, o está instalado en una ubicación no predeterminada — establece arriba el campo del directorio de datos de Hermes." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tester la connexion" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db introuvable au chemin configuré. Soit Hermes n'a pas encore été lancé sur ce serveur, soit il est installé à un emplacement non standard — définissez le champ répertoire de données Hermes ci-dessus." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "接続テスト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "設定されたパスに state.db が見つかりません。Hermes がこのサーバーでまだ実行されていないか、デフォルトでない場所にインストールされている可能性があります — 上の Hermes データディレクトリフィールドを設定してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Testar conexão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db não encontrado no caminho configurado. Ou o Hermes ainda não rodou neste servidor, ou está instalado em um local não padrão — defina o campo de diretório de dados do Hermes acima." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "测试连接" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "在配置的路径下未找到 state.db。要么 Hermes 尚未在此服务器上运行过,要么它安装在非默认位置 — 请在上方设置 Hermes 数据目录字段。" } } } }, - "Test failed": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Test fehlgeschlagen" + "state.db not found at the default location, but Scarf found one at:" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db wurde am Standardort nicht gefunden, Scarf hat aber eine unter folgendem Pfad entdeckt:" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Prueba fallida" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se encontró state.db en la ubicación predeterminada, pero Scarf encontró uno en:" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Test échoué" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db introuvable à l'emplacement par défaut, mais Scarf en a trouvé une à :" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "テスト失敗" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "デフォルトの場所には state.db がありませんが、Scarf が以下の場所に見つけました:" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Teste falhou" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db não encontrado no local padrão, mas o Scarf encontrou um em:" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "测试失败" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "默认位置下未找到 state.db,但 Scarf 在此处找到了一个:" } } } }, - "Test passed": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Test bestanden" + "state.db readable" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db lesbar" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Prueba superada" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db legible" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Test réussi" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db lisible" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "テスト成功" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db 読み取り可能" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Teste aprovado" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db legível" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "测试通过" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "state.db 可读" } } } }, - "Text-to-Speech": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Text-to-Speech" + "Status" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Texto a voz" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Estado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Synthèse vocale" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Statut" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "音声合成" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ステータス" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Texto para voz" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "文字转语音" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "状态" } } } }, - "The agent hasn't advertised any slash commands yet. Keep typing to send as a message, or press Esc.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Der Agent hat bisher keine Slash-Befehle angeboten. Weitertippen, um als Nachricht zu senden, oder Esc drücken." + "stderr:" : { + + }, + "stdout:" : { + + }, + "Stop" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stoppen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "El agente aún no ha anunciado comandos slash. Sigue escribiendo para enviar como mensaje, o pulsa Esc." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Detener" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "L'agent n'a pas encore annoncé de commandes slash. Continuez à taper pour envoyer en tant que message, ou appuyez sur Échap." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arrêter" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "エージェントはまだスラッシュコマンドを提示していません。入力を続けるとメッセージとして送信されます。キャンセルするには Esc を押してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "停止" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O agente ainda não anunciou nenhum comando slash. Continue digitando para enviar como mensagem, ou pressione Esc." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "agent 尚未公布任何斜杠命令。继续输入作为消息发送,或按 Esc。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "停止" } } } }, - "The remote's SSH fingerprint no longer matches what your `~/.ssh/known_hosts` file expected. This usually means the remote was reinstalled — or, less commonly, that someone is intercepting the connection.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Der SSH-Fingerabdruck des Remote passt nicht mehr zu dem, was deine `~/.ssh/known_hosts`-Datei erwartet. Meist bedeutet das, dass der Remote neu installiert wurde — seltener, dass jemand die Verbindung abfängt." + "Stop Hermes" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes stoppen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "La huella SSH del remoto ya no coincide con lo que tu archivo `~/.ssh/known_hosts` esperaba. Normalmente significa que el remoto se reinstaló — o, con menos frecuencia, que alguien intercepta la conexión." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Detener Hermes" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "L'empreinte SSH de l'hôte distant ne correspond plus à ce qu'attendait votre fichier `~/.ssh/known_hosts`. Cela signifie généralement que l'hôte distant a été réinstallé — ou, plus rarement, que quelqu'un intercepte la connexion." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Arrêter Hermes" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "リモートの SSH フィンガープリントが `~/.ssh/known_hosts` の期待値と一致しません。通常はリモートが再インストールされたことを意味します — まれに通信が傍受されている可能性もあります。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hermes を停止" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "A impressão SSH do remoto não corresponde mais ao que seu arquivo `~/.ssh/known_hosts` esperava. Normalmente isso significa que o remoto foi reinstalado — ou, mais raramente, que alguém está interceptando a conexão." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Parar Hermes" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "远端的 SSH 指纹与你 `~/.ssh/known_hosts` 文件中预期的不再匹配。这通常意味着远端已重装 — 较少见的情况是有人在拦截连接。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "停止 Hermes" } } } }, - "The server this window was opened with has been removed from your registry.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Der Server, mit dem dieses Fenster geöffnet wurde, wurde aus deiner Registrierung entfernt." + "Strip the template's begin/end block, preserve everything else in MEMORY.md" : { + "comment" : "A description of the action to remove a memory block.", + "isCommentAutoGenerated" : true + }, + "Subagent" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sub-Agent" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "El servidor con el que se abrió esta ventana se ha eliminado de tu registro." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Subagente" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Le serveur avec lequel cette fenêtre a été ouverte a été retiré de votre registre." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sous-agent" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "このウィンドウを開いたサーバーはレジストリから削除されました。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サブエージェント" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O servidor com o qual esta janela foi aberta foi removido do seu registro." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Subagente" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "打开此窗口所用的服务器已从你的注册表中移除。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "子 agent" } } } }, - "The server's SSH configuration is removed from Scarf. Your remote files are untouched.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Die SSH-Konfiguration des Servers wird aus Scarf entfernt. Deine Remote-Dateien bleiben unangetastet." + "Subagent Sessions (%lld)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sub-Agent-Sitzungen (%lld)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "La configuración SSH del servidor se elimina de Scarf. Tus archivos remotos no se tocan." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sesiones de subagente (%lld)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "La configuration SSH du serveur est supprimée de Scarf. Vos fichiers distants ne sont pas modifiés." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sessions de sous-agent (%lld)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "サーバーの SSH 設定は Scarf から削除されます。リモートファイルは変更されません。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サブエージェントセッション (%lld)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "A configuração SSH do servidor é removida do Scarf. Seus arquivos remotos permanecem intocados." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sessões de subagente (%lld)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "服务器的 SSH 配置已从 Scarf 中移除。你的远程文件未被改动。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "子 agent 会话 (%lld)" } } } }, - "The terminal is a real TTY — paste with ⌘V, press Return, and wait for the process to exit with \"login succeeded\".": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Das Terminal ist ein echtes TTY — mit ⌘V einfügen, Return drücken und warten, bis der Prozess mit \"login succeeded\" endet." + "Submit" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Absenden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "El terminal es un TTY real — pega con ⌘V, pulsa Intro y espera a que el proceso termine con «login succeeded»." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enviar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Le terminal est un véritable TTY — collez avec ⌘V, appuyez sur Retour et attendez que le processus se termine avec « login succeeded »." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Soumettre" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "このターミナルは実際の TTY です — ⌘V で貼り付け、Return を押して、プロセスが「login succeeded」で終了するのを待ってください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "送信" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O terminal é um TTY real — cole com ⌘V, pressione Return e aguarde o processo encerrar com \"login succeeded\"." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enviar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "终端是真正的 TTY — 使用 ⌘V 粘贴,按 Return,等待进程以 \"login succeeded\" 退出。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "提交" } } } }, - "These list fields must be edited directly in config.yaml.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Diese Listenfelder müssen direkt in config.yaml bearbeitet werden." + "Subscribe" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abonnieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Estos campos de lista deben editarse directamente en config.yaml." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Suscribir" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ces champs liste doivent être modifiés directement dans config.yaml." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "S'abonner" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "これらのリストフィールドは config.yaml で直接編集する必要があります。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "購読" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Esses campos de lista precisam ser editados diretamente em config.yaml." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Assinar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "这些列表字段必须直接在 config.yaml 中编辑。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "订阅" } } } }, - "This provider has no catalogued models.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Für diesen Anbieter sind keine katalogisierten Modelle vorhanden." + "Succeeded" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Erfolgreich" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Este proveedor no tiene modelos catalogados." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exitoso" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Ce fournisseur n'a pas de modèles catalogués." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réussi" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "このプロバイダーにはカタログ化されたモデルがありません。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "成功" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Este provedor não tem modelos catalogados." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sucesso" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "此提供方没有已登记的模型。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "成功" } } } }, - "This removes the credential from hermes. The upstream provider key is not revoked.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Damit werden die Anmeldedaten aus hermes entfernt. Der Schlüssel beim Upstream-Anbieter wird nicht widerrufen." + "Switch to This Profile" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zu diesem Profil wechseln" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esto quita la credencial de hermes. La clave del proveedor original no se revoca." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cambiar a este perfil" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cela retire les identifiants de hermes. La clé chez le fournisseur amont n'est pas révoquée." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basculer sur ce profil" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "これにより hermes から資格情報が削除されます。上流プロバイダーのキーは取り消されません。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "このプロファイルに切り替え" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Isso remove a credencial do hermes. A chave do provedor original não é revogada." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mudar para este perfil" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "这将从 hermes 中移除该凭证。上游提供方的密钥不会被撤销。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "切换到此配置" } } } }, - "This removes the profile directory and all data within it. This cannot be undone.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Damit werden das Profilverzeichnis und alle darin enthaltenen Daten entfernt. Dies kann nicht rückgängig gemacht werden." + "Switching the active profile changes the `~/.hermes` directory hermes uses. Restart Scarf after switching so it re-reads from the new profile's files." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Das Wechseln des aktiven Profils ändert das von hermes verwendete `~/.hermes`-Verzeichnis. Starte Scarf nach dem Wechsel neu, damit es aus den Dateien des neuen Profils liest." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esto elimina el directorio del perfil y todos sus datos. No se puede deshacer." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cambiar el perfil activo cambia el directorio `~/.hermes` que usa hermes. Reinicia Scarf tras cambiar para que relea los archivos del nuevo perfil." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cela supprime le répertoire du profil et toutes les données qu'il contient. Action irréversible." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Changer de profil actif modifie le répertoire `~/.hermes` utilisé par hermes. Redémarrez Scarf après le changement pour qu'il relise les fichiers du nouveau profil." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "これによりプロファイルディレクトリとその中のすべてのデータが削除されます。元に戻せません。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アクティブなプロファイルを切り替えると、hermes が使用する `~/.hermes` ディレクトリが変わります。切り替え後、Scarf を再起動して新しいプロファイルのファイルを読み込み直してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Isso remove o diretório do perfil e todos os dados dentro dele. Não pode ser desfeito." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trocar o perfil ativo muda o diretório `~/.hermes` usado pelo hermes. Reinicie o Scarf após trocar para ele reler os arquivos do novo perfil." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "这将移除配置目录及其中所有数据。此操作无法撤销。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "切换当前配置会改变 hermes 使用的 `~/.hermes` 目录。切换后请重启 Scarf,以便它从新配置的文件中重新读取。" } } } }, - "This removes the scheduled job permanently.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Damit wird der geplante Job dauerhaft entfernt." + "Tags (comma-separated)" : { + "comment" : "A label for a field that allows the user to enter a list of tags, separated by commas.", + "isCommentAutoGenerated" : true + }, + "Telegram Setup Docs" : { + + }, + "Template ID" : { + + }, + "Templates" : { + "comment" : "A label for the \"Templates\" menu.", + "isCommentAutoGenerated" : true + }, + "Terminal" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terminal" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esto elimina la tarea programada de forma permanente." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terminal" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cela supprime définitivement la tâche planifiée." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terminal" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "これによりスケジュールされたジョブが恒久的に削除されます。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ターミナル" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Isso remove a tarefa agendada permanentemente." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Terminal" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "这将永久移除该定时任务。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "终端" } } } }, - "This removes the server from config.yaml and deletes any OAuth token.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Damit wird der Server aus config.yaml entfernt und jedes OAuth-Token gelöscht." + "Test" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Testen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esto elimina el servidor de config.yaml y borra cualquier token OAuth." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Probar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cela retire le serveur de config.yaml et supprime tout jeton OAuth." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tester" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "これにより config.yaml からサーバーが削除され、OAuth トークンも削除されます。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "テスト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Isso remove o servidor do config.yaml e apaga qualquer token OAuth." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Testar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "这将从 config.yaml 中移除服务器并删除任何 OAuth 令牌。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "测试" } } } }, - "This uploads logs, config (with secrets redacted), and system info to Nous Research support infrastructure. Review the output below before sharing the returned URL.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Damit werden Logs, Konfiguration (mit geschwärzten Secrets) und Systeminfos zur Support-Infrastruktur von Nous Research hochgeladen. Prüfe die Ausgabe unten, bevor du die zurückgegebene URL teilst." + "Test All" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle testen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esto sube registros, configuración (con secretos redactados) e información del sistema a la infraestructura de soporte de Nous Research. Revisa la salida antes de compartir la URL devuelta." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Probar todo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cela téléverse les journaux, la configuration (secrets masqués) et les infos système vers l'infrastructure de support Nous Research. Vérifiez la sortie ci-dessous avant de partager l'URL retournée." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tout tester" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "これにより、ログ、設定(シークレットはマスク済み)、システム情報が Nous Research のサポートインフラにアップロードされます。返された URL を共有する前に下の出力を確認してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すべてテスト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Isso envia logs, configuração (com segredos mascarados) e informações do sistema para a infraestrutura de suporte da Nous Research. Revise a saída antes de compartilhar a URL retornada." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Testar todos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "这会将日志、配置(密钥已脱敏)和系统信息上传到 Nous Research 支持基础设施。分享返回的 URL 前请查看下方输出。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "测试全部" } } } }, - "This will overwrite files under ~/.hermes/ with the archive contents.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Damit werden Dateien unter ~/.hermes/ mit dem Archivinhalt überschrieben." + "Test Connection" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verbindung testen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esto sobrescribirá archivos bajo ~/.hermes/ con el contenido del archivo." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Probar conexión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cela écrasera les fichiers sous ~/.hermes/ avec le contenu de l'archive." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tester la connexion" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "これにより ~/.hermes/ 以下のファイルがアーカイブの内容で上書きされます。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "接続テスト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Isso sobrescreverá arquivos em ~/.hermes/ com o conteúdo do arquivo." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Testar conexão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "这将使用归档内容覆盖 ~/.hermes/ 下的文件。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "测试连接" } } } }, - "This will permanently delete the session and all its messages.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Damit werden die Sitzung und alle ihre Nachrichten dauerhaft gelöscht." + "Test failed" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Test fehlgeschlagen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esto eliminará permanentemente la sesión y todos sus mensajes." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prueba fallida" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Cela supprimera définitivement la session et tous ses messages." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Test échoué" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "これによりセッションとそのすべてのメッセージが恒久的に削除されます。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "テスト失敗" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Isso excluirá permanentemente a sessão e todas as suas mensagens." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Teste falhou" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "这将永久删除该会话及其所有消息。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "测试失败" } } } }, - "Timeout: %llds (%@)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Timeout: %1$lld s (%2$@)" + "Test passed" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Test bestanden" } }, - "en": { - "stringUnit": { - "state": "new", - "value": "Timeout: %1$llds (%2$@)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prueba superada" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Tiempo de espera: %1$lld s (%2$@)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Test réussi" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Délai : %1$lld s (%2$@)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "テスト成功" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "タイムアウト: %1$lld 秒 (%2$@)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Teste aprovado" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Tempo limite: %1$lld s (%2$@)" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "超时:%1$lld 秒 (%2$@)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "测试通过" } } } }, - "Timeouts": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Timeouts" + "Text-to-Speech" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Text-to-Speech" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Tiempos de espera" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Texto a voz" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Délais" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Synthèse vocale" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "タイムアウト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "音声合成" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Tempos limite" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Texto para voz" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "超时" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "文字转语音" } } } }, - "Tirith Sandbox": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Tirith-Sandbox" + "The agent hasn't advertised any slash commands yet. Keep typing to send as a message, or press Esc." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Der Agent hat bisher keine Slash-Befehle angeboten. Weitertippen, um als Nachricht zu senden, oder Esc drücken." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Sandbox Tirith" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "El agente aún no ha anunciado comandos slash. Sigue escribiendo para enviar como mensaje, o pulsa Esc." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Bac à sable Tirith" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'agent n'a pas encore annoncé de commandes slash. Continuez à taper pour envoyer en tant que message, ou appuyez sur Échap." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Tirith サンドボックス" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "エージェントはまだスラッシュコマンドを提示していません。入力を続けるとメッセージとして送信されます。キャンセルするには Esc を押してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Sandbox Tirith" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O agente ainda não anunciou nenhum comando slash. Continue digitando para enviar como mensagem, ou pressione Esc." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Tirith 沙箱" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "agent 尚未公布任何斜杠命令。继续输入作为消息发送,或按 Esc。" } } } }, - "To skip the passphrase prompt at every reboot, add `--apple-use-keychain` to cache it in macOS Keychain.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Um die Passphrase-Abfrage bei jedem Neustart zu überspringen, füge `--apple-use-keychain` hinzu, um sie im macOS-Schlüsselbund zu cachen." + "The remote's SSH fingerprint no longer matches what your `~/.ssh/known_hosts` file expected. This usually means the remote was reinstalled — or, less commonly, that someone is intercepting the connection." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Der SSH-Fingerabdruck des Remote passt nicht mehr zu dem, was deine `~/.ssh/known_hosts`-Datei erwartet. Meist bedeutet das, dass der Remote neu installiert wurde — seltener, dass jemand die Verbindung abfängt." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Para evitar que pida la frase de contraseña en cada reinicio, añade `--apple-use-keychain` para almacenarla en el llavero de macOS." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La huella SSH del remoto ya no coincide con lo que tu archivo `~/.ssh/known_hosts` esperaba. Normalmente significa que el remoto se reinstaló — o, con menos frecuencia, que alguien intercepta la conexión." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Pour éviter la demande de phrase secrète à chaque redémarrage, ajoutez `--apple-use-keychain` pour la mettre en cache dans le trousseau macOS." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "L'empreinte SSH de l'hôte distant ne correspond plus à ce qu'attendait votre fichier `~/.ssh/known_hosts`. Cela signifie généralement que l'hôte distant a été réinstallé — ou, plus rarement, que quelqu'un intercepte la connexion." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "毎回の再起動時にパスフレーズのプロンプトをスキップするには、`--apple-use-keychain` を追加して macOS キーチェーンにキャッシュしてください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "リモートの SSH フィンガープリントが `~/.ssh/known_hosts` の期待値と一致しません。通常はリモートが再インストールされたことを意味します — まれに通信が傍受されている可能性もあります。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Para pular a solicitação de frase secreta a cada reinicialização, adicione `--apple-use-keychain` para armazená-la no Keychain do macOS." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "A impressão SSH do remoto não corresponde mais ao que seu arquivo `~/.ssh/known_hosts` esperava. Normalmente isso significa que o remoto foi reinstalado — ou, mais raramente, que alguém está interceptando a conexão." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "要跳过每次重启时的密码短语提示,添加 `--apple-use-keychain` 以将其缓存到 macOS Keychain 中。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "远端的 SSH 指纹与你 `~/.ssh/known_hosts` 文件中预期的不再匹配。这通常意味着远端已重装 — 较少见的情况是有人在拦截连接。" } } } }, - "Toggle text-to-speech (/voice tts)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Text-to-Speech umschalten (/voice tts)" + "The server this window was opened with has been removed from your registry." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Der Server, mit dem dieses Fenster geöffnet wurde, wurde aus deiner Registrierung entfernt." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Alternar texto a voz (/voice tts)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "El servidor con el que se abrió esta ventana se ha eliminado de tu registro." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Basculer la synthèse vocale (/voice tts)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Le serveur avec lequel cette fenêtre a été ouverte a été retiré de votre registre." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "テキスト読み上げを切り替え (/voice tts)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "このウィンドウを開いたサーバーはレジストリから削除されました。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Alternar texto-para-fala (/voice tts)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O servidor com o qual esta janela foi aberta foi removido do seu registro." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "切换文字转语音 (/voice tts)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "打开此窗口所用的服务器已从你的注册表中移除。" } } } }, - "Toggle voice mode (/voice)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Sprachmodus umschalten (/voice)" + "The server's SSH configuration is removed from Scarf. Your remote files are untouched." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Die SSH-Konfiguration des Servers wird aus Scarf entfernt. Deine Remote-Dateien bleiben unangetastet." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Alternar modo de voz (/voice)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La configuración SSH del servidor se elimina de Scarf. Tus archivos remotos no se tocan." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Basculer le mode vocal (/voice)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "La configuration SSH du serveur est supprimée de Scarf. Vos fichiers distants ne sont pas modifiés." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "音声モードを切り替え (/voice)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "サーバーの SSH 設定は Scarf から削除されます。リモートファイルは変更されません。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Alternar modo de voz (/voice)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "A configuração SSH do servidor é removida do Scarf. Seus arquivos remotos permanecem intocados." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "切换语音模式 (/voice)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "服务器的 SSH 配置已从 Scarf 中移除。你的远程文件未被改动。" } } } }, - "Token on disk. Clear to re-authenticate next time the gateway connects.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Token auf der Festplatte. Löschen, damit sich das Gateway beim nächsten Verbinden neu authentifiziert." + "The terminal is a real TTY — paste with ⌘V, press Return, and wait for the process to exit with \"login succeeded\"." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Das Terminal ist ein echtes TTY — mit ⌘V einfügen, Return drücken und warten, bis der Prozess mit \"login succeeded\" endet." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Token en disco. Bórralo para que se vuelva a autenticar en la próxima conexión del gateway." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "El terminal es un TTY real — pega con ⌘V, pulsa Intro y espera a que el proceso termine con «login succeeded»." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Jeton sur disque. Effacez-le pour forcer une nouvelle authentification à la prochaine connexion du gateway." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Le terminal est un véritable TTY — collez avec ⌘V, appuyez sur Retour et attendez que le processus se termine avec « login succeeded »." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "トークンはディスク上にあります。消去すると、次回 gateway が接続する際に再認証します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "このターミナルは実際の TTY です — ⌘V で貼り付け、Return を押して、プロセスが「login succeeded」で終了するのを待ってください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Token no disco. Limpe para reautenticar na próxima vez que o gateway conectar." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O terminal é um TTY real — cole com ⌘V, pressione Return e aguarde o processo encerrar com \"login succeeded\"." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "令牌已保存到磁盘。清除后,gateway 下次连接时将重新认证。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "终端是真正的 TTY — 使用 ⌘V 粘贴,按 Return,等待进程以 \"login succeeded\" 退出。" } } } }, - "Tool Approval Required": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Tool-Genehmigung erforderlich" + "These files weren't installed by the template (the agent or you created them after install), so Scarf left them in place along with the folder itself." : { + "comment" : "A description of the files Scarf left in place when uninstalling a template.", + "isCommentAutoGenerated" : true + }, + "These list fields must be edited directly in config.yaml." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Diese Listenfelder müssen direkt in config.yaml bearbeitet werden." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Se requiere aprobación de herramienta" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Estos campos de lista deben editarse directamente en config.yaml." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Approbation d'outil requise" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ces champs liste doivent être modifiés directement dans config.yaml." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ツールの承認が必要" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これらのリストフィールドは config.yaml で直接編集する必要があります。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Aprovação de ferramenta necessária" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esses campos de lista precisam ser editados diretamente em config.yaml." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "需要工具批准" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这些列表字段必须直接在 config.yaml 中编辑。" } } } }, - "Tool Filters": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Tool-Filter" + "This Mac" : { + "comment" : "A description of the local machine.", + "isCommentAutoGenerated" : true + }, + "This project wasn't installed from a schemaful template." : { + + }, + "This provider has no catalogued models." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Für diesen Anbieter sind keine katalogisierten Modelle vorhanden." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Filtros de herramientas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Este proveedor no tiene modelos catalogados." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Filtres d'outils" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ce fournisseur n'a pas de modèles catalogués." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ツールフィルタ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "このプロバイダーにはカタログ化されたモデルがありません。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Filtros de ferramentas" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Este provedor não tem modelos catalogados." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "工具过滤器" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "此提供方没有已登记的模型。" } } } }, - "Tool Progress": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Tool-Fortschritt" + "This removes the credential from hermes. The upstream provider key is not revoked." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Damit werden die Anmeldedaten aus hermes entfernt. Der Schlüssel beim Upstream-Anbieter wird nicht widerrufen." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Progreso de herramientas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esto quita la credencial de hermes. La clave del proveedor original no se revoca." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Progression des outils" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cela retire les identifiants de hermes. La clé chez le fournisseur amont n'est pas révoquée." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ツールの進捗" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これにより hermes から資格情報が削除されます。上流プロバイダーのキーは取り消されません。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Progresso da ferramenta" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Isso remove a credencial do hermes. A chave do provedor original não é revogada." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "工具进度" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这将从 hermes 中移除该凭证。上游提供方的密钥不会被撤销。" } } } }, - "Tools": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Tools" + "This removes the profile directory and all data within it. This cannot be undone." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Damit werden das Profilverzeichnis und alle darin enthaltenen Daten entfernt. Dies kann nicht rückgängig gemacht werden." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Herramientas" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esto elimina el directorio del perfil y todos sus datos. No se puede deshacer." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Outils" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cela supprime le répertoire du profil et toutes les données qu'il contient. Action irréversible." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ツール" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これによりプロファイルディレクトリとその中のすべてのデータが削除されます。元に戻せません。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ferramentas" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Isso remove o diretório do perfil e todos os dados dentro dele. Não pode ser desfeito." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "工具" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这将移除配置目录及其中所有数据。此操作无法撤销。" } } } }, - "Top Tools": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Top-Tools" + "This removes the scheduled job permanently." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Damit wird der geplante Job dauerhaft entfernt." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Herramientas principales" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esto elimina la tarea programada de forma permanente." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Outils principaux" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cela supprime définitivement la tâche planifiée." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "トップツール" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これによりスケジュールされたジョブが恒久的に削除されます。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Principais ferramentas" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Isso remove a tarefa agendada permanentemente." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "常用工具" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这将永久移除该定时任务。" } } } }, - "Turns & Reasoning": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Turns & Reasoning" + "This removes the server from config.yaml and deletes any OAuth token." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Damit wird der Server aus config.yaml entfernt und jedes OAuth-Token gelöscht." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Turnos y razonamiento" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esto elimina el servidor de config.yaml y borra cualquier token OAuth." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tours et raisonnement" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cela retire le serveur de config.yaml et supprime tout jeton OAuth." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ターンと推論" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これにより config.yaml からサーバーが削除され、OAuth トークンも削除されます。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Turnos e raciocínio" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Isso remove o servidor do config.yaml e apaga qualquer token OAuth." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "轮次与推理" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这将从 config.yaml 中移除服务器并删除任何 OAuth 令牌。" } } } }, - "URL": {}, - "Uninstall": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Deinstallieren" + "This template has no configuration fields." : { + "comment" : "A description of a template with no configuration fields.", + "isCommentAutoGenerated" : true + }, + "This uploads logs, config (with secrets redacted), and system info to Nous Research support infrastructure. Review the output below before sharing the returned URL." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Damit werden Logs, Konfiguration (mit geschwärzten Secrets) und Systeminfos zur Support-Infrastruktur von Nous Research hochgeladen. Prüfe die Ausgabe unten, bevor du die zurückgegebene URL teilst." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Desinstalar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esto sube registros, configuración (con secretos redactados) e información del sistema a la infraestructura de soporte de Nous Research. Revisa la salida antes de compartir la URL devuelta." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Désinstaller" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cela téléverse les journaux, la configuration (secrets masqués) et les infos système vers l'infrastructure de support Nous Research. Vérifiez la sortie ci-dessous avant de partager l'URL retournée." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アンインストール" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これにより、ログ、設定(シークレットはマスク済み)、システム情報が Nous Research のサポートインフラにアップロードされます。返された URL を共有する前に下の出力を確認してください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Desinstalar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Isso envia logs, configuração (com segredos mascarados) e informações do sistema para a infraestrutura de suporte da Nous Research. Revise a saída antes de compartilhar a URL retornada." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "卸载" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这会将日志、配置(密钥已脱敏)和系统信息上传到 Nous Research 支持基础设施。分享返回的 URL 前请查看下方输出。" } } } }, - "Unknown: %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Unbekannt: %@" + "This will overwrite files under ~/.hermes/ with the archive contents." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Damit werden Dateien unter ~/.hermes/ mit dem Archivinhalt überschrieben." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Desconocido: %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esto sobrescribirá archivos bajo ~/.hermes/ con el contenido del archivo." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Inconnu : %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cela écrasera les fichiers sous ~/.hermes/ avec le contenu de l'archive." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "不明: %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これにより ~/.hermes/ 以下のファイルがアーカイブの内容で上書きされます。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Desconhecido: %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Isso sobrescreverá arquivos em ~/.hermes/ com o conteúdo do arquivo." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未知:%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这将使用归档内容覆盖 ~/.hermes/ 下的文件。" } } } }, - "Update": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktualisieren" + "This will permanently delete the session and all its messages." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Damit werden die Sitzung und alle ihre Nachrichten dauerhaft gelöscht." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Actualizar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esto eliminará permanentemente la sesión y todos sus mensajes." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Mettre à jour" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cela supprimera définitivement la session et tous ses messages." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "更新" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これによりセッションとそのすべてのメッセージが恒久的に削除されます。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Atualizar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Isso excluirá permanentemente a sessão e todas as suas mensagens." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "更新" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "这将永久删除该会话及其所有消息。" } } } }, - "Update All": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Alle aktualisieren" + "Timeout: %llds (%@)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Timeout: %1$lld s (%2$@)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Actualizar todo" + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Timeout: %1$llds (%2$@)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tout mettre à jour" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tiempo de espera: %1$lld s (%2$@)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "すべて更新" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Délai : %1$lld s (%2$@)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Atualizar todos" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "タイムアウト: %1$lld 秒 (%2$@)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "全部更新" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tempo limite: %1$lld s (%2$@)" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "超时:%1$lld 秒 (%2$@)" } } } }, - "Updated: %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Aktualisiert: %@" + "Timeouts" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Timeouts" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Actualizado: %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tiempos de espera" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Mis à jour : %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Délais" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "更新日時: %@" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "タイムアウト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Atualizado: %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tempos limite" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "已更新:%@" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "超时" } } } }, - "Updates": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Updates" + "Tirith Sandbox" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tirith-Sandbox" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Actualizaciones" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sandbox Tirith" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Mises à jour" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bac à sable Tirith" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アップデート" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tirith サンドボックス" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Atualizações" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sandbox Tirith" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "更新" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tirith 沙箱" } } } }, - "Upload": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Hochladen" + "To skip the passphrase prompt at every reboot, add `--apple-use-keychain` to cache it in macOS Keychain." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Um die Passphrase-Abfrage bei jedem Neustart zu überspringen, füge `--apple-use-keychain` hinzu, um sie im macOS-Schlüsselbund zu cachen." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Subir" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Para evitar que pida la frase de contraseña en cada reinicio, añade `--apple-use-keychain` para almacenarla en el llavero de macOS." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Téléverser" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pour éviter la demande de phrase secrète à chaque redémarrage, ajoutez `--apple-use-keychain` pour la mettre en cache dans le trousseau macOS." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "アップロード" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "毎回の再起動時にパスフレーズのプロンプトをスキップするには、`--apple-use-keychain` を追加して macOS キーチェーンにキャッシュしてください。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Enviar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Para pular a solicitação de frase secreta a cada reinicialização, adicione `--apple-use-keychain` para armazená-la no Keychain do macOS." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "上传" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "要跳过每次重启时的密码短语提示,添加 `--apple-use-keychain` 以将其缓存到 macOS Keychain 中。" } } } }, - "Upload debug report?": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Debug-Bericht hochladen?" + "Toggle text-to-speech (/voice tts)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Text-to-Speech umschalten (/voice tts)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "¿Subir informe de depuración?" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alternar texto a voz (/voice tts)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Téléverser le rapport de débogage ?" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basculer la synthèse vocale (/voice tts)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "デバッグレポートをアップロードしますか?" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "テキスト読み上げを切り替え (/voice tts)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Enviar relatório de depuração?" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alternar texto-para-fala (/voice tts)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "上传调试报告?" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "切换文字转语音 (/voice tts)" } } } }, - "Usage Stats": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Nutzungsstatistik" + "Toggle voice mode (/voice)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sprachmodus umschalten (/voice)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Estadísticas de uso" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alternar modo de voz (/voice)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Statistiques d'utilisation" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Basculer le mode vocal (/voice)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "使用統計" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "音声モードを切り替え (/voice)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Estatísticas de uso" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alternar modo de voz (/voice)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "使用统计" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "切换语音模式 (/voice)" } } } }, - "Use": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Verwenden" + "Token on disk. Clear to re-authenticate next time the gateway connects." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Token auf der Festplatte. Löschen, damit sich das Gateway beim nächsten Verbinden neu authentifiziert." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Usar" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Token en disco. Bórralo para que se vuelva a autenticar en la próxima conexión del gateway." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Utiliser" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jeton sur disque. Effacez-le pour forcer une nouvelle authentification à la prochaine connexion du gateway." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "使用" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "トークンはディスク上にあります。消去すると、次回 gateway が接続する際に再認証します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Usar" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Token no disco. Limpe para reautenticar na próxima vez que o gateway conectar." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "使用" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "令牌已保存到磁盘。清除后,gateway 下次连接时将重新认证。" } } } }, - "Use a model not in the catalog. Hermes accepts any string the provider recognizes, including provider-prefixed forms like \"openrouter/anthropic/claude-opus-4.6\".": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Verwende ein Modell, das nicht im Katalog ist. Hermes akzeptiert jede Zeichenkette, die der Anbieter erkennt, inklusive anbieter-präfixierter Formen wie \"openrouter/anthropic/claude-opus-4.6\"." + "Tool Approval Required" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tool-Genehmigung erforderlich" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Usa un modelo que no esté en el catálogo. Hermes acepta cualquier cadena que reconozca el proveedor, incluidas formas con prefijo como «openrouter/anthropic/claude-opus-4.6»." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se requiere aprobación de herramienta" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Utilisez un modèle absent du catalogue. Hermes accepte toute chaîne reconnue par le fournisseur, y compris des formes préfixées comme « openrouter/anthropic/claude-opus-4.6 »." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Approbation d'outil requise" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "カタログにないモデルを使用します。Hermes はプロバイダーが認識する任意の文字列を受け付けます(例: \"openrouter/anthropic/claude-opus-4.6\" のようなプロバイダープレフィックス形式も可)。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ツールの承認が必要" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Use um modelo fora do catálogo. O Hermes aceita qualquer string reconhecida pelo provedor, inclusive formas com prefixo do provedor como \"openrouter/anthropic/claude-opus-4.6\"." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aprovação de ferramenta necessária" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "使用未登记在目录中的模型。Hermes 接受提供方识别的任何字符串,包括带有提供方前缀的形式,如 \"openrouter/anthropic/claude-opus-4.6\"。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "需要工具批准" } } } }, - "Use this": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Dieses verwenden" + "Tool Filters" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tool-Filter" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Usar este" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtros de herramientas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Utiliser celui-ci" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtres d'outils" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "これを使用" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ツールフィルタ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Usar este" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Filtros de ferramentas" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "使用此项" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "工具过滤器" } } } }, - "Use {dot.notation} to reference fields in the webhook payload.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Verwende {dot.notation}, um Felder im Webhook-Payload zu referenzieren." + "Tool Progress" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tool-Fortschritt" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Usa {dot.notation} para referenciar campos del payload del webhook." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Progreso de herramientas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Utilisez {dot.notation} pour référencer des champs dans la charge utile du webhook." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Progression des outils" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "webhook ペイロードのフィールドを参照するには {dot.notation} を使用します。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ツールの進捗" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Use {dot.notation} para referenciar campos no payload do webhook." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Progresso da ferramenta" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "使用 {dot.notation} 引用 webhook payload 中的字段。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "工具进度" } } } }, - "Used as the YAML key. Lowercase, no spaces.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Wird als YAML-Schlüssel verwendet. Kleinbuchstaben, keine Leerzeichen." + "tool_a, tool_b" : { + + }, + "tool_c" : { + + }, + "Tools" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tools" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Se usa como clave YAML. Minúsculas, sin espacios." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Herramientas" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Utilisé comme clé YAML. Minuscules, sans espaces." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Outils" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "YAML キーとして使用されます。小文字・空白なし。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ツール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Usado como chave YAML. Minúsculas, sem espaços." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ferramentas" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "用作 YAML 键。小写,不含空格。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "工具" } } } }, - "View": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Anzeigen" + "Top Tools" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Top-Tools" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ver" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Herramientas principales" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Voir" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Outils principaux" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "トップツール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ver" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Principais ferramentas" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "查看" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "常用工具" } } } }, - "View All": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Alle anzeigen" + "TTS Off" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS aus" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Ver todo" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS apagado" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Tout voir" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS désactivé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "すべて表示" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS オフ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Ver tudo" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS desligado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "查看全部" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS 关" } } } }, - "Vision": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Vision" + "TTS On" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS an" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Visión" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS encendido" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Vision" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS activé" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ビジョン" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS オン" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Visão" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS ligado" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "视觉" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "TTS 开" } } } }, - "Voice": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Stimme" + "Turns & Reasoning" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Turns & Reasoning" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Voz" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Turnos y razonamiento" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Voix" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tours et raisonnement" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "音声" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ターンと推論" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Voz" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Turnos e raciocínio" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "语音" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "轮次与推理" } } } }, - "Voice Off": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Stimme aus" + "Uninstall" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deinstallieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Voz desactivada" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desinstalar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Voix désactivée" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Désinstaller" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "音声オフ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アンインストール" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Voz desligada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desinstalar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "语音关" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "卸载" } } } }, - "Voice On": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Stimme an" + "Uninstall Failed" : { + + }, + "Uninstall template" : { + "comment" : "A button that uninstalls a template.", + "isCommentAutoGenerated" : true + }, + "Uninstall Template (remove installed files)…" : { + "comment" : "A button that removes a project's files from the system.", + "isCommentAutoGenerated" : true + }, + "Unknown: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unbekannt: %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Voz activada" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desconocido: %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Voix activée" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Inconnu : %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "音声オン" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "不明: %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Voz ligada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desconhecido: %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "语音开" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未知:%@" } } } }, - "Waiting for authorization URL…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Warte auf Autorisierungs-URL…" + "Update" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualisieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esperando URL de autorización…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "En attente de l'URL d'autorisation…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mettre à jour" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "認可 URL を待機中…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Aguardando URL de autorização…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atualizar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "等待授权 URL…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新" } } } }, - "Waiting for first probe": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Warte auf erste Prüfung" + "Update All" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle aktualisieren" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esperando primera comprobación" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizar todo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "En attente de la première sonde" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tout mettre à jour" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "最初のプローブを待機中" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すべて更新" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Aguardando primeira verificação" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atualizar todos" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "等待首次探测" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "全部更新" } } } }, - "Waiting for hermes to prompt for the code…": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Warte, bis hermes nach dem Code fragt…" + "Updated: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aktualisiert: %@" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Esperando a que hermes pida el código…" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizado: %@" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "En attente que hermes demande le code…" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mis à jour : %@" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "hermes がコードを要求するのを待機中…" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新日時: %@" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Aguardando o hermes solicitar o código…" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atualizado: %@" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "等待 hermes 提示输入代码…" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已更新:%@" } } } }, - "Web Extract": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Web-Extraktion" + "Updates" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Updates" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Extracción web" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Actualizaciones" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Extraction Web" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mises à jour" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Web 抽出" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アップデート" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Extração da Web" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Atualizações" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "网页提取" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "更新" } } } }, - "Webhook (advanced)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Webhook (erweitert)" + "Upload" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hochladen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Webhook (avanzado)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Subir" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Webhook (avancé)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Téléverser" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Webhook(詳細)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "アップロード" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Webhook (avançado)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enviar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Webhook(高级)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "上传" } } } }, - "Webhook (hermes side)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Webhook (hermes-Seite)" + "Upload debug report?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Debug-Bericht hochladen?" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Webhook (lado hermes)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Subir informe de depuración?" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Webhook (côté hermes)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Téléverser le rapport de débogage ?" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Webhook(hermes 側)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "デバッグレポートをアップロードしますか?" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Webhook (lado hermes)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enviar relatório de depuração?" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Webhook(hermes 侧)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "上传调试报告?" } } } }, - "Webhook Security": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Webhook-Sicherheit" + "URL" : { + + }, + "Usage Stats" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nutzungsstatistik" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Seguridad de webhook" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Estadísticas de uso" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Sécurité webhook" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Statistiques d'utilisation" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Webhook セキュリティ" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用統計" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Segurança de webhook" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Estatísticas de uso" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Webhook 安全" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用统计" } } } }, - "Webhook Setup Docs": {}, - "Webhook platform not enabled": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Webhook-Plattform nicht aktiviert" + "Use" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwenden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Plataforma de webhooks no activada" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usar" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Plateforme webhook non activée" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Utiliser" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Webhook プラットフォームが有効ではありません" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Plataforma de webhook não ativada" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usar" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "未启用 webhook 平台" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用" } } } }, - "Webhooks": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Webhooks" + "Use {dot.notation} to reference fields in the webhook payload." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwende {dot.notation}, um Felder im Webhook-Payload zu referenzieren." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Webhooks" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usa {dot.notation} para referenciar campos del payload del webhook." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Webhooks" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Utilisez {dot.notation} pour référencer des champs dans la charge utile du webhook." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Webhook" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "webhook ペイロードのフィールドを参照するには {dot.notation} を使用します。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Webhooks" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Use {dot.notation} para referenciar campos no payload do webhook." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Webhooks" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用 {dot.notation} 引用 webhook payload 中的字段。" } } } }, - "Webhooks let external services trigger agent responses. Each subscription gets its own URL endpoint.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Webhooks ermöglichen externen Diensten, Agent-Antworten auszulösen. Jedes Abonnement hat seinen eigenen URL-Endpunkt." + "Use a model not in the catalog. Hermes accepts any string the provider recognizes, including provider-prefixed forms like \"openrouter/anthropic/claude-opus-4.6\"." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verwende ein Modell, das nicht im Katalog ist. Hermes akzeptiert jede Zeichenkette, die der Anbieter erkennt, inklusive anbieter-präfixierter Formen wie \"openrouter/anthropic/claude-opus-4.6\"." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Los webhooks permiten que servicios externos disparen respuestas del agente. Cada suscripción obtiene su propio endpoint de URL." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usa un modelo que no esté en el catálogo. Hermes acepta cualquier cadena que reconozca el proveedor, incluidas formas con prefijo como «openrouter/anthropic/claude-opus-4.6»." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Les webhooks permettent à des services externes de déclencher des réponses d'agent. Chaque abonnement a son propre point d'accès URL." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Utilisez un modèle absent du catalogue. Hermes accepte toute chaîne reconnue par le fournisseur, y compris des formes préfixées comme « openrouter/anthropic/claude-opus-4.6 »." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "Webhook を使うと外部サービスがエージェントの応答をトリガーできます。各サブスクリプションは独自の URL エンドポイントを持ちます。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "カタログにないモデルを使用します。Hermes はプロバイダーが認識する任意の文字列を受け付けます(例: \"openrouter/anthropic/claude-opus-4.6\" のようなプロバイダープレフィックス形式も可)。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Webhooks permitem que serviços externos disparem respostas do agente. Cada assinatura tem seu próprio endpoint de URL." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Use um modelo fora do catálogo. O Hermes aceita qualquer string reconhecida pelo provedor, inclusive formas com prefixo do provedor como \"openrouter/anthropic/claude-opus-4.6\"." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "Webhooks 允许外部服务触发 agent 响应。每个订阅都有自己的 URL 端点。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用未登记在目录中的模型。Hermes 接受提供方识别的任何字符串,包括带有提供方前缀的形式,如 \"openrouter/anthropic/claude-opus-4.6\"。" } } } }, - "Website Blocklist": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Website-Blockliste" + "Use this" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dieses verwenden" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Lista de bloqueo de sitios" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usar este" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Liste de blocage de sites" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Utiliser celui-ci" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ウェブサイトブロックリスト" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これを使用" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Lista de bloqueio de sites" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usar este" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "网站黑名单" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "使用此项" } } } }, - "WhatsApp Setup Docs": {}, - "WhatsApp uses the Baileys library to emulate a WhatsApp Web session. Pair this Mac as a linked device by running the pairing wizard and scanning the QR code with your phone (Settings → Linked Devices → Link a Device).": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "WhatsApp nutzt die Baileys-Bibliothek, um eine WhatsApp-Web-Sitzung zu emulieren. Kopple diesen Mac als verknüpftes Gerät, indem du den Kopplungsassistenten ausführst und den QR-Code mit deinem Telefon scannst (Einstellungen → Verknüpfte Geräte → Gerät verknüpfen)." + "Used as the YAML key. Lowercase, no spaces." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wird als YAML-Schlüssel verwendet. Kleinbuchstaben, keine Leerzeichen." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "WhatsApp usa la biblioteca Baileys para emular una sesión de WhatsApp Web. Empareja este Mac como dispositivo vinculado ejecutando el asistente y escaneando el código QR con tu teléfono (Ajustes → Dispositivos vinculados → Vincular dispositivo)." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se usa como clave YAML. Minúsculas, sin espacios." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "WhatsApp utilise la bibliothèque Baileys pour émuler une session WhatsApp Web. Appairez ce Mac comme appareil lié en lançant l'assistant d'appairage et en scannant le QR code avec votre téléphone (Paramètres → Appareils liés → Associer un appareil)." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Utilisé comme clé YAML. Minuscules, sans espaces." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "WhatsApp は Baileys ライブラリで WhatsApp Web セッションをエミュレートします。ペアリングウィザードを実行し、スマートフォンで QR コードをスキャンしてこの Mac をリンク済みデバイスとしてペアリングしてください(設定 → リンク済みデバイス → デバイスをリンク)。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "YAML キーとして使用されます。小文字・空白なし。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "O WhatsApp usa a biblioteca Baileys para emular uma sessão do WhatsApp Web. Pareie este Mac como dispositivo vinculado executando o assistente de pareamento e escaneando o QR code com seu telefone (Ajustes → Dispositivos vinculados → Vincular um dispositivo)." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Usado como chave YAML. Minúsculas, sem espaços." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "WhatsApp 使用 Baileys 库模拟 WhatsApp Web 会话。通过运行配对向导并用手机扫描二维码,将此 Mac 配对为关联设备(设置 → 关联设备 → 关联一台设备)。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "用作 YAML 键。小写,不含空格。" } } } }, - "Working": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "In Arbeit" + "user" : { + + }, + "v%@" : { + "comment" : "A version number.", + "isCommentAutoGenerated" : true + }, + "value" : { + + }, + "Version" : { + + }, + "View" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Anzeigen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Trabajando" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ver" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Travail en cours" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voir" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "処理中" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "Trabalhando" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ver" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "工作中" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看" } } } }, - "X": {}, - "Y": {}, - "active": {}, - "dangerous": {}, - "default": {}, - "e.g. anthropic": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "z. B. anthropic" + "View All" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alle anzeigen" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "p. ej. anthropic" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ver todo" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "par ex. anthropic" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tout voir" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "例: anthropic" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すべて表示" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "ex: anthropic" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ver tudo" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "例如 anthropic" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查看全部" } } } }, - "e.g. deploy": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "z. B. deploy" + "Vision" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vision" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "p. ej. deploy" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visión" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "par ex. deploy" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vision" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "例: deploy" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ビジョン" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "ex: deploy" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visão" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "例如 deploy" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "视觉" } } } }, - "e.g. experimental": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "z. B. experimental" + "Voice" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stimme" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "p. ej. experimental" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voz" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "par ex. experimental" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voix" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "例: experimental" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "音声" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "ex: experimental" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voz" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "例如 experimental" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "语音" } } } }, - "e.g. github": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "z. B. github" + "Voice Off" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stimme aus" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "p. ej. github" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voz desactivada" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "par ex. github" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voix désactivée" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "例: github" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "音声オフ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "ex: github" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voz desligada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "例如 github" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "语音关" } } } }, - "e.g. openai": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "z. B. openai" + "Voice On" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Stimme an" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "p. ej. openai" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voz activada" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "par ex. openai" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voix activée" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "例: openai" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "音声オン" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "ex: openai" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Voz ligada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "例如 openai" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "语音开" } } } }, - "e.g. openai/gpt-4o": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "z. B. openai/gpt-4o" + "Waiting for authorization URL…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Warte auf Autorisierungs-URL…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "p. ej. openai/gpt-4o" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esperando URL de autorización…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "par ex. openai/gpt-4o" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "En attente de l'URL d'autorisation…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "例: openai/gpt-4o" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "認可 URL を待機中…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "ex: openai/gpt-4o" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aguardando URL de autorização…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "例如 openai/gpt-4o" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "等待授权 URL…" } } } }, - "e.g. team-prod": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "z. B. team-prod" + "Waiting for first probe" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Warte auf erste Prüfung" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "p. ej. team-prod" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esperando primera comprobación" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "par ex. team-prod" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "En attente de la première sonde" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "例: team-prod" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "最初のプローブを待機中" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "ex: team-prod" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aguardando primeira verificação" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "例如 team-prod" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "等待首次探测" } } } }, - "exit code: %d": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Exit-Code: %d" + "Waiting for hermes to prompt for the code…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Warte, bis hermes nach dem Code fragt…" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "código de salida: %d" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esperando a que hermes pida el código…" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "code de sortie : %d" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "En attente que hermes demande le code…" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "終了コード: %d" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "hermes がコードを要求するのを待機中…" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "código de saída: %d" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aguardando o hermes solicitar o código…" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "退出码:%d" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "等待 hermes 提示输入代码…" } } } }, - "github.com/owner/plugin-repo or owner/repo": {}, - "hermes at %@": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "hermes auf %@" + "Web Extract" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Web-Extraktion" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "hermes en %@" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Extracción web" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "hermes sur %@" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Extraction Web" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "%@ 上の hermes" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Web 抽出" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "hermes em %@" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Extração da Web" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "%@ 上的 hermes" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "网页提取" } } } }, - "hermes profile show": {}, - "hermes.example.com or a ~/.ssh/config alias": {}, - "https://...": {}, - "iMessage integration runs through BlueBubbles Server. You need a Mac that stays on with Messages.app signed in — install BlueBubbles Server on it, then point hermes at that server's URL.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "Die iMessage-Integration läuft über BlueBubbles Server. Du brauchst einen Mac, der eingeschaltet bleibt und in Messages.app angemeldet ist — installiere BlueBubbles Server darauf und richte hermes auf die URL dieses Servers aus." + "Webhook (advanced)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook (erweitert)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "La integración de iMessage usa BlueBubbles Server. Necesitas un Mac encendido con Messages.app iniciado — instala BlueBubbles Server ahí y apunta hermes a la URL de ese servidor." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook (avanzado)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "L'intégration iMessage passe par BlueBubbles Server. Il vous faut un Mac qui reste allumé avec Messages.app connecté — installez BlueBubbles Server dessus, puis pointez hermes vers l'URL de ce serveur." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook (avancé)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "iMessage 連携は BlueBubbles Server を経由します。メッセージ App にサインインしたまま動作し続ける Mac が必要です — そこに BlueBubbles Server をインストールし、そのサーバーの URL を hermes に指定してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook(詳細)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "A integração com iMessage usa o BlueBubbles Server. Você precisa de um Mac ligado e com Messages.app conectado — instale o BlueBubbles Server nele e aponte o hermes para a URL desse servidor." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook (avançado)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "iMessage 集成通过 BlueBubbles Server 运行。你需要一台保持开机且登录 Messages.app 的 Mac — 在其上安装 BlueBubbles Server,然后将 hermes 指向该服务器的 URL。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook(高级)" } } } }, - "my_server": {}, - "new-name": {}, - "npx": {}, - "required": {}, - "signal-cli Terminal": {}, - "signal-cli is available on PATH": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "signal-cli ist im PATH verfügbar" + "Webhook (hermes side)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook (hermes-Seite)" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "signal-cli está disponible en el PATH" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook (lado hermes)" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "signal-cli est disponible dans le PATH" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook (côté hermes)" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "signal-cli は PATH 上で利用可能です" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook(hermes 側)" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "signal-cli está disponível no PATH" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook (lado hermes)" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "signal-cli 已在 PATH 中" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook(hermes 侧)" } } } }, - "signal-cli not found on PATH — install it first": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "signal-cli im PATH nicht gefunden — bitte zuerst installieren" + "Webhook platform not enabled" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook-Plattform nicht aktiviert" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "signal-cli no está en el PATH — instálalo primero" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plataforma de webhooks no activada" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "signal-cli introuvable dans le PATH — installez-le d'abord" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plateforme webhook non activée" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "signal-cli が PATH にありません — 先にインストールしてください" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook プラットフォームが有効ではありません" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "signal-cli não encontrado no PATH — instale-o primeiro" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Plataforma de webhook não ativada" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "PATH 中未找到 signal-cli — 请先安装" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "未启用 webhook 平台" } } } }, - "sk-…": {}, - "ssh trace": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "ssh-Trace" + "Webhook Security" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook-Sicherheit" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "traza ssh" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Seguridad de webhook" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "trace ssh" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sécurité webhook" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ssh トレース" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook セキュリティ" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "trace do ssh" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Segurança de webhook" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "ssh 跟踪" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook 安全" } } } }, - "ssh-agent (leave blank)": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "ssh-agent (leer lassen)" + "Webhook Setup Docs" : { + + }, + "Webhooks" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhooks" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "ssh-agent (dejar vacío)" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhooks" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "ssh-agent (laisser vide)" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhooks" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ssh-agent(空のまま)" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "ssh-agent (deixe em branco)" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhooks" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "ssh-agent(留空)" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhooks" } } } }, - "state.db not found at the configured path. Either Hermes hasn't run yet on this server, or it's installed at a non-default location — set the Hermes data directory field above.": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "state.db wurde am konfigurierten Pfad nicht gefunden. Entweder lief Hermes auf diesem Server noch nicht, oder es ist an einem nicht standardmäßigen Ort installiert — setze oben das Feld für das Hermes-Datenverzeichnis." + "Webhooks let external services trigger agent responses. Each subscription gets its own URL endpoint." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhooks ermöglichen externen Diensten, Agent-Antworten auszulösen. Jedes Abonnement hat seinen eigenen URL-Endpunkt." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "No se encontró state.db en la ruta configurada. O bien Hermes no se ha ejecutado aún en este servidor, o está instalado en una ubicación no predeterminada — establece arriba el campo del directorio de datos de Hermes." + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Los webhooks permiten que servicios externos disparen respuestas del agente. Cada suscripción obtiene su propio endpoint de URL." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "state.db introuvable au chemin configuré. Soit Hermes n'a pas encore été lancé sur ce serveur, soit il est installé à un emplacement non standard — définissez le champ répertoire de données Hermes ci-dessus." + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Les webhooks permettent à des services externes de déclencher des réponses d'agent. Chaque abonnement a son propre point d'accès URL." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "設定されたパスに state.db が見つかりません。Hermes がこのサーバーでまだ実行されていないか、デフォルトでない場所にインストールされている可能性があります — 上の Hermes データディレクトリフィールドを設定してください。" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhook を使うと外部サービスがエージェントの応答をトリガーできます。各サブスクリプションは独自の URL エンドポイントを持ちます。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "state.db não encontrado no caminho configurado. Ou o Hermes ainda não rodou neste servidor, ou está instalado em um local não padrão — defina o campo de diretório de dados do Hermes acima." + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhooks permitem que serviços externos disparem respostas do agente. Cada assinatura tem seu próprio endpoint de URL." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "在配置的路径下未找到 state.db。要么 Hermes 尚未在此服务器上运行过,要么它安装在非默认位置 — 请在上方设置 Hermes 数据目录字段。" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Webhooks 允许外部服务触发 agent 响应。每个订阅都有自己的 URL 端点。" } } } }, - "state.db not found at the default location, but Scarf found one at:": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "state.db wurde am Standardort nicht gefunden, Scarf hat aber eine unter folgendem Pfad entdeckt:" + "Website Blocklist" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Website-Blockliste" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "No se encontró state.db en la ubicación predeterminada, pero Scarf encontró uno en:" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lista de bloqueo de sitios" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "state.db introuvable à l'emplacement par défaut, mais Scarf en a trouvé une à :" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Liste de blocage de sites" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "デフォルトの場所には state.db がありませんが、Scarf が以下の場所に見つけました:" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ウェブサイトブロックリスト" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "state.db não encontrado no local padrão, mas o Scarf encontrou um em:" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lista de bloqueio de sites" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "默认位置下未找到 state.db,但 Scarf 在此处找到了一个:" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "网站黑名单" } } } }, - "state.db readable": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "state.db lesbar" + "WhatsApp Setup Docs" : { + + }, + "WhatsApp uses the Baileys library to emulate a WhatsApp Web session. Pair this Mac as a linked device by running the pairing wizard and scanning the QR code with your phone (Settings → Linked Devices → Link a Device)." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "WhatsApp nutzt die Baileys-Bibliothek, um eine WhatsApp-Web-Sitzung zu emulieren. Kopple diesen Mac als verknüpftes Gerät, indem du den Kopplungsassistenten ausführst und den QR-Code mit deinem Telefon scannst (Einstellungen → Verknüpfte Geräte → Gerät verknüpfen)." } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "state.db legible" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "WhatsApp usa la biblioteca Baileys para emular una sesión de WhatsApp Web. Empareja este Mac como dispositivo vinculado ejecutando el asistente y escaneando el código QR con tu teléfono (Ajustes → Dispositivos vinculados → Vincular dispositivo)." } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "state.db lisible" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "WhatsApp utilise la bibliothèque Baileys pour émuler une session WhatsApp Web. Appairez ce Mac comme appareil lié en lançant l'assistant d'appairage et en scannant le QR code avec votre téléphone (Paramètres → Appareils liés → Associer un appareil)." } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "state.db 読み取り可能" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "WhatsApp は Baileys ライブラリで WhatsApp Web セッションをエミュレートします。ペアリングウィザードを実行し、スマートフォンで QR コードをスキャンしてこの Mac をリンク済みデバイスとしてペアリングしてください(設定 → リンク済みデバイス → デバイスをリンク)。" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "state.db legível" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "O WhatsApp usa a biblioteca Baileys para emular uma sessão do WhatsApp Web. Pareie este Mac como dispositivo vinculado executando o assistente de pareamento e escaneando o QR code com seu telefone (Ajustes → Dispositivos vinculados → Vincular um dispositivo)." } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "state.db 可读" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "WhatsApp 使用 Baileys 库模拟 WhatsApp Web 会话。通过运行配对向导并用手机扫描二维码,将此 Mac 配对为关联设备(设置 → 关联设备 → 关联一台设备)。" } } } }, - "stderr:": {}, - "stdout:": {}, - "tool_a, tool_b": {}, - "tool_c": {}, - "user": {}, - "value": {}, - "·": {}, - "— or use user/password login —": { - "localizations": { - "de": { - "stringUnit": { - "state": "translated", - "value": "— oder Benutzername/Passwort-Anmeldung verwenden —" + "Where should this project live?" : { + + }, + "Will be saved to the Keychain on commit." : { + "comment" : "A description of a secret field that will be saved to the Keychain on commit.", + "isCommentAutoGenerated" : true + }, + "Working" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "In Arbeit" } }, - "es": { - "stringUnit": { - "state": "translated", - "value": "— o usa inicio de sesión con usuario/contraseña —" + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trabajando" } }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "— ou utilisez la connexion utilisateur/mot de passe —" + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Travail en cours" } }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "— またはユーザー名/パスワードでログイン —" + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "処理中" } }, - "pt-BR": { - "stringUnit": { - "state": "translated", - "value": "— ou use login com usuário/senha —" + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Trabalhando" } }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "— 或使用用户名/密码登录 —" + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "工作中" } } } }, - "•": {}, - "••••••••••": {} + "X" : { + + }, + "Y" : { + + }, + "Your name" : { + "comment" : "A label for the user's name.", + "isCommentAutoGenerated" : true + } }, - "version": "1.0" -} + "version" : "1.1" +} \ No newline at end of file diff --git a/scarf/scarf/scarfApp.swift b/scarf/scarf/scarfApp.swift index 92cdfa5..ea0836f 100644 --- a/scarf/scarf/scarfApp.swift +++ b/scarf/scarf/scarfApp.swift @@ -57,7 +57,23 @@ struct ScarfApp: App { // covers the case where the user added a server in // another window since this one last opened. .onAppear { liveRegistry.rebuild() } + // scarf://install?url=… deep-link handler. Stages the + // URL on the process-wide router; ProjectsView picks it + // up and presents the install sheet. Activating the + // app here ensures a cold launch from a browser click + // surfaces the sheet without the user having to click + // into Scarf first. + .onOpenURL { url in + TemplateURLRouter.shared.handle(url) + NSApplication.shared.activate() + } } else { + // MissingServerView is a dead-end "server was removed" pane + // with no ProjectsView — so no observer of the router's + // pendingInstallURL exists in this window. Routing a + // scarf://install URL here would silently drop it. Leave + // onOpenURL off this branch; ContextBoundRoot windows in + // the same app instance will still handle it. MissingServerView(removedServerID: serverID) .environment(registry) .environment(updater) diff --git a/scarf/scarfTests/ProjectTemplateTests.swift b/scarf/scarfTests/ProjectTemplateTests.swift new file mode 100644 index 0000000..6b14205 --- /dev/null +++ b/scarf/scarfTests/ProjectTemplateTests.swift @@ -0,0 +1,1135 @@ +import Testing +import Foundation +@testable import scarf + +/// Cross-suite serialization lock for tests that touch the real +/// `~/.hermes/scarf/projects.json`. Swift Testing's `.serialized` trait +/// only serializes tests WITHIN a suite — multiple suites still run in +/// parallel. Three suites in this file write to the same file and +/// previously raced each other silently (saveRegistry used to swallow +/// write failures); now that saveRegistry throws, the race surfaces. +/// +/// The lock is acquired by `acquireAndSnapshot()` at the top of each +/// registry-touching test and released by `restore(_:)` via the test's +/// `defer`. Asymmetric acquire-in-one-fn / release-in-another looks +/// unusual but the snapshot/restore pairing is so tight (every test +/// defers the restore) that it's reliable in practice. +final class TestRegistryLock: @unchecked Sendable { + static let shared = TestRegistryLock() + private let lock = NSLock() + + /// Acquire the cross-suite lock and snapshot the registry. Pair + /// every call with a `defer { TestRegistryLock.restore(snapshot) }`. + static func acquireAndSnapshot() -> Data? { + shared.lock.lock() + let path = ServerContext.local.paths.projectsRegistry + return try? Data(contentsOf: URL(fileURLWithPath: path)) + } + + /// Restore the registry from snapshot and release the lock. + static func restore(_ snapshot: Data?) { + defer { shared.lock.unlock() } + let path = ServerContext.local.paths.projectsRegistry + if let snapshot { + try? snapshot.write(to: URL(fileURLWithPath: path)) + } else { + try? FileManager.default.removeItem(atPath: path) + } + } +} + +/// Exercises the service's ability to unpack, parse, and validate bundles. +/// Doesn't touch the installer — see `ProjectTemplateInstallerTests` — so +/// these don't need write access to ~/.hermes. +@Suite struct ProjectTemplateServiceTests { + + @Test func manifestSlugSanitizesPunctuation() { + let manifest = Self.sampleManifest(id: "alan@w/focus dashboard!") + #expect(manifest.slug == "alan-w-focus-dashboard") + } + + @Test func manifestSlugFallsBackToPlaceholder() { + let manifest = Self.sampleManifest(id: "////") + #expect(manifest.slug == "template") + } + + @Test func inspectRejectsMissingManifest() throws { + let dir = try Self.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: dir) } + + // A zip with no template.json + let bundle = try Self.makeBundle(dir: dir, files: [ + "README.md": "hi", + "AGENTS.md": "hi", + "dashboard.json": "{}" + ], includeManifest: false) + + let service = ProjectTemplateService(context: .local) + #expect(throws: ProjectTemplateError.self) { + try service.inspect(zipPath: bundle) + } + } + + @Test func inspectRejectsMissingAgentsMd() throws { + let dir = try Self.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: dir) } + + let bundle = try Self.makeBundle(dir: dir, files: [ + "README.md": "# Readme", + "dashboard.json": Self.sampleDashboardJSON + ]) + + let service = ProjectTemplateService(context: .local) + #expect(throws: ProjectTemplateError.self) { + try service.inspect(zipPath: bundle) + } + } + + @Test func inspectAcceptsMinimalValidBundle() throws { + let dir = try Self.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: dir) } + + let bundle = try Self.makeBundle(dir: dir, files: [ + "README.md": "# Readme", + "AGENTS.md": "# Agents", + "dashboard.json": Self.sampleDashboardJSON + ]) + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: bundle) + defer { service.cleanupTempDir(inspection.unpackedDir) } + + #expect(inspection.manifest.id == "test/example") + #expect(inspection.manifest.slug == "test-example") + #expect(inspection.cronJobs.isEmpty) + #expect(inspection.files.contains("AGENTS.md")) + } + + @Test func inspectRejectsContentClaimMismatch() throws { + let dir = try Self.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: dir) } + + // Claim cron: 2 but ship no cron dir → service must reject. + let manifest = Self.sampleManifest(cron: 2) + let manifestJSON = try JSONEncoder().encode(manifest) + let manifestString = String(data: manifestJSON, encoding: .utf8)! + + let bundle = try Self.makeBundle(dir: dir, files: [ + "README.md": "# Readme", + "AGENTS.md": "# Agents", + "dashboard.json": Self.sampleDashboardJSON, + "template.json": manifestString + ], includeManifest: false) + + let service = ProjectTemplateService(context: .local) + #expect(throws: ProjectTemplateError.self) { + try service.inspect(zipPath: bundle) + } + } + + // MARK: - Helpers + + static let sampleDashboardJSON = """ + { + "version": 1, + "title": "Example", + "description": "test", + "sections": [] + } + """ + + static func sampleManifest( + id: String = "test/example", + cron: Int? = nil, + skills: [String]? = nil, + instructions: [String]? = nil, + configFieldCount: Int? = nil, + configSchema: TemplateConfigSchema? = nil + ) -> ProjectTemplateManifest { + // schemaVersion auto-bumps to 2 when a schema is present so tests + // that exercise the schema path mirror real manifest behaviour. + let version = (configSchema != nil) ? 2 : 1 + return ProjectTemplateManifest( + schemaVersion: version, + id: id, + name: "Example", + version: "1.0.0", + minScarfVersion: nil, + minHermesVersion: nil, + author: TemplateAuthor(name: "Tester", url: nil), + description: "Test template", + category: nil, + tags: nil, + icon: nil, + screenshots: nil, + contents: TemplateContents( + dashboard: true, + agentsMd: true, + instructions: instructions, + skills: skills, + cron: cron, + memory: nil, + config: configFieldCount ?? configSchema?.fields.count + ), + config: configSchema + ) + } + + static func makeTempDir() throws -> String { + let dir = NSTemporaryDirectory() + "scarf-template-test-" + UUID().uuidString + try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true) + return dir + } + + /// Write files to a staging dir, then zip them into `/bundle.scarftemplate` + /// and return its path. When `includeManifest` is true the caller doesn't + /// need to provide `template.json` — we synthesize a valid one. + static func makeBundle( + dir: String, + files: [String: String], + includeManifest: Bool = true + ) throws -> String { + let staging = dir + "/staging" + try FileManager.default.createDirectory(atPath: staging, withIntermediateDirectories: true) + + for (relativePath, content) in files { + let full = staging + "/" + relativePath + let parent = (full as NSString).deletingLastPathComponent + if !FileManager.default.fileExists(atPath: parent) { + try FileManager.default.createDirectory(atPath: parent, withIntermediateDirectories: true) + } + try content.data(using: .utf8)!.write(to: URL(fileURLWithPath: full)) + } + if includeManifest { + let manifest = sampleManifest() + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let data = try encoder.encode(manifest) + try data.write(to: URL(fileURLWithPath: staging + "/template.json")) + } + + let bundlePath = dir + "/bundle.scarftemplate" + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/zip") + process.currentDirectoryURL = URL(fileURLWithPath: staging) + process.arguments = ["-qq", "-r", bundlePath, "."] + try process.run() + process.waitUntilExit() + #expect(process.terminationStatus == 0) + return bundlePath + } +} + +/// URL-router has no filesystem side effects — safe to unit-test directly. +@Suite struct TemplateURLRouterTests { + + @Test @MainActor func refusesNonScarfScheme() { + let router = TemplateURLRouter.shared + router.pendingInstallURL = nil + let ok = router.handle(URL(string: "https://example.com/foo")!) + #expect(ok == false) + #expect(router.pendingInstallURL == nil) + } + + @Test @MainActor func refusesUnknownHost() { + let router = TemplateURLRouter.shared + router.pendingInstallURL = nil + let ok = router.handle(URL(string: "scarf://bogus?url=https://example.com/x.scarftemplate")!) + #expect(ok == false) + #expect(router.pendingInstallURL == nil) + } + + @Test @MainActor func refusesNonHttpsPayload() { + let router = TemplateURLRouter.shared + router.pendingInstallURL = nil + let ok = router.handle(URL(string: "scarf://install?url=file:///etc/passwd")!) + #expect(ok == false) + #expect(router.pendingInstallURL == nil) + } + + @Test @MainActor func acceptsFileURLWithScarftemplateExtension() { + let router = TemplateURLRouter.shared + router.pendingInstallURL = nil + let path = "/tmp/example.scarftemplate" + let ok = router.handle(URL(fileURLWithPath: path)) + #expect(ok) + #expect(router.pendingInstallURL?.isFileURL == true) + #expect(router.pendingInstallURL?.path == path) + router.consume() + } + + @Test @MainActor func refusesFileURLWithOtherExtension() { + let router = TemplateURLRouter.shared + router.pendingInstallURL = nil + let ok = router.handle(URL(fileURLWithPath: "/tmp/somefile.zip")) + #expect(ok == false) + #expect(router.pendingInstallURL == nil) + } + + @Test @MainActor func acceptsHttpsInstallUrl() { + let router = TemplateURLRouter.shared + router.pendingInstallURL = nil + let target = "https://example.com/foo.scarftemplate" + let ok = router.handle(URL(string: "scarf://install?url=\(target)")!) + #expect(ok) + #expect(router.pendingInstallURL?.absoluteString == target) + router.consume() + } +} + +/// End-to-end install test against a minimal bundle (dashboard + README + +/// AGENTS.md, no skills/cron/memory). Exercises the full install path +/// through `preflight → createProjectFiles → registerProject → +/// writeLockFile`. We avoid touching user state by: +/// 1. Picking a temp `projectDir` under `NSTemporaryDirectory()`. +/// 2. Snapshotting and restoring `~/.hermes/scarf/projects.json` around +/// each test so the registry write is reversible. +/// Skills/cron/memory paths aren't touched because the test bundles claim +/// none. That's the intentional v1 coverage: the project-dir side effects +/// are exhaustively tested; global-state side effects (skills namespace, +/// cron CLI, memory append) are covered by manual verification per the +/// plan's step 7. +@Suite(.serialized) struct ProjectTemplateInstallerTests { + + @Test func installsMinimalBundleAndWritesLockFile() throws { + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + let parentDir = scratch + "/parent" + try FileManager.default.createDirectory(atPath: parentDir, withIntermediateDirectories: true) + + let bundle = try ProjectTemplateServiceTests.makeBundle(dir: scratch, files: [ + "README.md": "# Minimal", + "AGENTS.md": "# Agent notes", + "dashboard.json": ProjectTemplateServiceTests.sampleDashboardJSON + ]) + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: bundle) + defer { service.cleanupTempDir(inspection.unpackedDir) } + let plan = try service.buildPlan(inspection: inspection, parentDir: parentDir) + + let registryBefore = Self.snapshotRegistry() + defer { Self.restoreRegistry(registryBefore) } + + let installer = ProjectTemplateInstaller(context: .local) + let entry = try installer.install(plan: plan) + + #expect(FileManager.default.fileExists(atPath: plan.projectDir)) + #expect(FileManager.default.fileExists(atPath: plan.projectDir + "/AGENTS.md")) + #expect(FileManager.default.fileExists(atPath: plan.projectDir + "/README.md")) + #expect(FileManager.default.fileExists(atPath: plan.projectDir + "/.scarf/dashboard.json")) + #expect(FileManager.default.fileExists(atPath: plan.projectDir + "/.scarf/template.lock.json")) + #expect(entry.path == plan.projectDir) + + let lockData = try Data(contentsOf: URL(fileURLWithPath: plan.projectDir + "/.scarf/template.lock.json")) + let lock = try JSONDecoder().decode(TemplateLock.self, from: lockData) + #expect(lock.templateId == inspection.manifest.id) + #expect(lock.templateVersion == inspection.manifest.version) + #expect(lock.projectFiles.contains(plan.projectDir + "/AGENTS.md")) + #expect(lock.cronJobNames.isEmpty) + #expect(lock.memoryBlockId == nil) + } + + @Test func preflightRejectsExistingProjectDir() throws { + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + let parentDir = scratch + "/parent" + try FileManager.default.createDirectory(atPath: parentDir, withIntermediateDirectories: true) + + let bundle = try ProjectTemplateServiceTests.makeBundle(dir: scratch, files: [ + "README.md": "# Minimal", + "AGENTS.md": "# Agent notes", + "dashboard.json": ProjectTemplateServiceTests.sampleDashboardJSON + ]) + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: bundle) + defer { service.cleanupTempDir(inspection.unpackedDir) } + let plan = try service.buildPlan(inspection: inspection, parentDir: parentDir) + + // Simulate a concurrent creation between buildPlan and install. + try FileManager.default.createDirectory(atPath: plan.projectDir, withIntermediateDirectories: true) + + let installer = ProjectTemplateInstaller(context: .local) + #expect(throws: ProjectTemplateError.self) { + try installer.install(plan: plan) + } + } + + @Test func buildPlanRefusesDuplicateProjectDir() throws { + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + let parentDir = scratch + "/parent" + try FileManager.default.createDirectory(atPath: parentDir, withIntermediateDirectories: true) + + let bundle = try ProjectTemplateServiceTests.makeBundle(dir: scratch, files: [ + "README.md": "# Minimal", + "AGENTS.md": "# Agent notes", + "dashboard.json": ProjectTemplateServiceTests.sampleDashboardJSON + ]) + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: bundle) + defer { service.cleanupTempDir(inspection.unpackedDir) } + + // Pre-create the slugged project dir so buildPlan's collision check + // fires before we get to install. + let slugDir = parentDir + "/" + inspection.manifest.slug + try FileManager.default.createDirectory(atPath: slugDir, withIntermediateDirectories: true) + + #expect(throws: ProjectTemplateError.self) { + try service.buildPlan(inspection: inspection, parentDir: parentDir) + } + } + + // MARK: - Cron prompt token substitution + + @Test func substituteCronTokensResolvesProjectDir() throws { + let plan = try TemplateInstallerViewModelTests.makePlanWithConfigSchema() + let raw = "Read {{PROJECT_DIR}}/.scarf/config.json" + let resolved = ProjectTemplateInstaller.substituteCronTokens(raw, plan: plan) + #expect(resolved == "Read \(plan.projectDir)/.scarf/config.json") + // Original placeholder must be fully replaced — a lingering + // {{PROJECT_DIR}} would leave the cron job trying to read a + // literal file named `{{PROJECT_DIR}}` which doesn't exist. + #expect(resolved.contains("{{PROJECT_DIR}}") == false) + } + + @Test func substituteCronTokensResolvesIdAndSlug() throws { + let plan = try TemplateInstallerViewModelTests.makePlanWithConfigSchema() + let raw = "Log as {{TEMPLATE_ID}} (slug {{TEMPLATE_SLUG}})" + let resolved = ProjectTemplateInstaller.substituteCronTokens(raw, plan: plan) + #expect(resolved.contains(plan.manifest.id)) + #expect(resolved.contains(plan.manifest.slug)) + #expect(resolved.contains("{{TEMPLATE_ID}}") == false) + #expect(resolved.contains("{{TEMPLATE_SLUG}}") == false) + } + + @Test func substituteCronTokensLeavesUnknownTokensUntouched() throws { + let plan = try TemplateInstallerViewModelTests.makePlanWithConfigSchema() + let raw = "{{PROJECT_DIR}} but keep {{UNSUPPORTED}} literal" + let resolved = ProjectTemplateInstaller.substituteCronTokens(raw, plan: plan) + #expect(resolved.contains(plan.projectDir)) + // Unsupported placeholders pass through verbatim — template + // authors will notice in testing that their token didn't get + // replaced and either use a supported one or request a new one. + #expect(resolved.contains("{{UNSUPPORTED}}")) + } + + @Test func substituteCronTokensRepeatsWithinString() throws { + let plan = try TemplateInstallerViewModelTests.makePlanWithConfigSchema() + let raw = "Read {{PROJECT_DIR}}/a and write {{PROJECT_DIR}}/b" + let resolved = ProjectTemplateInstaller.substituteCronTokens(raw, plan: plan) + // Both occurrences should be replaced — not just the first. + // A single-replace bug here would leave the second relative, + // causing the same CWD issue this whole feature was meant to + // fix. + let count = resolved.components(separatedBy: plan.projectDir).count - 1 + #expect(count == 2) + } + + // MARK: - Registry snapshot helpers + + /// Read the raw bytes of the current projects.json so we can restore + /// it byte-for-byte after the test. `nil` means the file didn't exist + /// — restore by deleting whatever got created. + // Delegates to TestRegistryLock so tests across this suite + the + // two other registry-touching suites share one lock. Every + // `snapshotRegistry()` call acquires; the paired + // `restoreRegistry(_:)` defer releases. Without this, parallel + // test runs race on `~/.hermes/scarf/projects.json` writes and + // the saveRegistry throw surfaces the collision as a test failure. + nonisolated private static func snapshotRegistry() -> Data? { + TestRegistryLock.acquireAndSnapshot() + } + + nonisolated private static func restoreRegistry(_ snapshot: Data?) { + TestRegistryLock.restore(snapshot) + } +} + +/// End-to-end install + uninstall test: install a minimal bundle, uninstall +/// it, verify every tracked file is gone, the registry is restored to its +/// pre-install state, and user-added files (if any) are preserved. Scoped +/// to bundles with no skills/cron/memory so no global state is touched. +@Suite(.serialized) struct ProjectTemplateUninstallerTests { + + @Test func roundTripsInstallThenUninstall() throws { + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + let parentDir = scratch + "/parent" + try FileManager.default.createDirectory(atPath: parentDir, withIntermediateDirectories: true) + + let bundle = try ProjectTemplateServiceTests.makeBundle(dir: scratch, files: [ + "README.md": "# Minimal", + "AGENTS.md": "# Agent notes", + "dashboard.json": ProjectTemplateServiceTests.sampleDashboardJSON + ]) + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: bundle) + defer { service.cleanupTempDir(inspection.unpackedDir) } + let plan = try service.buildPlan(inspection: inspection, parentDir: parentDir) + + let registryBefore = Self.snapshotRegistry() + defer { Self.restoreRegistry(registryBefore) } + + let installer = ProjectTemplateInstaller(context: .local) + let entry = try installer.install(plan: plan) + #expect(FileManager.default.fileExists(atPath: plan.projectDir)) + + let uninstaller = ProjectTemplateUninstaller(context: .local) + #expect(uninstaller.isTemplateInstalled(project: entry)) + let uninstallPlan = try uninstaller.loadUninstallPlan(for: entry) + #expect(uninstallPlan.projectFilesToRemove.count == 4) // README, AGENTS, dashboard.json, lock + #expect(uninstallPlan.extraProjectEntries.isEmpty) + #expect(uninstallPlan.projectDirBecomesEmpty) + #expect(uninstallPlan.skillsNamespaceDir == nil) + #expect(uninstallPlan.cronJobsToRemove.isEmpty) + #expect(uninstallPlan.memoryBlockPresent == false) + + try uninstaller.uninstall(plan: uninstallPlan) + + #expect(FileManager.default.fileExists(atPath: plan.projectDir) == false) + // Registry entry gone — length matches pre-install snapshot. + let service2 = ProjectDashboardService(context: .local) + let registryAfter = service2.loadRegistry() + #expect(registryAfter.projects.contains(where: { $0.path == entry.path }) == false) + } + + @Test func preservesUserAddedFiles() throws { + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + let parentDir = scratch + "/parent" + try FileManager.default.createDirectory(atPath: parentDir, withIntermediateDirectories: true) + + let bundle = try ProjectTemplateServiceTests.makeBundle(dir: scratch, files: [ + "README.md": "# Minimal", + "AGENTS.md": "# Agent notes", + "dashboard.json": ProjectTemplateServiceTests.sampleDashboardJSON + ]) + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: bundle) + defer { service.cleanupTempDir(inspection.unpackedDir) } + let plan = try service.buildPlan(inspection: inspection, parentDir: parentDir) + + let registryBefore = Self.snapshotRegistry() + defer { Self.restoreRegistry(registryBefore) } + + let installer = ProjectTemplateInstaller(context: .local) + let entry = try installer.install(plan: plan) + + // Simulate the user / agent creating files post-install. + let userFile = plan.projectDir + "/sites.txt" + try "https://example.com\n".data(using: .utf8)! + .write(to: URL(fileURLWithPath: userFile)) + + let uninstaller = ProjectTemplateUninstaller(context: .local) + let uninstallPlan = try uninstaller.loadUninstallPlan(for: entry) + #expect(uninstallPlan.extraProjectEntries.contains(userFile)) + #expect(uninstallPlan.projectDirBecomesEmpty == false) + + try uninstaller.uninstall(plan: uninstallPlan) + + // Project dir should still exist because sites.txt is there. + #expect(FileManager.default.fileExists(atPath: plan.projectDir)) + #expect(FileManager.default.fileExists(atPath: userFile)) + // Lock-tracked files are gone. + #expect(FileManager.default.fileExists(atPath: plan.projectDir + "/AGENTS.md") == false) + #expect(FileManager.default.fileExists(atPath: plan.projectDir + "/README.md") == false) + #expect(FileManager.default.fileExists(atPath: plan.projectDir + "/.scarf/template.lock.json") == false) + } + + @Test func loadUninstallPlanRejectsProjectWithoutLock() throws { + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + try FileManager.default.createDirectory(atPath: scratch + "/bare", withIntermediateDirectories: true) + let entry = ProjectEntry(name: "Bare", path: scratch + "/bare") + + let uninstaller = ProjectTemplateUninstaller(context: .local) + #expect(uninstaller.isTemplateInstalled(project: entry) == false) + #expect(throws: ProjectTemplateError.self) { + try uninstaller.loadUninstallPlan(for: entry) + } + } + + // MARK: - Registry snapshot helpers (dup'd intentionally from + // ProjectTemplateInstallerTests — small helper, not worth a shared + // fixture file for one more suite). + + // Delegates to TestRegistryLock so tests across this suite + the + // two other registry-touching suites share one lock. Every + // `snapshotRegistry()` call acquires; the paired + // `restoreRegistry(_:)` defer releases. Without this, parallel + // test runs race on `~/.hermes/scarf/projects.json` writes and + // the saveRegistry throw surfaces the collision as a test failure. + nonisolated private static func snapshotRegistry() -> Data? { + TestRegistryLock.acquireAndSnapshot() + } + + nonisolated private static func restoreRegistry(_ snapshot: Data?) { + TestRegistryLock.restore(snapshot) + } +} + +/// End-to-end tests for manifest schemaVersion 2 (template configuration). +/// Exercises the full cycle: inspect → buildPlan → install → uninstall +/// against a synthesized schemaful bundle. Uses an isolated Keychain +/// service suffix so no leftover login-Keychain items remain after the +/// test — every secret we write is deleted on teardown. +@Suite(.serialized) struct ProjectTemplateConfigInstallTests { + + /// Minimal schemaful manifest with one non-secret field + one + /// secret field. Written into the synthesized `.scarftemplate` + /// bundle for the round-trip tests. + static func makeSchemafulManifest() -> ProjectTemplateManifest { + ProjectTemplateServiceTests.sampleManifest( + id: "tester/configured", + configSchema: TemplateConfigSchema( + fields: [ + .init(key: "site_url", type: .string, label: "Site URL", + description: "where to ping", required: true, placeholder: nil, + defaultValue: nil, options: nil, minLength: nil, + maxLength: nil, pattern: nil, minNumber: nil, + maxNumber: nil, step: nil, itemType: nil, + minItems: nil, maxItems: nil), + .init(key: "api_token", type: .secret, label: "API Token", + description: nil, required: true, placeholder: nil, + defaultValue: nil, options: nil, minLength: nil, + maxLength: nil, pattern: nil, minNumber: nil, + maxNumber: nil, step: nil, itemType: nil, + minItems: nil, maxItems: nil), + ], + modelRecommendation: nil + ) + ) + } + + @Test func inspectAcceptsSchemaV2Bundle() throws { + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + + let manifest = Self.makeSchemafulManifest() + let manifestData = try JSONEncoder().encode(manifest) + let manifestString = String(data: manifestData, encoding: .utf8)! + + let bundle = try ProjectTemplateServiceTests.makeBundle(dir: scratch, files: [ + "template.json": manifestString, + "README.md": "# r", + "AGENTS.md": "# a", + "dashboard.json": ProjectTemplateServiceTests.sampleDashboardJSON + ], includeManifest: false) + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: bundle) + defer { service.cleanupTempDir(inspection.unpackedDir) } + + #expect(inspection.manifest.schemaVersion == 2) + #expect(inspection.manifest.config?.fields.count == 2) + } + + @Test func buildPlanSurfacesSchemaAndQueuesConfigFiles() throws { + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + + let manifest = Self.makeSchemafulManifest() + let manifestJSON = String(data: try JSONEncoder().encode(manifest), encoding: .utf8)! + let bundle = try ProjectTemplateServiceTests.makeBundle(dir: scratch, files: [ + "template.json": manifestJSON, + "README.md": "# r", "AGENTS.md": "# a", + "dashboard.json": ProjectTemplateServiceTests.sampleDashboardJSON + ], includeManifest: false) + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: bundle) + defer { service.cleanupTempDir(inspection.unpackedDir) } + let plan = try service.buildPlan(inspection: inspection, parentDir: scratch) + + // Schema carried through the plan. + #expect(plan.configSchema?.fields.count == 2) + #expect(plan.manifestCachePath?.hasSuffix("/.scarf/manifest.json") == true) + // config.json + manifest.json entries in projectFiles. + let destinations = plan.projectFiles.map(\.destinationPath) + #expect(destinations.contains { $0.hasSuffix("/.scarf/config.json") }) + #expect(destinations.contains { $0.hasSuffix("/.scarf/manifest.json") }) + } + + @Test func verifyClaimsRejectsConfigCountMismatch() throws { + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + + // Hand-build a manifest whose contents.config claim (2) doesn't + // match its schema.fields.count (1) — validator should reject. + let schema = TemplateConfigSchema( + fields: [ + .init(key: "only", type: .string, label: "Only", + description: nil, required: false, placeholder: nil, + defaultValue: nil, options: nil, minLength: nil, + maxLength: nil, pattern: nil, minNumber: nil, + maxNumber: nil, step: nil, itemType: nil, + minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + let bogus = ProjectTemplateServiceTests.sampleManifest( + id: "tester/mismatch", + configFieldCount: 2, // claim lies + configSchema: schema // reality is 1 + ) + let manifestJSON = String(data: try JSONEncoder().encode(bogus), encoding: .utf8)! + let bundle = try ProjectTemplateServiceTests.makeBundle(dir: scratch, files: [ + "template.json": manifestJSON, + "README.md": "# r", "AGENTS.md": "# a", + "dashboard.json": ProjectTemplateServiceTests.sampleDashboardJSON + ], includeManifest: false) + + let service = ProjectTemplateService(context: .local) + #expect(throws: ProjectTemplateError.self) { + try service.inspect(zipPath: bundle) + } + } + + @Test func installWritesConfigJsonAndManifestCache() throws { + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + let parentDir = scratch + "/parent" + try FileManager.default.createDirectory(atPath: parentDir, withIntermediateDirectories: true) + + let manifest = Self.makeSchemafulManifest() + let manifestJSON = String(data: try JSONEncoder().encode(manifest), encoding: .utf8)! + let bundle = try ProjectTemplateServiceTests.makeBundle(dir: scratch, files: [ + "template.json": manifestJSON, + "README.md": "# r", "AGENTS.md": "# a", + "dashboard.json": ProjectTemplateServiceTests.sampleDashboardJSON + ], includeManifest: false) + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: bundle) + defer { service.cleanupTempDir(inspection.unpackedDir) } + var plan = try service.buildPlan(inspection: inspection, parentDir: parentDir) + + // Isolated Keychain service suffix so the test doesn't touch + // the real login Keychain. + let suffix = "tests-" + UUID().uuidString + let keychain = ProjectConfigKeychain(testServiceSuffix: suffix) + let configService = ProjectConfigService(keychain: keychain) + + // Store secret via the service (VM would do this before install). + let project = ProjectEntry(name: manifest.name, path: plan.projectDir) + let secretRef = try configService.storeSecret( + templateSlug: manifest.slug, + fieldKey: "api_token", + project: project, + secret: Data("sk-top-secret".utf8) + ) + plan.configValues = [ + "site_url": .string("https://example.com"), + "api_token": secretRef + ] + + let registryBefore = Self.snapshotRegistry() + defer { Self.restoreRegistry(registryBefore) } + + let installer = ProjectTemplateInstaller(context: .local) + _ = try installer.install(plan: plan) + + // config.json landed with non-secret values + keychain ref. + let configPath = plan.projectDir + "/.scarf/config.json" + #expect(FileManager.default.fileExists(atPath: configPath)) + let configData = try Data(contentsOf: URL(fileURLWithPath: configPath)) + let configFile = try JSONDecoder().decode(ProjectConfigFile.self, from: configData) + #expect(configFile.values["site_url"] == .string("https://example.com")) + if case .keychainRef(let uri) = configFile.values["api_token"] { + #expect(uri.hasPrefix("keychain://")) + } else { + Issue.record("api_token should have been stored as keychainRef") + } + + // manifest.json cache landed for the post-install editor. + let cachePath = plan.projectDir + "/.scarf/manifest.json" + #expect(FileManager.default.fileExists(atPath: cachePath)) + let cachedManifest = try JSONDecoder().decode( + ProjectTemplateManifest.self, + from: Data(contentsOf: URL(fileURLWithPath: cachePath)) + ) + #expect(cachedManifest.config?.fields.count == 2) + + // Lock file records the keychain item so uninstall can clean up. + let lockPath = plan.projectDir + "/.scarf/template.lock.json" + let lockData = try Data(contentsOf: URL(fileURLWithPath: lockPath)) + let lock = try JSONDecoder().decode(TemplateLock.self, from: lockData) + #expect(lock.configKeychainItems?.count == 1) + #expect(lock.configFields == ["site_url", "api_token"]) + + // Clean up the real Keychain entry we created outside the + // test-suffixed namespace (storeSecret uses real service name + // because the test's config-service wasn't isolated for this + // call's secret; we manually delete via our test keychain). + if let ref = TemplateKeychainRef.parse( + (configFile.values["api_token"].flatMap { v -> String? in + if case .keychainRef(let u) = v { return u } else { return nil } + }) ?? "" + ) { + try? ProjectConfigKeychain().delete(ref: ref) + } + } + + @Test func uninstallDeletesKeychainItemsViaLock() throws { + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + let parentDir = scratch + "/parent" + try FileManager.default.createDirectory(atPath: parentDir, withIntermediateDirectories: true) + + let manifest = Self.makeSchemafulManifest() + let manifestJSON = String(data: try JSONEncoder().encode(manifest), encoding: .utf8)! + let bundle = try ProjectTemplateServiceTests.makeBundle(dir: scratch, files: [ + "template.json": manifestJSON, + "README.md": "# r", "AGENTS.md": "# a", + "dashboard.json": ProjectTemplateServiceTests.sampleDashboardJSON + ], includeManifest: false) + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: bundle) + defer { service.cleanupTempDir(inspection.unpackedDir) } + var plan = try service.buildPlan(inspection: inspection, parentDir: parentDir) + + // Real Keychain — we store, install, then uninstall and verify + // the item is gone. Uses the real service name (no test suffix) + // because the installer + uninstaller go through their own + // ProjectConfigKeychain instances without a suffix. + let project = ProjectEntry(name: manifest.name, path: plan.projectDir) + let configService = ProjectConfigService() + let secretRef = try configService.storeSecret( + templateSlug: manifest.slug, + fieldKey: "api_token", + project: project, + secret: Data("delete-me".utf8) + ) + plan.configValues = [ + "site_url": .string("https://example.com"), + "api_token": secretRef + ] + + let registryBefore = Self.snapshotRegistry() + defer { Self.restoreRegistry(registryBefore) } + + let installer = ProjectTemplateInstaller(context: .local) + let entry = try installer.install(plan: plan) + + // Verify the secret is there before uninstall. + guard case .keychainRef(let uri) = secretRef, + let ref = TemplateKeychainRef.parse(uri) else { + Issue.record("expected secret to be a keychainRef") + return + } + #expect((try ProjectConfigKeychain().get(ref: ref)) == Data("delete-me".utf8)) + + // Uninstall → secret should be gone. + let uninstaller = ProjectTemplateUninstaller(context: .local) + let uninstallPlan = try uninstaller.loadUninstallPlan(for: entry) + try uninstaller.uninstall(plan: uninstallPlan) + + #expect((try ProjectConfigKeychain().get(ref: ref)) == nil) + } + + // MARK: - Registry snapshot helpers (dup'd from ProjectTemplateInstallerTests) + + // Delegates to TestRegistryLock so tests across this suite + the + // two other registry-touching suites share one lock. Every + // `snapshotRegistry()` call acquires; the paired + // `restoreRegistry(_:)` defer releases. Without this, parallel + // test runs race on `~/.hermes/scarf/projects.json` writes and + // the saveRegistry throw surfaces the collision as a test failure. + nonisolated private static func snapshotRegistry() -> Data? { + TestRegistryLock.acquireAndSnapshot() + } + + nonisolated private static func restoreRegistry(_ snapshot: Data?) { + TestRegistryLock.restore(snapshot) + } +} + +/// State-machine tests for `TemplateInstallerViewModel`. The install +/// flow's configure step is driven entirely through the VM — the view +/// transitions `.awaitingParentDirectory → .awaitingConfig → .planned` +/// based on `submitConfig(values:)` / `cancelConfig()` calls. If those +/// transitions break, the user lands on the wrong sheet stage (or no +/// sheet at all, as in the v1.1.0 regression where the config sheet's +/// internal `dismiss()` tore down the outer install sheet before +/// submitConfig had a chance to fire). +@Suite(.serialized) @MainActor struct TemplateInstallerViewModelTests { + + @Test func submitConfigStashesValuesAndTransitionsToPlanned() throws { + let vm = TemplateInstallerViewModel(context: .local) + // Seed the VM with an awaiting-config plan (schema-ful). + let plan = try Self.makePlanWithConfigSchema() + vm.plan = plan + vm.stage = .awaitingConfig + + let values: [String: TemplateConfigValue] = [ + "site_url": .string("https://example.com") + ] + vm.submitConfig(values: values) + + // Stage must advance past the configure step, values must land + // on the plan where install() will pick them up. + if case .planned = vm.stage { + // ok + } else { + Issue.record("expected .planned, got \(vm.stage)") + } + #expect(vm.plan?.configValues["site_url"] == .string("https://example.com")) + } + + @Test func cancelConfigReturnsToAwaitingParentDirectory() throws { + let vm = TemplateInstallerViewModel(context: .local) + vm.plan = try Self.makePlanWithConfigSchema() + vm.stage = .awaitingConfig + + vm.cancelConfig() + + if case .awaitingParentDirectory = vm.stage { + // ok — user can re-pick the parent dir or fully cancel + } else { + Issue.record("expected .awaitingParentDirectory, got \(vm.stage)") + } + // Plan is preserved so re-entering the configure step doesn't + // re-run buildPlan. + #expect(vm.plan != nil) + } + + @Test func submitConfigNoOpWhenPlanIsNil() { + let vm = TemplateInstallerViewModel(context: .local) + vm.plan = nil + vm.stage = .awaitingConfig + vm.submitConfig(values: ["k": .string("v")]) + // With no plan, the call should be silent — no crash, stage + // stays where it was. (Defensive guard in submitConfig.) + if case .awaitingConfig = vm.stage { + // ok + } else { + Issue.record("expected stage to remain .awaitingConfig when plan is nil; got \(vm.stage)") + } + } + + // MARK: - Fixture + + /// Build a `TemplateInstallPlan` carrying a single-field config + /// schema. Exists as a local helper rather than a shared one + /// because no other suite needs it. + nonisolated static func makePlanWithConfigSchema() throws -> TemplateInstallPlan { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "site_url", type: .string, label: "Site URL", + description: nil, required: true, placeholder: nil, + defaultValue: nil, options: nil, minLength: nil, + maxLength: nil, pattern: nil, minNumber: nil, + maxNumber: nil, step: nil, itemType: nil, + minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + let manifest = ProjectTemplateServiceTests.sampleManifest( + id: "tester/vm-transitions", + configSchema: schema + ) + let tmp = try ProjectTemplateServiceTests.makeTempDir() + // Not a real bundle dir — we never unzip or install from this + // plan, we only test state transitions that don't touch disk. + return TemplateInstallPlan( + manifest: manifest, + unpackedDir: tmp, + projectDir: tmp + "/project", + projectFiles: [], + skillsNamespaceDir: nil, + skillsFiles: [], + cronJobs: [], + memoryAppendix: nil, + memoryPath: ServerContext.local.paths.memoryMD, + projectRegistryName: "VM Transitions", + configSchema: schema, + configValues: [:], + manifestCachePath: tmp + "/project/.scarf/manifest.json" + ) + } +} + +/// Validates every `.scarftemplate` shipped under `templates///` +/// in the repo. A template whose manifest, `contents` claim, or file set is +/// out of sync will fail here — so shipped templates can't silently rot. +@Suite struct ProjectTemplateExampleTemplateTests { + + @Test func siteStatusCheckerParsesAndPlans() throws { + let bundle = try Self.locateExample(author: "awizemann", name: "site-status-checker") + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: bundle) + defer { service.cleanupTempDir(inspection.unpackedDir) } + + #expect(inspection.manifest.id == "awizemann/site-status-checker") + #expect(inspection.manifest.schemaVersion == 2) // config-enabled + #expect(inspection.manifest.contents.dashboard) + #expect(inspection.manifest.contents.agentsMd) + #expect(inspection.manifest.contents.cron == 1) + #expect(inspection.manifest.contents.config == 2) + #expect(inspection.cronJobs.count == 1) + #expect(inspection.cronJobs.first?.name == "Check site status") + #expect(inspection.cronJobs.first?.schedule == "0 9 * * *") + + // Schema assertions — the two fields we declared should survive + // unzip + parse + validate with their constraints intact. + let schema = try #require(inspection.manifest.config) + #expect(schema.fields.count == 2) + let sitesField = try #require(schema.field(for: "sites")) + #expect(sitesField.type == .list) + #expect(sitesField.itemType == "string") + #expect(sitesField.required == true) + #expect(sitesField.minItems == 1) + #expect(sitesField.maxItems == 25) + let timeoutField = try #require(schema.field(for: "timeout_seconds")) + #expect(timeoutField.type == .number) + #expect(timeoutField.minNumber == 1) + #expect(timeoutField.maxNumber == 60) + #expect(schema.modelRecommendation?.preferred == "claude-haiku-4") + + let scratch = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: scratch) } + let plan = try service.buildPlan(inspection: inspection, parentDir: scratch) + #expect(plan.projectDir.hasSuffix("awizemann-site-status-checker")) + #expect(plan.skillsFiles.isEmpty) + #expect(plan.memoryAppendix == nil) + #expect(plan.cronJobs.count == 1) + #expect(plan.configSchema?.fields.count == 2) + #expect(plan.manifestCachePath?.hasSuffix("/.scarf/manifest.json") == true) + // Plan queues both config.json + manifest.json in projectFiles. + let destinations = plan.projectFiles.map(\.destinationPath) + #expect(destinations.contains { $0.hasSuffix("/.scarf/config.json") }) + #expect(destinations.contains { $0.hasSuffix("/.scarf/manifest.json") }) + // Cron job name gets prefixed with the template tag so users can + // find + remove it later. + #expect(plan.cronJobs.first?.name == "[tmpl:awizemann/site-status-checker] Check site status") + + // Verify the bundled dashboard.json decodes against the same + // `ProjectDashboard` struct the app uses at runtime — catches drift + // between template-author conventions and the actual renderer + // (e.g. a widget type that ProjectsView doesn't know, a + // non-number value for a stat, etc.). + let dashboardPath = inspection.unpackedDir + "/dashboard.json" + let dashboardData = try Data(contentsOf: URL(fileURLWithPath: dashboardPath)) + let dashboard = try JSONDecoder().decode(ProjectDashboard.self, from: dashboardData) + #expect(dashboard.title == "Site Status") + // Four sections: Current Status (stats), Watched Sites (list), + // Live Site Preview (webview — drives the Site tab), How to Use (text). + #expect(dashboard.sections.count == 4) + + // First section should have three stat widgets that the cron job + // updates by value. Assert titles + types so the AGENTS.md contract + // can't drift from the actual dashboard. + let statsSection = dashboard.sections[0] + #expect(statsSection.title == "Current Status") + let statTitles = statsSection.widgets.filter { $0.type == "stat" }.map(\.title) + #expect(statTitles.contains("Sites Up")) + #expect(statTitles.contains("Sites Down")) + #expect(statTitles.contains("Last Checked")) + + // Live Site Preview section must contain exactly one webview + // widget. The presence of any webview widget is what makes Scarf + // expose the Site tab next to Dashboard, so losing this section + // would silently drop a user-visible feature. The cron job + // rewrites this widget's `url` to the first configured site on + // every run — AGENTS.md documents the contract. + let previewSection = dashboard.sections[2] + #expect(previewSection.title == "Live Site Preview") + let webviews = previewSection.widgets.filter { $0.type == "webview" } + #expect(webviews.count == 1) + #expect(webviews.first?.title == "First Watched Site") + #expect((webviews.first?.url ?? "").isEmpty == false) + + // Cron prompt references .scarf/config.json (where values.sites + // + values.timeout_seconds live), the dashboard/log it writes, + // and the {{PROJECT_DIR}} placeholder the installer resolves + // at install time. If either stops being referenced, the cron + // wouldn't know which data to read or where to write results. + let cronPrompt = inspection.cronJobs.first?.prompt ?? "" + #expect(cronPrompt.contains("config.json")) + #expect(cronPrompt.contains("values.sites")) + #expect(cronPrompt.contains("dashboard.json")) + #expect(cronPrompt.contains("status-log.md")) + // {{PROJECT_DIR}} must remain UNRESOLVED in the bundle — the + // installer substitutes it at install time. If someone + // accidentally baked an absolute path into the template, that + // path would follow every install to every user's machine. + #expect(cronPrompt.contains("{{PROJECT_DIR}}")) + } + + /// Resolve the example bundle path robustly. Unit-test working dirs + /// differ between `xcodebuild test` (project root) and an Xcode IDE + /// run (build-output dir), so we walk up from this source file until + /// we find the repo root. Templates live at + /// `templates///.scarftemplate` per the catalog + /// layout (see `templates/README.md`). + nonisolated private static func locateExample(author: String, name: String) throws -> String { + var dir = URL(fileURLWithPath: #filePath).deletingLastPathComponent() + for _ in 0..<6 { + let candidate = dir.appendingPathComponent("templates/\(author)/\(name)/\(name).scarftemplate") + if FileManager.default.fileExists(atPath: candidate.path) { + return candidate.path + } + dir = dir.deletingLastPathComponent() + } + throw ProjectTemplateError.requiredFileMissing("templates/\(author)/\(name)/\(name).scarftemplate") + } +} + +/// Round-trips a real project structure through the exporter and back into +/// the service. Does NOT run the installer (which would write to +/// ~/.hermes) — it verifies the produced bundle is valid, and stops there. +@Suite struct ProjectTemplateExportTests { + + @Test func roundTripsMinimalProject() throws { + let fakeProject = NSTemporaryDirectory() + "scarf-project-" + UUID().uuidString + try FileManager.default.createDirectory(atPath: fakeProject + "/.scarf", withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(atPath: fakeProject) } + + try ProjectTemplateServiceTests.sampleDashboardJSON + .data(using: .utf8)! + .write(to: URL(fileURLWithPath: fakeProject + "/.scarf/dashboard.json")) + try "# Test project".data(using: .utf8)! + .write(to: URL(fileURLWithPath: fakeProject + "/README.md")) + try "# Agent notes".data(using: .utf8)! + .write(to: URL(fileURLWithPath: fakeProject + "/AGENTS.md")) + + let entry = ProjectEntry(name: "Round Trip", path: fakeProject) + let exporter = ProjectTemplateExporter(context: .local) + let outputDir = try ProjectTemplateServiceTests.makeTempDir() + defer { try? FileManager.default.removeItem(atPath: outputDir) } + let outputPath = outputDir + "/rt.scarftemplate" + + let inputs = ProjectTemplateExporter.ExportInputs( + project: entry, + templateId: "tester/round-trip", + templateName: "Round Trip", + templateVersion: "0.1.0", + description: "round-trip test", + authorName: "Tester", + authorUrl: nil, + category: nil, + tags: [], + includeSkillIds: [], + includeCronJobIds: [], + memoryAppendix: nil + ) + + try exporter.export(inputs: inputs, outputZipPath: outputPath) + #expect(FileManager.default.fileExists(atPath: outputPath)) + + let service = ProjectTemplateService(context: .local) + let inspection = try service.inspect(zipPath: outputPath) + defer { service.cleanupTempDir(inspection.unpackedDir) } + #expect(inspection.manifest.id == "tester/round-trip") + #expect(inspection.files.contains("dashboard.json")) + #expect(inspection.files.contains("README.md")) + #expect(inspection.files.contains("AGENTS.md")) + } +} diff --git a/scarf/scarfTests/TemplateConfigTests.swift b/scarf/scarfTests/TemplateConfigTests.swift new file mode 100644 index 0000000..57d529f --- /dev/null +++ b/scarf/scarfTests/TemplateConfigTests.swift @@ -0,0 +1,402 @@ +import Testing +import Foundation +@testable import scarf + +// MARK: - Schema validation + +@Suite struct TemplateConfigSchemaValidationTests { + + @Test func acceptsMinimalValidSchema() throws { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "name", type: .string, label: "Name", + description: nil, required: true, placeholder: nil, + defaultValue: nil, options: nil, minLength: nil, + maxLength: nil, pattern: nil, minNumber: nil, + maxNumber: nil, step: nil, itemType: nil, + minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + try ProjectConfigService.validateSchema(schema) + } + + @Test func rejectsDuplicateKeys() { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "same", type: .string, label: "A", description: nil, + required: false, placeholder: nil, defaultValue: nil, + options: nil, minLength: nil, maxLength: nil, + pattern: nil, minNumber: nil, maxNumber: nil, + step: nil, itemType: nil, minItems: nil, maxItems: nil), + .init(key: "same", type: .bool, label: "B", description: nil, + required: false, placeholder: nil, defaultValue: nil, + options: nil, minLength: nil, maxLength: nil, + pattern: nil, minNumber: nil, maxNumber: nil, + step: nil, itemType: nil, minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + #expect(throws: TemplateConfigSchemaError.self) { + try ProjectConfigService.validateSchema(schema) + } + } + + @Test func rejectsSecretWithDefault() { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "api_key", type: .secret, label: "API Key", + description: nil, required: true, placeholder: nil, + defaultValue: .string("leaked-by-accident"), + options: nil, minLength: nil, maxLength: nil, + pattern: nil, minNumber: nil, maxNumber: nil, + step: nil, itemType: nil, minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + #expect(throws: TemplateConfigSchemaError.self) { + try ProjectConfigService.validateSchema(schema) + } + } + + @Test func rejectsEnumWithoutOptions() { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "choice", type: .enum, label: "Choice", + description: nil, required: true, placeholder: nil, + defaultValue: nil, options: [], + minLength: nil, maxLength: nil, pattern: nil, + minNumber: nil, maxNumber: nil, step: nil, + itemType: nil, minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + #expect(throws: TemplateConfigSchemaError.self) { + try ProjectConfigService.validateSchema(schema) + } + } + + @Test func rejectsEnumWithDuplicateValues() { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "choice", type: .enum, label: "Choice", + description: nil, required: true, placeholder: nil, + defaultValue: nil, + options: [.init(value: "a", label: "A"), + .init(value: "a", label: "Another A")], + minLength: nil, maxLength: nil, pattern: nil, + minNumber: nil, maxNumber: nil, step: nil, + itemType: nil, minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + #expect(throws: TemplateConfigSchemaError.self) { + try ProjectConfigService.validateSchema(schema) + } + } + + @Test func rejectsUnsupportedListItemType() { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "items", type: .list, label: "Items", + description: nil, required: true, placeholder: nil, + defaultValue: nil, options: nil, + minLength: nil, maxLength: nil, pattern: nil, + minNumber: nil, maxNumber: nil, step: nil, + itemType: "number", minItems: 1, maxItems: 10) + ], + modelRecommendation: nil + ) + #expect(throws: TemplateConfigSchemaError.self) { + try ProjectConfigService.validateSchema(schema) + } + } + + @Test func rejectsEmptyModelPreferred() { + let schema = TemplateConfigSchema( + fields: [], + modelRecommendation: .init(preferred: " ", rationale: nil, alternatives: nil) + ) + #expect(throws: TemplateConfigSchemaError.self) { + try ProjectConfigService.validateSchema(schema) + } + } +} + +// MARK: - Value validation + +@Suite struct TemplateConfigValueValidationTests { + + @Test func requiredFieldRejectsEmptyString() { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "name", type: .string, label: "Name", + description: nil, required: true, placeholder: nil, + defaultValue: nil, options: nil, minLength: nil, + maxLength: nil, pattern: nil, minNumber: nil, + maxNumber: nil, step: nil, itemType: nil, + minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + let errors = ProjectConfigService.validateValues( + ["name": .string("")], against: schema + ) + #expect(errors.count == 1) + #expect(errors.first?.fieldKey == "name") + } + + @Test func patternRejectsBadInput() { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "email", type: .string, label: "Email", + description: nil, required: true, placeholder: nil, + defaultValue: nil, options: nil, minLength: nil, + maxLength: nil, pattern: "^[^@]+@[^@]+$", + minNumber: nil, maxNumber: nil, step: nil, + itemType: nil, minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + let errors = ProjectConfigService.validateValues( + ["email": .string("not-an-email")], against: schema + ) + #expect(errors.count == 1) + } + + @Test func numberRangeEnforced() { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "port", type: .number, label: "Port", + description: nil, required: true, placeholder: nil, + defaultValue: nil, options: nil, minLength: nil, + maxLength: nil, pattern: nil, minNumber: 1024, + maxNumber: 65535, step: nil, itemType: nil, + minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + let errors = ProjectConfigService.validateValues( + ["port": .number(80)], against: schema + ) + #expect(errors.count == 1) + } + + @Test func enumRejectsUnknownValue() { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "mode", type: .enum, label: "Mode", + description: nil, required: true, placeholder: nil, + defaultValue: nil, + options: [.init(value: "fast", label: "Fast"), + .init(value: "slow", label: "Slow")], + minLength: nil, maxLength: nil, pattern: nil, + minNumber: nil, maxNumber: nil, step: nil, + itemType: nil, minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + let errors = ProjectConfigService.validateValues( + ["mode": .string("medium")], against: schema + ) + #expect(errors.count == 1) + } + + @Test func listItemBoundsEnforced() { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "urls", type: .list, label: "URLs", + description: nil, required: true, placeholder: nil, + defaultValue: nil, options: nil, minLength: nil, + maxLength: nil, pattern: nil, minNumber: nil, + maxNumber: nil, step: nil, itemType: "string", + minItems: 1, maxItems: 3) + ], + modelRecommendation: nil + ) + let tooFew = ProjectConfigService.validateValues( + ["urls": .list([])], against: schema + ) + let tooMany = ProjectConfigService.validateValues( + ["urls": .list(["a", "b", "c", "d"])], against: schema + ) + let justRight = ProjectConfigService.validateValues( + ["urls": .list(["a", "b"])], against: schema + ) + #expect(tooFew.count == 1) + #expect(tooMany.count == 1) + #expect(justRight.isEmpty) + } + + @Test func secretFieldAcceptsKeychainRef() { + let schema = TemplateConfigSchema( + fields: [ + .init(key: "tok", type: .secret, label: "Token", + description: nil, required: true, placeholder: nil, + defaultValue: nil, options: nil, minLength: nil, + maxLength: nil, pattern: nil, minNumber: nil, + maxNumber: nil, step: nil, itemType: nil, + minItems: nil, maxItems: nil) + ], + modelRecommendation: nil + ) + let errors = ProjectConfigService.validateValues( + ["tok": .keychainRef("keychain://test/tok:abc")], + against: schema + ) + #expect(errors.isEmpty) + } +} + +// MARK: - Keychain ref helpers + +@Suite struct TemplateKeychainRefTests { + + @Test func uriRoundTrips() { + let ref = TemplateKeychainRef( + service: "com.scarf.template.alice-foo", + account: "api_key:deadbeef" + ) + #expect(ref.uri == "keychain://com.scarf.template.alice-foo/api_key:deadbeef") + let parsed = TemplateKeychainRef.parse(ref.uri) + #expect(parsed == ref) + } + + @Test func parseRejectsMalformedUris() { + #expect(TemplateKeychainRef.parse("") == nil) + #expect(TemplateKeychainRef.parse("keychain://") == nil) + #expect(TemplateKeychainRef.parse("keychain:///account-only") == nil) + #expect(TemplateKeychainRef.parse("keychain://service-only") == nil) + #expect(TemplateKeychainRef.parse("https://example.com/foo") == nil) + } + + @Test func hashDiffersByProjectPath() { + let a = TemplateKeychainRef.make(templateSlug: "s", fieldKey: "k", projectPath: "/Users/a/p1") + let b = TemplateKeychainRef.make(templateSlug: "s", fieldKey: "k", projectPath: "/Users/a/p2") + #expect(a.service == b.service) // same template + #expect(a.account != b.account) // different project → different hash suffix + } + + @Test func hashStableForSamePath() { + let a = TemplateKeychainRef.make(templateSlug: "s", fieldKey: "k", projectPath: "/Users/a/p1") + let b = TemplateKeychainRef.make(templateSlug: "s", fieldKey: "k", projectPath: "/Users/a/p1") + #expect(a == b) + } +} + +// MARK: - On-disk config round-trip + +@Suite struct ProjectConfigFileTests { + + @Test func roundTripsNonSecretValues() throws { + let file = ProjectConfigFile( + schemaVersion: 2, + templateId: "alice/example", + values: [ + "name": .string("Alice"), + "enabled": .bool(true), + "count": .number(42), + "tags": .list(["a", "b", "c"]), + ], + updatedAt: "2026-04-25T00:00:00Z" + ) + let encoded = try JSONEncoder().encode(file) + let decoded = try JSONDecoder().decode(ProjectConfigFile.self, from: encoded) + #expect(decoded.schemaVersion == 2) + #expect(decoded.templateId == "alice/example") + #expect(decoded.values["name"] == .string("Alice")) + #expect(decoded.values["enabled"] == .bool(true)) + #expect(decoded.values["count"] == .number(42)) + #expect(decoded.values["tags"] == .list(["a", "b", "c"])) + } + + @Test func preservesKeychainRefsOnRoundTrip() throws { + let file = ProjectConfigFile( + schemaVersion: 2, + templateId: "alice/example", + values: ["tok": .keychainRef("keychain://com.scarf.template.alice-example/tok:deadbeef")], + updatedAt: "2026-04-25T00:00:00Z" + ) + let encoded = try JSONEncoder().encode(file) + let decoded = try JSONDecoder().decode(ProjectConfigFile.self, from: encoded) + // Keychain refs must NOT demote to plain strings on round-trip + // — otherwise a post-install editor would lose the secret + // binding when saving unchanged values. + #expect(decoded.values["tok"] == .keychainRef("keychain://com.scarf.template.alice-example/tok:deadbeef")) + } +} + +// MARK: - ProjectConfigService + Keychain integration + +/// Exercises the full secret-storage path through a real macOS Keychain +/// with a test-only service suffix so nothing leaks into the user's +/// login Keychain. Every test sets + reads + deletes within a unique +/// service name so parallel runs don't collide. +@Suite struct ProjectConfigSecretsTests { + + @Test func storeAndResolveSecret() throws { + let suffix = "tests-" + UUID().uuidString + let keychain = ProjectConfigKeychain(testServiceSuffix: suffix) + let service = ProjectConfigService(keychain: keychain) + let project = ProjectEntry(name: "Scratch", path: NSTemporaryDirectory() + UUID().uuidString) + + let stored = try service.storeSecret( + templateSlug: "alice-example", + fieldKey: "api_key", + project: project, + secret: Data("hunter2".utf8) + ) + + // What goes into config.json is a keychainRef, not the bytes. + guard case .keychainRef(let uri) = stored else { + Issue.record("expected keychainRef, got \(stored)") + return + } + #expect(uri.hasPrefix("keychain://")) + + // Resolve brings the bytes back. + let resolved = try service.resolveSecret(ref: stored) + #expect(resolved == Data("hunter2".utf8)) + + // Clean up so we don't leave a test item in the Keychain. + if let ref = TemplateKeychainRef.parse(uri) { + try keychain.delete(ref: ref) + #expect((try keychain.get(ref: ref)) == nil) + } + } + + @Test func setOverwritesExistingSecret() throws { + let suffix = "tests-" + UUID().uuidString + let keychain = ProjectConfigKeychain(testServiceSuffix: suffix) + let ref = TemplateKeychainRef(service: "com.scarf.template.overwrite", account: "k:1") + try keychain.set(ref: ref, secret: Data("first".utf8)) + try keychain.set(ref: ref, secret: Data("second".utf8)) + #expect((try keychain.get(ref: ref)) == Data("second".utf8)) + try keychain.delete(ref: ref) + } + + @Test func deleteOfMissingItemSucceeds() throws { + let suffix = "tests-" + UUID().uuidString + let keychain = ProjectConfigKeychain(testServiceSuffix: suffix) + let ref = TemplateKeychainRef(service: "com.scarf.template.absent", account: "never:set") + // Deleting a non-existent item is a no-op — must not throw. + try keychain.delete(ref: ref) + } + + @Test func deleteMultipleSecretsClearsAll() throws { + let suffix = "tests-" + UUID().uuidString + let keychain = ProjectConfigKeychain(testServiceSuffix: suffix) + let service = ProjectConfigService(keychain: keychain) + + let refs = (0..<3).map { i in + TemplateKeychainRef(service: "com.scarf.template.bulk", account: "k:\(i)") + } + for ref in refs { + try keychain.set(ref: ref, secret: Data("v".utf8)) + } + try service.deleteSecrets(refs: refs) + for ref in refs { + #expect((try keychain.get(ref: ref)) == nil) + } + } +} diff --git a/scripts/catalog.sh b/scripts/catalog.sh new file mode 100755 index 0000000..f9730dc --- /dev/null +++ b/scripts/catalog.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# +# Scarf templates catalog helper — runs the Python validator, renders the +# static site into .gh-pages-worktree/templates/, and (on `publish`) +# commits + pushes that subdir on the gh-pages branch. +# +# Usage: +# ./scripts/catalog.sh check # validate every template; no output +# ./scripts/catalog.sh build # validate + write templates/catalog.json + .gh-pages-worktree/templates/ +# ./scripts/catalog.sh preview [DIR] # render self-contained preview; DIR defaults to /tmp/scarf-catalog-preview +# ./scripts/catalog.sh publish # secret-scan + commit + push gh-pages (templates subdir only) +# ./scripts/catalog.sh serve [PORT] # serve .gh-pages-worktree/ on localhost:PORT (default 8000) +# ./scripts/catalog.sh --help # this help +# +# The secret-scan runs BEFORE publish and inspects the generated +# .gh-pages-worktree/templates/ tree — same hard-pattern regex as +# scripts/wiki.sh so template README/AGENTS content that accidentally +# leaks credentials gets blocked before it reaches the public site. +# +# Bootstrap (one-time): requires a .gh-pages-worktree/ clone of the +# gh-pages branch. The release script (scripts/release.sh) creates it on +# first use. If it's missing: +# git worktree add .gh-pages-worktree gh-pages +# +# Recovery: if .gh-pages-worktree/ is deleted, re-run the command above. + +set -euo pipefail + +# ---------- config ---------- +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +GHPAGES_DIR="$REPO_ROOT/.gh-pages-worktree" +CATALOG_SUBDIR="templates" +PY="${PYTHON:-python3}" +BUILDER="$REPO_ROOT/tools/build-catalog.py" + +# ---------- helpers (same shape as scripts/wiki.sh so a reader doesn't +# have to learn two conventions) ---------- +log() { printf '\033[1;34m==> %s\033[0m\n' "$*"; } +warn() { printf '\033[1;33m[WARN] %s\033[0m\n' "$*" >&2; } +die() { printf '\033[1;31m[ERR] %s\033[0m\n' "$*" >&2; exit 1; } + +need_builder() { + [[ -f "$BUILDER" ]] || die "missing $BUILDER" + command -v "$PY" >/dev/null 2>&1 || die "python3 not found (set \$PYTHON if needed)" +} + +need_ghpages() { + [[ -d "$GHPAGES_DIR/.git" ]] || die "no gh-pages worktree at $GHPAGES_DIR + Run: git worktree add .gh-pages-worktree gh-pages" +} + +# ---------- secret-scan (mirrors scripts/wiki.sh hard-pattern set) ---------- +hard_regex='(sk-[A-Za-z0-9_-]{20,}|ghp_[A-Za-z0-9]{30,}|ghs_[A-Za-z0-9]{30,}|ghu_[A-Za-z0-9]{30,}|gho_[A-Za-z0-9]{30,}|ghr_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|-----BEGIN [A-Z ]*PRIVATE KEY-----|BEGIN OPENSSH PRIVATE KEY)' + +scan_hard_ghpages() { + # Scan the generated output, NOT the repo source — the validator + # already scans bundle contents. This pass catches anything that leaked + # through template.json fields or README prose. + local hits + hits="$(grep -rInE --exclude-dir=.git "$hard_regex" "$GHPAGES_DIR/$CATALOG_SUBDIR" 2>/dev/null || true)" + if [[ -n "$hits" ]]; then + printf '%s\n' "$hits" >&2 + die "hard-pattern secret match in rendered site — refusing to publish." + fi +} + +# ---------- commands ---------- +cmd_check() { + need_builder + "$PY" "$BUILDER" --check --repo "$REPO_ROOT" +} + +cmd_build() { + need_builder + "$PY" "$BUILDER" --build --repo "$REPO_ROOT" +} + +cmd_preview() { + need_builder + local dir="${1:-/tmp/scarf-catalog-preview}" + rm -rf "$dir" + mkdir -p "$dir" + "$PY" "$BUILDER" --preview "$dir" --repo "$REPO_ROOT" + log "Preview rendered to $dir" + log "Serve with: (cd $dir && python3 -m http.server 8000) then open http://localhost:8000/" +} + +cmd_serve() { + need_ghpages + local port="${1:-8000}" + log "Serving $GHPAGES_DIR on http://localhost:$port/" + (cd "$GHPAGES_DIR" && "$PY" -m http.server "$port") +} + +cmd_publish() { + need_builder + need_ghpages + log "Validating" + "$PY" "$BUILDER" --check --repo "$REPO_ROOT" + log "Building" + "$PY" "$BUILDER" --build --repo "$REPO_ROOT" + + log "Secret-scanning rendered site" + scan_hard_ghpages + + log "Staging + committing gh-pages" + (cd "$GHPAGES_DIR" && git add "$CATALOG_SUBDIR") + if (cd "$GHPAGES_DIR" && git diff --cached --quiet); then + log "No changes to publish." + return 0 + fi + local msg + msg="catalog: rebuild at $(date -u +%Y-%m-%dT%H:%M:%SZ)" + (cd "$GHPAGES_DIR" && git commit -m "$msg") + log "Pushing gh-pages" + (cd "$GHPAGES_DIR" && git push origin gh-pages) + log "Published." +} + +cmd_help() { + sed -n '1,30p' "$0" | sed -n '/^# Usage/,/^#$/p' +} + +# ---------- dispatch ---------- +sub="${1:-help}" +shift || true +case "$sub" in + check) cmd_check "$@" ;; + build) cmd_build "$@" ;; + preview) cmd_preview "$@" ;; + serve) cmd_serve "$@" ;; + publish) cmd_publish "$@" ;; + help|--help|-h) cmd_help ;; + *) die "unknown command: $sub (try --help)" ;; +esac diff --git a/site/assets/icon.png b/site/assets/icon.png new file mode 100644 index 0000000..2eaf0bb Binary files /dev/null and b/site/assets/icon.png differ diff --git a/site/index.html.tmpl b/site/index.html.tmpl new file mode 100644 index 0000000..b2a1d6e --- /dev/null +++ b/site/index.html.tmpl @@ -0,0 +1,48 @@ + + + + + + Scarf Templates + + + + + + + +
+

Pre-packaged projects for Scarf

+

+ Browse {{COUNT}} community template{{COUNT_PLURAL}} — each ships with a + ready-to-install Scarf dashboard, a cross-agent AGENTS.md, optional + cron jobs and skills. Click a template to see what it does; one click installs + it into Scarf. +

+
+ +
+
+ {{CARDS}} +
+
+ + + + diff --git a/site/styles.css b/site/styles.css new file mode 100644 index 0000000..ad9a87a --- /dev/null +++ b/site/styles.css @@ -0,0 +1,441 @@ +/* Scarf Templates — catalog site. + * Vanilla CSS, no framework. Matches Scarf's green accent and keeps + * decoration minimal — the live dashboard preview is the point. */ + +:root { + --fg: #1a1a1a; + --fg-muted: #666; + --bg: #fafafa; + --bg-card: #ffffff; + --border: #e5e5e5; + --accent: #2aa876; /* scarf green */ + --accent-dark: #1f7f5a; + --red: #d9534f; + --blue: #3498db; + --orange: #f0ad4e; + --radius: 8px; + --shadow: 0 1px 2px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.04); + --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} + +@media (prefers-color-scheme: dark) { + :root { + --fg: #e5e5e5; + --fg-muted: #9a9a9a; + --bg: #141414; + --bg-card: #1e1e1e; + --border: #2a2a2a; + --accent: #3abf8a; + --accent-dark: #2aa876; + --shadow: 0 1px 2px rgba(0,0,0,0.3), 0 4px 12px rgba(0,0,0,0.3); + } +} + +* { box-sizing: border-box; } + +body { + margin: 0; + font-family: var(--sans); + color: var(--fg); + background: var(--bg); + line-height: 1.5; +} + +code, pre { + font-family: var(--mono); + font-size: 0.92em; +} + +pre { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 12px 16px; + overflow-x: auto; +} +code { + background: rgba(0,0,0,0.05); + padding: 2px 5px; + border-radius: 4px; +} +pre code { background: transparent; padding: 0; } + +a { color: var(--accent-dark); text-decoration: none; } +a:hover { text-decoration: underline; } + +h1, h2, h3 { line-height: 1.25; } + +/* ---------- header / footer ---------- */ + +.site-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 32px; + border-bottom: 1px solid var(--border); + background: var(--bg-card); +} +.brand { + display: flex; + align-items: center; + gap: 12px; + color: var(--fg); +} +.brand:hover { text-decoration: none; } +.brand-name { font-weight: 600; font-size: 18px; } +.site-nav a { + margin-left: 20px; + color: var(--fg-muted); + font-size: 14px; +} + +.site-footer { + padding: 32px; + text-align: center; + color: var(--fg-muted); + font-size: 14px; + border-top: 1px solid var(--border); + margin-top: 40px; +} + +/* ---------- landing ---------- */ + +.hero { + padding: 48px 32px 24px; + max-width: 720px; + margin: 0 auto; + text-align: center; +} +.hero h1 { font-size: 32px; margin: 0 0 12px; } +.hero p { color: var(--fg-muted); } + +.catalog { + max-width: 1100px; + margin: 0 auto; + padding: 16px 32px 48px; +} +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 16px; +} +.card { + display: block; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 20px; + box-shadow: var(--shadow); + color: inherit; + transition: transform 0.12s ease, box-shadow 0.12s ease; +} +.card:hover { + transform: translateY(-2px); + box-shadow: 0 2px 4px rgba(0,0,0,0.06), 0 8px 24px rgba(0,0,0,0.06); + text-decoration: none; +} +.card h3 { margin: 0 0 6px; font-size: 18px; } +.card .desc { color: var(--fg-muted); margin: 0 0 14px; font-size: 14px; } +.card .meta { + display: flex; + justify-content: space-between; + font-size: 12px; + color: var(--fg-muted); + margin-bottom: 8px; +} +.card .author { font-weight: 500; } +.card .version { font-family: var(--mono); } +.tags { display: flex; flex-wrap: wrap; gap: 4px; } +.tag { + display: inline-block; + padding: 2px 8px; + background: rgba(42,168,118,0.15); + color: var(--accent-dark); + border-radius: 10px; + font-size: 11px; +} + +/* ---------- template detail page ---------- */ + +.detail { + max-width: 900px; + margin: 0 auto; + padding: 32px; +} +.detail-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 32px; + margin-bottom: 40px; + padding-bottom: 24px; + border-bottom: 1px solid var(--border); +} +.detail-header h1 { margin: 0 0 4px; } +.detail-header h1 .version { + font-family: var(--mono); + font-size: 16px; + color: var(--fg-muted); + font-weight: 400; +} +.detail-header .desc { color: var(--fg-muted); margin: 0 0 12px; } +.detail-header .meta { + display: flex; + gap: 16px; + font-size: 13px; + color: var(--fg-muted); + margin-bottom: 8px; +} +.detail-header .id { font-family: var(--mono); } + +.install-actions { display: flex; flex-direction: column; gap: 8px; min-width: 200px; } +.btn { + display: inline-block; + padding: 10px 20px; + border-radius: var(--radius); + font-weight: 500; + text-align: center; + font-size: 14px; +} +.btn-primary { + background: var(--accent); + color: white; +} +.btn-primary:hover { background: var(--accent-dark); text-decoration: none; color: white; } +.btn-secondary { + background: transparent; + color: var(--fg); + border: 1px solid var(--border); +} +.btn-secondary:hover { border-color: var(--accent); text-decoration: none; color: var(--accent-dark); } + +.detail-dashboard { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 24px; + margin-bottom: 32px; +} +.detail-dashboard h2 { margin-top: 0; } +.detail-dashboard-note { + color: var(--fg-muted); + font-size: 13px; + margin-top: 4px; + margin-bottom: 20px; +} + +.detail-readme h2 { margin-top: 0; } +.detail-readme > div { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 24px; +} + +/* ---------- config schema panel (v2.3) ---------- */ + +.detail-config { margin-bottom: 32px; } +.detail-config:empty, .detail-config > div:empty { display: none; } + +.config-schema { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 24px; +} +.config-schema-header { margin-top: 0; } +.config-schema-desc { + color: var(--fg-muted); + font-size: 13px; + margin-top: 4px; + margin-bottom: 16px; +} +.config-schema-list { + margin: 0; + padding: 0; + display: grid; + grid-template-columns: 1fr; + gap: 12px; +} +.config-field-header { + display: flex; + align-items: baseline; + gap: 8px; + margin-top: 4px; + font-weight: 500; +} +.config-field-key { font-family: var(--mono); font-size: 13px; } +.config-field-type { + font-family: var(--mono); + font-size: 11px; + padding: 1px 6px; + border-radius: 10px; + background: rgba(0,0,0,0.08); + color: var(--fg-muted); +} +.config-field-required { + font-size: 11px; + color: var(--red); + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 1px 6px; + border-radius: 10px; + background: rgba(217,83,79,0.12); +} +.config-field-body { + margin: 0 0 4px 0; + padding-left: 0; + font-size: 14px; +} +.config-field-label { + font-size: 14px; + margin-bottom: 2px; +} +.config-field-description { + color: var(--fg-muted); + font-size: 13px; + margin-bottom: 4px; +} +.config-field-constraint { + font-size: 12px; + color: var(--fg-muted); + font-style: italic; +} + +.config-model-rec { + margin-top: 20px; + padding: 14px 16px; + border-radius: var(--radius); + background: rgba(42,168,118,0.08); + border: 1px solid rgba(42,168,118,0.2); +} +.config-model-label { + font-size: 11px; + color: var(--accent-dark); + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 600; + margin-bottom: 4px; +} +.config-model-preferred { + font-family: var(--mono); + font-size: 14px; + margin-bottom: 4px; +} +.config-model-rationale { + color: var(--fg-muted); + font-size: 13px; +} +.config-model-alternatives { + color: var(--fg-muted); + font-size: 12px; + margin-top: 4px; +} + +/* ---------- dashboard preview ---------- */ + +.dashboard-header h1.dashboard-title { margin: 0 0 4px; font-size: 22px; } +.dashboard-desc { color: var(--fg-muted); margin: 0 0 24px; font-size: 14px; } +.dashboard-section { margin-bottom: 24px; } +.section-title { margin: 0 0 10px; font-size: 14px; font-weight: 600; color: var(--fg-muted); text-transform: uppercase; letter-spacing: 0.5px; } + +.widget-grid { + display: grid; + grid-template-columns: repeat(var(--cols, 3), 1fr); + gap: 12px; +} + +.widget { + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 14px 16px; +} + +.widget-title { + font-size: 12px; + font-weight: 500; + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* stat */ +.widget-stat { display: flex; flex-direction: column; gap: 6px; } +.widget-stat-top { display: flex; align-items: center; gap: 8px; } +.widget-stat-icon { font-size: 14px; color: var(--fg-muted); } +.widget-stat-value { font-size: 32px; font-weight: 600; line-height: 1.1; } +.widget-stat-subtitle { font-size: 11px; color: var(--fg-muted); } +.widget-stat[data-color="green"] .widget-stat-icon { color: var(--accent); } +.widget-stat[data-color="red"] .widget-stat-icon { color: var(--red); } +.widget-stat[data-color="blue"] .widget-stat-icon { color: var(--blue); } +.widget-stat[data-color="orange"] .widget-stat-icon { color: var(--orange); } + +/* progress */ +.widget-progress-label { font-size: 13px; margin: 6px 0 8px; } +.progress-bar { height: 8px; background: rgba(0,0,0,0.05); border-radius: 4px; overflow: hidden; } +.progress-fill { height: 100%; background: var(--accent); border-radius: 4px; } + +/* text */ +.widget-text-body { font-size: 14px; margin-top: 6px; } +.widget-text-body h1 { font-size: 20px; margin: 12px 0 8px; } +.widget-text-body h2 { font-size: 17px; margin: 10px 0 6px; } +.widget-text-body h3 { font-size: 14px; margin: 8px 0 4px; } +.widget-text-body p { margin: 8px 0; } +.widget-text-body ul, .widget-text-body ol { padding-left: 22px; } + +/* table */ +.data-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 8px; } +.data-table th, .data-table td { padding: 6px 8px; border-bottom: 1px solid var(--border); text-align: left; } +.data-table th { font-weight: 500; color: var(--fg-muted); } + +/* list */ +.widget-list-items { margin: 6px 0 0; padding-left: 18px; font-size: 13px; } +.widget-list-item { + display: flex; + justify-content: space-between; + gap: 12px; + padding: 3px 0; +} +.widget-list-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.widget-list-status { + font-size: 10px; + padding: 2px 6px; + border-radius: 10px; + background: rgba(0,0,0,0.08); + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: 0.5px; + flex-shrink: 0; +} +.widget-list-status[data-status="up"] { background: rgba(42,168,118,0.18); color: var(--accent-dark); } +.widget-list-status[data-status="down"] { background: rgba(217,83,79,0.18); color: var(--red); } + +/* chart */ +.widget-chart-svg { width: 100%; height: auto; margin-top: 8px; } +.chart-axis { stroke: var(--border); stroke-width: 1; } +.chart-line { fill: none; stroke-width: 2; } +.chart-line[data-color="accent"], .chart-bar[data-color="accent"] { stroke: var(--accent); fill: var(--accent); } +.chart-line[data-color="red"], .chart-bar[data-color="red"] { stroke: var(--red); fill: var(--red); } +.chart-line[data-color="blue"], .chart-bar[data-color="blue"] { stroke: var(--blue); fill: var(--blue); } +.chart-line[data-color="orange"], .chart-bar[data-color="orange"] { stroke: var(--orange); fill: var(--orange); } +.widget-chart-empty { color: var(--fg-muted); font-size: 13px; padding: 20px 0; text-align: center; } + +/* webview */ +.widget-webview iframe { border: 1px solid var(--border); border-radius: 6px; margin-top: 8px; } + +/* unknown */ +.widget-unknown-body { color: var(--fg-muted); font-size: 13px; margin-top: 6px; } + +/* ---------- responsive ---------- */ + +@media (max-width: 680px) { + .site-header { padding: 12px 16px; } + .site-nav a { margin-left: 12px; font-size: 13px; } + .hero { padding: 32px 16px 16px; } + .catalog, .detail { padding: 16px; } + .detail-header { flex-direction: column; gap: 16px; } + .install-actions { flex-direction: row; min-width: 0; } + .btn { flex: 1; } +} diff --git a/site/template.html.tmpl b/site/template.html.tmpl new file mode 100644 index 0000000..9f9c9d4 --- /dev/null +++ b/site/template.html.tmpl @@ -0,0 +1,104 @@ + + + + + + {{NAME}} — Scarf Templates + + + + + + + +
+
+
+

{{NAME}} v{{VERSION}}

+

{{DESC}}

+

+ by {{AUTHOR_HTML}} + {{ID}} + {{CATEGORY}} +

+

{{TAGS_HTML}}

+
+ +
+ +
+

Live dashboard preview

+

+ Exactly what you'll see inside Scarf after install. Values shown here are + placeholders; the agent updates them each time the cron job runs. +

+
+
+ +
+
+
+ +
+

README

+
+
+
+ + + + + + + diff --git a/site/widgets.js b/site/widgets.js new file mode 100644 index 0000000..fc72e7e --- /dev/null +++ b/site/widgets.js @@ -0,0 +1,523 @@ +// Scarf dashboard widget renderer — the dogfood piece. +// +// Takes the SAME `dashboard.json` shape the Scarf macOS app renders +// (see scarf/scarf/Core/Models/ProjectDashboard.swift) and produces an +// HTML approximation for the catalog site. A template's detail page +// shows a live preview of exactly what the user's project dashboard +// will look like after install. +// +// Widget types mirrored from the Swift dispatcher: +// stat — big number + label + icon + color +// progress — label + 0..1 bar +// text — markdown (tiny subset renderer) +// table — plain HTML table +// list — bulleted list with optional status badge +// chart — SVG line/bar by series +// webview — sandboxed