From e48bf37995500ef57b0f35ad8b82465b093ef0e1 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 4 Nov 2026 17:30:57 +0000 Subject: [PATCH] feat(addons): translate the add-on pages into every language of the web UI The shared connector page kept its words in English text maps inside the components, and only Studio shipped translations. Move the maps to JSON bundles (addons/internal/connector-ui/src/i18n) and add bundles for the Telegram, Slack, Discord, GitHub, Jellyfin, Home Assistant and Talk pages in German, Spanish, French, Indonesian, Italian, Korean, Brazilian Portuguese and Simplified Chinese. English stays the default, and a key that a bundle lacks reads in English. Plural sentences now go through the translator, so each language picks its own plural form. The "More" and "optional" labels, the sender line and the dates follow the language of the page instead of the browser. A new gate, addons/internal/i18ncheck, fails when a language lacks a bundle, a bundle lacks a key or has an extra one, a placeholder differs from English or a value is empty. It runs in make test-i18n and so in the UI unit tests of CI, with no baseline. Playwright specs render the pages in Italian, check that no language runs text out of its box on a phone, and save screenshots when UI_SHOTS_DIR is set. Assisted-by: Claude:claude-sonnet-5-5 node --- .agents/building-and-testing.md | 1 + Makefile | 5 + addons/discord/ui/src/addon.jsx | 11 +- addons/discord/ui/src/i18n/discord.de.json | 87 ++++++ addons/discord/ui/src/i18n/discord.es.json | 87 ++++++ addons/discord/ui/src/i18n/discord.fr.json | 87 ++++++ addons/discord/ui/src/i18n/discord.id.json | 87 ++++++ addons/discord/ui/src/i18n/discord.it.json | 87 ++++++ addons/discord/ui/src/i18n/discord.ko.json | 87 ++++++ addons/discord/ui/src/i18n/discord.pt-BR.json | 87 ++++++ addons/discord/ui/src/i18n/discord.zh-CN.json | 87 ++++++ addons/github/ui/src/addon.jsx | 14 +- addons/github/ui/src/i18n/github.de.json | 131 ++++++++ addons/github/ui/src/i18n/github.es.json | 131 ++++++++ addons/github/ui/src/i18n/github.fr.json | 131 ++++++++ addons/github/ui/src/i18n/github.id.json | 131 ++++++++ addons/github/ui/src/i18n/github.it.json | 131 ++++++++ addons/github/ui/src/i18n/github.ko.json | 131 ++++++++ addons/github/ui/src/i18n/github.pt-BR.json | 131 ++++++++ addons/github/ui/src/i18n/github.zh-CN.json | 131 ++++++++ addons/home-assistant/ui/src/addon.jsx | 14 +- .../ui/src/i18n/home-assistant.de.json | 140 +++++++++ .../ui/src/i18n/home-assistant.es.json | 140 +++++++++ .../ui/src/i18n/home-assistant.fr.json | 140 +++++++++ .../ui/src/i18n/home-assistant.id.json | 140 +++++++++ .../ui/src/i18n/home-assistant.it.json | 140 +++++++++ .../ui/src/i18n/home-assistant.ko.json | 140 +++++++++ .../ui/src/i18n/home-assistant.pt-BR.json | 140 +++++++++ .../ui/src/i18n/home-assistant.zh-CN.json | 140 +++++++++ .../internal/connector-ui/src/AccessView.jsx | 17 +- .../connector-ui/src/AnswerFields.jsx | 51 +--- .../connector-ui/src/ApprovalFields.jsx | 39 --- .../connector-ui/src/CapabilitiesPanel.jsx | 45 --- .../connector-ui/src/ConnectorPage.jsx | 22 +- .../internal/connector-ui/src/FilesFields.jsx | 19 -- .../connector-ui/src/PairingFields.jsx | 21 +- .../connector-ui/src/PermissionFields.jsx | 64 ---- .../connector-ui/src/ReadinessList.jsx | 71 +---- addons/internal/connector-ui/src/access.js | 15 +- .../internal/connector-ui/src/connector.css | 6 +- addons/internal/connector-ui/src/connector.js | 14 +- addons/internal/connector-ui/src/controls.jsx | 14 +- .../connector-ui/src/i18n/connector.de.json | 281 ++++++++++++++++++ .../connector-ui/src/i18n/connector.en.json | 281 ++++++++++++++++++ .../connector-ui/src/i18n/connector.es.json | 281 ++++++++++++++++++ .../connector-ui/src/i18n/connector.fr.json | 281 ++++++++++++++++++ .../connector-ui/src/i18n/connector.id.json | 281 ++++++++++++++++++ .../connector-ui/src/i18n/connector.it.json | 281 ++++++++++++++++++ .../connector-ui/src/i18n/connector.ko.json | 281 ++++++++++++++++++ .../src/i18n/connector.pt-BR.json | 281 ++++++++++++++++++ .../src/i18n/connector.zh-CN.json | 281 ++++++++++++++++++ .../internal/connector-ui/src/i18n/index.js | 20 ++ addons/internal/i18ncheck/check.mjs | 116 ++++++++ addons/internal/i18ncheck/test/check.test.mjs | 47 +++ addons/jellyfin/ui/src/addon.jsx | 14 +- addons/jellyfin/ui/src/i18n/jellyfin.de.json | 128 ++++++++ addons/jellyfin/ui/src/i18n/jellyfin.es.json | 128 ++++++++ addons/jellyfin/ui/src/i18n/jellyfin.fr.json | 128 ++++++++ addons/jellyfin/ui/src/i18n/jellyfin.id.json | 128 ++++++++ addons/jellyfin/ui/src/i18n/jellyfin.it.json | 128 ++++++++ addons/jellyfin/ui/src/i18n/jellyfin.ko.json | 128 ++++++++ .../jellyfin/ui/src/i18n/jellyfin.pt-BR.json | 128 ++++++++ .../jellyfin/ui/src/i18n/jellyfin.zh-CN.json | 128 ++++++++ addons/slack/ui/src/addon.jsx | 11 +- addons/slack/ui/src/i18n/slack.de.json | 69 +++++ addons/slack/ui/src/i18n/slack.es.json | 69 +++++ addons/slack/ui/src/i18n/slack.fr.json | 69 +++++ addons/slack/ui/src/i18n/slack.id.json | 69 +++++ addons/slack/ui/src/i18n/slack.it.json | 69 +++++ addons/slack/ui/src/i18n/slack.ko.json | 69 +++++ addons/slack/ui/src/i18n/slack.pt-BR.json | 69 +++++ addons/slack/ui/src/i18n/slack.zh-CN.json | 69 +++++ addons/talk/ui/src/addon.jsx | 11 +- addons/talk/ui/src/i18n/talk.de.json | 149 ++++++++++ addons/talk/ui/src/i18n/talk.es.json | 149 ++++++++++ addons/talk/ui/src/i18n/talk.fr.json | 149 ++++++++++ addons/talk/ui/src/i18n/talk.id.json | 149 ++++++++++ addons/talk/ui/src/i18n/talk.it.json | 149 ++++++++++ addons/talk/ui/src/i18n/talk.ko.json | 149 ++++++++++ addons/talk/ui/src/i18n/talk.pt-BR.json | 149 ++++++++++ addons/talk/ui/src/i18n/talk.zh-CN.json | 149 ++++++++++ addons/telegram/ui/src/addon.jsx | 11 +- addons/telegram/ui/src/i18n/telegram.de.json | 52 ++++ addons/telegram/ui/src/i18n/telegram.es.json | 52 ++++ addons/telegram/ui/src/i18n/telegram.fr.json | 52 ++++ addons/telegram/ui/src/i18n/telegram.id.json | 52 ++++ addons/telegram/ui/src/i18n/telegram.it.json | 52 ++++ addons/telegram/ui/src/i18n/telegram.ko.json | 52 ++++ .../telegram/ui/src/i18n/telegram.pt-BR.json | 52 ++++ .../telegram/ui/src/i18n/telegram.zh-CN.json | 52 ++++ addons/telegram/ui/test/locale.test.mjs | 29 ++ core/http/react-ui/e2e/addon-locale.spec.js | 203 +++++++++++++ core/http/react-ui/e2e/studio-addon.js | 4 +- docs/content/features/addons.md | 19 +- 94 files changed, 9161 insertions(+), 344 deletions(-) create mode 100644 addons/discord/ui/src/i18n/discord.de.json create mode 100644 addons/discord/ui/src/i18n/discord.es.json create mode 100644 addons/discord/ui/src/i18n/discord.fr.json create mode 100644 addons/discord/ui/src/i18n/discord.id.json create mode 100644 addons/discord/ui/src/i18n/discord.it.json create mode 100644 addons/discord/ui/src/i18n/discord.ko.json create mode 100644 addons/discord/ui/src/i18n/discord.pt-BR.json create mode 100644 addons/discord/ui/src/i18n/discord.zh-CN.json create mode 100644 addons/github/ui/src/i18n/github.de.json create mode 100644 addons/github/ui/src/i18n/github.es.json create mode 100644 addons/github/ui/src/i18n/github.fr.json create mode 100644 addons/github/ui/src/i18n/github.id.json create mode 100644 addons/github/ui/src/i18n/github.it.json create mode 100644 addons/github/ui/src/i18n/github.ko.json create mode 100644 addons/github/ui/src/i18n/github.pt-BR.json create mode 100644 addons/github/ui/src/i18n/github.zh-CN.json create mode 100644 addons/home-assistant/ui/src/i18n/home-assistant.de.json create mode 100644 addons/home-assistant/ui/src/i18n/home-assistant.es.json create mode 100644 addons/home-assistant/ui/src/i18n/home-assistant.fr.json create mode 100644 addons/home-assistant/ui/src/i18n/home-assistant.id.json create mode 100644 addons/home-assistant/ui/src/i18n/home-assistant.it.json create mode 100644 addons/home-assistant/ui/src/i18n/home-assistant.ko.json create mode 100644 addons/home-assistant/ui/src/i18n/home-assistant.pt-BR.json create mode 100644 addons/home-assistant/ui/src/i18n/home-assistant.zh-CN.json create mode 100644 addons/internal/connector-ui/src/i18n/connector.de.json create mode 100644 addons/internal/connector-ui/src/i18n/connector.en.json create mode 100644 addons/internal/connector-ui/src/i18n/connector.es.json create mode 100644 addons/internal/connector-ui/src/i18n/connector.fr.json create mode 100644 addons/internal/connector-ui/src/i18n/connector.id.json create mode 100644 addons/internal/connector-ui/src/i18n/connector.it.json create mode 100644 addons/internal/connector-ui/src/i18n/connector.ko.json create mode 100644 addons/internal/connector-ui/src/i18n/connector.pt-BR.json create mode 100644 addons/internal/connector-ui/src/i18n/connector.zh-CN.json create mode 100644 addons/internal/connector-ui/src/i18n/index.js create mode 100644 addons/internal/i18ncheck/check.mjs create mode 100644 addons/internal/i18ncheck/test/check.test.mjs create mode 100644 addons/jellyfin/ui/src/i18n/jellyfin.de.json create mode 100644 addons/jellyfin/ui/src/i18n/jellyfin.es.json create mode 100644 addons/jellyfin/ui/src/i18n/jellyfin.fr.json create mode 100644 addons/jellyfin/ui/src/i18n/jellyfin.id.json create mode 100644 addons/jellyfin/ui/src/i18n/jellyfin.it.json create mode 100644 addons/jellyfin/ui/src/i18n/jellyfin.ko.json create mode 100644 addons/jellyfin/ui/src/i18n/jellyfin.pt-BR.json create mode 100644 addons/jellyfin/ui/src/i18n/jellyfin.zh-CN.json create mode 100644 addons/slack/ui/src/i18n/slack.de.json create mode 100644 addons/slack/ui/src/i18n/slack.es.json create mode 100644 addons/slack/ui/src/i18n/slack.fr.json create mode 100644 addons/slack/ui/src/i18n/slack.id.json create mode 100644 addons/slack/ui/src/i18n/slack.it.json create mode 100644 addons/slack/ui/src/i18n/slack.ko.json create mode 100644 addons/slack/ui/src/i18n/slack.pt-BR.json create mode 100644 addons/slack/ui/src/i18n/slack.zh-CN.json create mode 100644 addons/talk/ui/src/i18n/talk.de.json create mode 100644 addons/talk/ui/src/i18n/talk.es.json create mode 100644 addons/talk/ui/src/i18n/talk.fr.json create mode 100644 addons/talk/ui/src/i18n/talk.id.json create mode 100644 addons/talk/ui/src/i18n/talk.it.json create mode 100644 addons/talk/ui/src/i18n/talk.ko.json create mode 100644 addons/talk/ui/src/i18n/talk.pt-BR.json create mode 100644 addons/talk/ui/src/i18n/talk.zh-CN.json create mode 100644 addons/telegram/ui/src/i18n/telegram.de.json create mode 100644 addons/telegram/ui/src/i18n/telegram.es.json create mode 100644 addons/telegram/ui/src/i18n/telegram.fr.json create mode 100644 addons/telegram/ui/src/i18n/telegram.id.json create mode 100644 addons/telegram/ui/src/i18n/telegram.it.json create mode 100644 addons/telegram/ui/src/i18n/telegram.ko.json create mode 100644 addons/telegram/ui/src/i18n/telegram.pt-BR.json create mode 100644 addons/telegram/ui/src/i18n/telegram.zh-CN.json create mode 100644 addons/telegram/ui/test/locale.test.mjs create mode 100644 core/http/react-ui/e2e/addon-locale.spec.js diff --git a/.agents/building-and-testing.md b/.agents/building-and-testing.md index f8fdb82795..fccc4119a0 100644 --- a/.agents/building-and-testing.md +++ b/.agents/building-and-testing.md @@ -51,6 +51,7 @@ Rules (both gates): - **Add-on Go tests:** `TEST_PATHS` and `COVERAGE_ROOTS` include `./addons/...` and `./tests/addons/actione2e` on Linux (the Sandbox add-on does not build on macOS). The other `tests/addons/*` suites need fixtures that `scripts/addons/verify.sh` prepares and run in `.github/workflows/addons-tests.yml`. See `docs/content/development/addon-testing.md`. - **UI unit checks (`make test-ui-unit`, `tests-ui-unit.yml`):** eslint (errors only), the inline-style ratchet, the host UI unit tests, the unit tests of every `addons/*/ui` package and the locale ratchet. - **Locale ratchet (`make test-i18n`):** every locale must have every namespace and key of English. `core/http/react-ui/i18n-baseline.json` records the gaps that exist today as a count per locale and namespace; the count may only fall. A namespace file that a locale lacks fails the gate whatever the baseline says (the browser would get a 404 for it on every page load). After you add keys to a locale, run `node scripts/i18n-check.mjs --write` in `core/http/react-ui` and commit the lower baseline; `--report` lists every missing key. +- **Add-on locale gate (also in `make test-i18n`):** the page bundles of the add-ons (`addons/*/ui/src/i18n/..json` and the shared `addons/internal/connector-ui/src/i18n`) must have one file per language of the web UI, every key of English, no extra key, the same `{{placeholders}}` and no empty value. There is no baseline: when you add an English key, add it to every language in the same change. `node addons/internal/i18ncheck/check.mjs` lists the gaps. - **PostgreSQL stores:** `test.yml` starts a PostgreSQL service and sets `LOCALAI_TEST_POSTGRES_URL`; `make test-postgres` fails when any PostgreSQL store test skips. - **Shipped bundle:** specs tagged `@production-chunks` (stale chunk recovery, `e2e/production-bundle.spec.js`) run on the production code-split build through `make test-ui-stale-chunk`; the coverage build inlines dynamic imports and skips them. - **Distributed:** `tests-e2e-distributed.yml` runs `make test-e2e-distributed` (PostgreSQL and NATS in containers) and `make test-distributed-fakebus` (the in-process bus, race detector) nightly and on changes to the distributed code. diff --git a/Makefile b/Makefile index edaa34b29e..f998bd7787 100644 --- a/Makefile +++ b/Makefile @@ -1726,8 +1726,13 @@ test-ui-unit: test-i18n ## Every locale must have every namespace and key of English, except the gaps ## recorded in core/http/react-ui/i18n-baseline.json, which may only shrink. +## The locale bundles of the add-on pages (addons/*/ui/src/i18n and the shared +## connector pages) have no baseline: every language of the web UI must have +## every key of English, with the same placeholders and no empty value. test-i18n: cd core/http/react-ui && node scripts/i18n-check.mjs + node addons/internal/i18ncheck/check.mjs + node --test addons/internal/i18ncheck/test/check.test.mjs ## The PostgreSQL stores of the chats, agents, connectors, notifications, ## workspace and triggers. They skip without a database, which would let a diff --git a/addons/discord/ui/src/addon.jsx b/addons/discord/ui/src/addon.jsx index 9f1d0443db..f9fb93d53a 100644 --- a/addons/discord/ui/src/addon.jsx +++ b/addons/discord/ui/src/addon.jsx @@ -5,9 +5,18 @@ import { registerLocales } from '../../../internal/host-compat/src/i18n.js' import ConnectorPage from '../../../internal/connector-ui/src/ConnectorPage' import { Field, FieldError, Notice, TextInput } from '../../../internal/connector-ui/src/controls' import en from './i18n/discord.en.json' +import it from './i18n/discord.it.json' +import es from './i18n/discord.es.json' +import de from './i18n/discord.de.json' +import fr from './i18n/discord.fr.json' +import zh from './i18n/discord.zh-CN.json' +import id from './i18n/discord.id.json' +import ko from './i18n/discord.ko.json' +import pt from './i18n/discord.pt-BR.json' import { DISCORD_LISTS, PERMISSIONS, PERMISSION_INTEGER, badEntries, discordSecret, inviteUrl, tokenProblem } from './ids' -registerLocales({ discord: { en } }) +// The words of the page, in every language of the web UI; a key a language lacks reads in English. +registerLocales({ discord: { en, it, es, de, fr, 'zh-CN': zh, id, ko, 'pt-BR': pt } }) // The bot token. It is typed only to replace it. function DiscordToken({ form, setForm, t }) { diff --git a/addons/discord/ui/src/i18n/discord.de.json b/addons/discord/ui/src/i18n/discord.de.json new file mode 100644 index 0000000000..32104ce06f --- /dev/null +++ b/addons/discord/ui/src/i18n/discord.de.json @@ -0,0 +1,87 @@ +{ + "title": "Discord", + "intro": "Verbinde einen Discord-Bot mit einem deiner gespeicherten Agenten. Personen sprechen mit dem Agenten in einer Direktnachricht an den Bot oder indem sie ihn in einem Kanal oder Thread erwähnen. Jede Person und jeder Thread bekommt einen eigenen Chat.", + "new": "Neuer Discord-Bot", + "edit": "Discord-Bot bearbeiten", + "name": "Name", + "token": "Bot-Token", + "tokenHelp": "Von der Seite Bot deiner Anwendung im Discord Developer Portal (Reset Token). Es wird versiegelt gespeichert und nie wieder angezeigt.", + "tokenSaved": "Gespeichert. Gib ein neues Token ein, um es zu ersetzen.", + "tokenSpace": "Das Token enthält keine Leerzeichen. Füge nur das Token ein, ohne das Wort Bot.", + "tokenShape": "Das ist zu kurz für ein Bot-Token.", + "agent": "Agent", + "agentNone": "Agent wählen", + "agentNoModel": "{{name}} (braucht ein Modell)", + "who": "Wer ihn nutzen darf", + "onlyListed": "Nur die Personen und Kanäle, die ich aufführe", + "anyone": "Alle, die dem Bot schreiben oder ihn in einem Kanal erwähnen können, den er lesen kann", + "anyoneWarning": "Das ist für öffentliche Kanäle und für Agenten gedacht, die nur lesen. Wer den Bot erreichen kann, kann mit diesem Agenten sprechen: per Direktnachricht, wenn er mit dem Bot einen Server teilt, und in jedem Kanal, den der Bot lesen kann und in dem er ihn erwähnt. Personen, die nicht auf einer Liste stehen, haben keine eigene Berechtigung: Der Agent kann für sie kein Gedächtnis speichern, keine Trigger vorschlagen und ihre Dateien nicht entgegennehmen, und die sensiblen Aktionen bleiben gesperrt. Die übrigen Werkzeuge des Agenten funktionieren weiterhin für alle. Was Personen in einem öffentlichen Kanal schreiben, ist nicht vertrauenswürdiger Text und kann versuchen, den Agenten zu lenken (Prompt Injection). Gib dem Agenten für diese Verbindung nur lesende Werkzeuge, keine, die Nachrichten senden, Geld ausgeben oder Dateien ändern. Jede Person bekommt einen eigenen Chat, und die Zahl der Nachrichten, die eine Person pro Minute senden kann, ist begrenzt.", + "anyoneConfirm": "Ich verstehe, dass jeder diesen Agenten nutzen kann und seine Werkzeuge nur lesend sein sollten", + "allowIds": "Erlaubte Personen und Kanäle", + "allowIdsHint": "Eine Nutzer-ID oder eine Kanal-ID (Zahlen)", + "rules": "Aufgeführte Personen können in einer Direktnachricht schreiben und den Bot in jedem Kanal erwähnen. Ein aufgeführter Kanal lässt alle darin den Bot dort erwähnen, aber sie bekommen keine eigene Berechtigung. Auf einem Server antwortet der Bot nur, wenn er erwähnt wird, wenn man ihm antwortet oder in einem Thread, in dem er erwähnt wurde. Er antwortet nie auf Bots oder Webhooks, und seine Nachrichten benachrichtigen niemanden. Nutze Kanal-IDs: Eine Server-ID (Guild) passt auf nichts. Nur IDs, keine Namen: Schalte in Discord den Entwicklermodus ein (Einstellungen, Erweitert), klicke dann mit der rechten Maustaste auf eine Person oder einen Kanal und wähle Copy ID.", + "badIds": "Das sind keine Discord-IDs: {{list}}. Nutze die Zahl aus Copy ID (17 bis 20 Ziffern). Namen werden nicht akzeptiert.", + "notifyChat": "Meine Benachrichtigungen an diesen Kanal oder diese Person senden", + "notifyHelp": "Optional. Eine Kanal-ID, oder user: gefolgt von deiner Nutzer-ID, um sie als Direktnachrichten zu erhalten.", + "test": "Test", + "testOk": "Mit {{bot}} verbunden.", + "checkFailed": "Gespeichert. Die Prüfung des Tokens ist fehlgeschlagen: {{reason}}", + "senders": "Absender", + "noSenders": "Noch hat niemand diesem Bot geschrieben.", + "allowButton": "Erlauben", + "ignored": "Ignoriert", + "accepted": "Akzeptiert", + "save": "Speichern", + "cancel": "Abbrechen", + "editButton": "Bearbeiten", + "delete": "Löschen", + "empty": "Noch keine Discord-Bots.", + "agentOf": "Agent: {{name}}", + "accepts": "Akzeptiert: {{who}}", + "modes": { + "anyone": "alle", + "nobody": "noch niemand", + "listed_one": "1 aufgeführter Eintrag", + "listed_other": "{{count}} aufgeführte Einträge" + }, + "on": "An", + "onFor": "{{name}} an", + "inactive": "Läuft nicht: {{reason}}.", + "confirmDelete": { + "title": "{{name}} löschen?", + "message": "Der Bot antwortet nicht mehr, und das mit ihm gespeicherte Token wird gelöscht. Um ihn erneut einzurichten, musst du das Token noch einmal eingeben.", + "confirm": "Löschen" + }, + "setup": { + "title": "So erstellst du den Discord-Bot", + "step1": "Wähle unter discord.com/developers/applications New Application und gib ihr einen Namen.", + "step2": "Wähle auf der Seite Bot Reset Token, kopiere das Token einmal und füge es hier in das Feld Bot-Token ein. Halte es geheim.", + "step3": "Schalte auf derselben Seite unter Privileged Gateway Intents den Message Content Intent ein. Ohne ihn kann der Bot nicht lesen, was Personen auf einem Server schreiben. Lass die anderen beiden Intents aus.", + "step4": "Optional: Schalte Public Bot aus, damit nur du den Bot zu einem Server hinzufügen kannst.", + "step5": "Gib unten die Application ID (General Information) ein und öffne den Link, der daraus entsteht. Wähle den Server und lass die Berechtigungen, wie sie sind.", + "step6": "Prüfe auf dem Server, dass die Rolle des Bots die Kanäle sehen kann, in denen er antworten soll. Führe dann die Kanal-IDs hier auf und erwähne den Bot, um es auszuprobieren.", + "appId": "Application ID", + "appIdHelp": "Die Zahl aus General Information deiner Anwendung. Sie ist nicht geheim. Sie erzeugt nur den Link; nichts wird gespeichert.", + "appIdBad": "Die Application ID ist eine Zahl mit 17 bis 20 Ziffern.", + "invite": "Link, der den Bot zu einem Server hinzufügt", + "copy": "Link kopieren", + "copied": "Kopiert", + "permissions": "Der Link fordert diese Berechtigungen an (die Zahl {{number}}):", + "perm": { + "viewChannels": "View Channels: um zu sehen, wo er erwähnt wird", + "sendMessages": "Send Messages: um zu antworten", + "sendInThreads": "Send Messages in Threads: um in Threads zu antworten", + "attachFiles": "Attach Files: um Dateien zu senden, die der Agent erstellt", + "readHistory": "Read Message History: um einen Thread und die Dateien einer Nachricht zu lesen" + }, + "never": "Gib dem Bot nie Administrator, Manage Messages oder Mention Everyone. Er braucht keine davon." + }, + "back": "Zurück zu den Konnektoren", + "answer": { + "reply": { + "off": "Nur die Antwort (Discord hat hier keine Schreibanzeige)" + }, + "replyHelp": "Fortschritt und Live zeigen eine Arbeitsnachricht, die die Antwort am Ende ersetzt. Discord begrenzt Bearbeitungen, daher aktualisiert Live etwa einmal pro Sekunde. Eine lange Antwort geht in neuen Nachrichten mit höchstens 2000 Zeichen weiter.", + "conversationHelp": "Sende !new in Discord, um einen neuen Chat zu starten. Eine Nachricht, die mit einem Schrägstrich beginnt, kann Discord für einen eigenen Befehl halten." + } +} diff --git a/addons/discord/ui/src/i18n/discord.es.json b/addons/discord/ui/src/i18n/discord.es.json new file mode 100644 index 0000000000..b51cb613a6 --- /dev/null +++ b/addons/discord/ui/src/i18n/discord.es.json @@ -0,0 +1,87 @@ +{ + "title": "Discord", + "intro": "Conecta un bot de Discord a uno de tus agentes guardados. Las personas hablan con el agente en un mensaje directo con el bot, o mencionándolo en un canal o hilo. Cada persona, y cada hilo, tiene su propio chat.", + "new": "Nuevo bot de Discord", + "edit": "Editar bot de Discord", + "name": "Nombre", + "token": "Token del bot", + "tokenHelp": "De la página Bot de tu aplicación en el portal de desarrolladores de Discord (Reset Token). Se guarda cifrado y no se vuelve a mostrar.", + "tokenSaved": "Guardado. Escribe un token nuevo para reemplazarlo.", + "tokenSpace": "El token no tiene espacios. Pega solo el token, sin la palabra Bot.", + "tokenShape": "Esto es demasiado corto para un token de bot.", + "agent": "Agente", + "agentNone": "Elige un agente", + "agentNoModel": "{{name}} (necesita un modelo)", + "who": "Quién puede usarlo", + "onlyListed": "Solo las personas y canales que yo añada a la lista", + "anyone": "Cualquiera que pueda escribir al bot o mencionarlo en un canal que este pueda leer", + "anyoneWarning": "Esto es para canales públicos y para agentes que solo leen. Cualquiera que pueda llegar al bot puede hablar con este agente: por mensaje directo si comparte un servidor con el bot, y en cada canal donde el bot pueda leer y lo mencionen. Las personas que no están en una lista no tienen permisos propios: el agente no puede guardar memoria, proponer disparadores ni tomar sus archivos por ellas, y las acciones sensibles siguen cerradas. Las demás herramientas del agente funcionan para todos. Lo que la gente escribe en un canal público es texto no fiable y puede intentar dirigir al agente (prompt injection). Da al agente herramientas de solo lectura para esta conexión, y ninguna que envíe mensajes, gaste dinero o cambie archivos. Cada persona tiene su propio chat, y el número de mensajes que una persona puede enviar por minuto está limitado.", + "anyoneConfirm": "Entiendo que cualquiera puede usar este agente y que sus herramientas deben ser de solo lectura", + "allowIds": "Personas y canales permitidos", + "allowIdsHint": "Un ID de usuario o de canal (números)", + "rules": "Las personas de la lista pueden escribir por mensaje directo y mencionar al bot en cualquier canal. Un canal de la lista permite que todos los que estén en él mencionen allí al bot, pero no reciben permisos propios. En un servidor el bot solo responde cuando lo mencionan o le responden, o dentro de un hilo donde lo mencionaron. Nunca responde a bots ni a webhooks, y sus mensajes nunca avisan a nadie. Usa IDs de canal: el ID de un servidor (guild) no coincide con nada. Solo IDs, sin nombres: en Discord activa el Modo desarrollador (Settings, Advanced), haz clic derecho en una persona o canal y elige Copy ID.", + "badIds": "Esto no son IDs de Discord: {{list}}. Usa el número de Copy ID (de 17 a 20 dígitos). No se aceptan nombres.", + "notifyChat": "Enviar mis notificaciones a este canal o persona", + "notifyHelp": "Opcional. Un ID de canal, o user: seguido de tu ID de usuario para recibirlas como mensajes directos.", + "test": "Probar", + "testOk": "Conectado a {{bot}}.", + "checkFailed": "Guardado. Falló la comprobación del token: {{reason}}", + "senders": "Remitentes", + "noSenders": "Nadie ha escrito aún a este bot.", + "allowButton": "Permitir", + "ignored": "Ignorado", + "accepted": "Aceptado", + "save": "Guardar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Eliminar", + "empty": "Aún no hay bots de Discord.", + "agentOf": "Agente: {{name}}", + "accepts": "Acepta: {{who}}", + "modes": { + "anyone": "cualquiera", + "nobody": "nadie por ahora", + "listed_one": "1 entrada en la lista", + "listed_other": "{{count}} entradas en la lista" + }, + "on": "Activado", + "onFor": "{{name}} activado", + "inactive": "No se está ejecutando: {{reason}}.", + "confirmDelete": { + "title": "¿Eliminar {{name}}?", + "message": "El bot deja de responder y se elimina el token guardado con él. Para configurarlo de nuevo tendrás que volver a introducir el token.", + "confirm": "Eliminar" + }, + "setup": { + "title": "Cómo crear el bot de Discord", + "step1": "En discord.com/developers/applications elige New Application y ponle un nombre.", + "step2": "En la página Bot elige Reset Token, copia el token una sola vez y pégalo aquí en el campo Token del bot. Mantenlo en secreto.", + "step3": "En la misma página, en Privileged Gateway Intents, activa Message Content Intent. Sin él, el bot no puede leer lo que la gente escribe en un servidor. Deja desactivados los otros dos intents.", + "step4": "Opcional: desactiva Public Bot, para que solo tú puedas añadir el bot a un servidor.", + "step5": "Escribe abajo el Application ID (General Information) y abre el enlace que genera. Elige el servidor y deja los permisos como están.", + "step6": "En el servidor, comprueba que el rol del bot puede ver los canales en los que debe responder. Luego añade aquí los IDs de esos canales y menciona al bot para probarlo.", + "appId": "Application ID", + "appIdHelp": "El número de General Information de tu aplicación. No es secreto. Solo sirve para generar el enlace; no se guarda nada.", + "appIdBad": "El Application ID es un número de 17 a 20 dígitos.", + "invite": "Enlace que añade el bot a un servidor", + "copy": "Copiar el enlace", + "copied": "Copiado", + "permissions": "El enlace pide estos permisos (el número {{number}}):", + "perm": { + "viewChannels": "View Channels: para ver dónde lo mencionan", + "sendMessages": "Send Messages: para responder", + "sendInThreads": "Send Messages in Threads: para responder en hilos", + "attachFiles": "Attach Files: para enviar los archivos que crea el agente", + "readHistory": "Read Message History: para leer un hilo y los archivos de un mensaje" + }, + "never": "No des nunca al bot Administrator, Manage Messages ni Mention Everyone. No necesita ninguno." + }, + "back": "Volver a los conectores", + "answer": { + "reply": { + "off": "Solo la respuesta (Discord no tiene aquí indicador de escritura)" + }, + "replyHelp": "Progreso y En vivo muestran un solo mensaje de trabajo, y la respuesta lo reemplaza. Discord limita las ediciones, así que En vivo se actualiza cada segundo más o menos. Una respuesta larga sigue en mensajes nuevos de 2000 caracteres como máximo.", + "conversationHelp": "Envía !new en Discord para empezar un chat nuevo. Discord puede interpretar un mensaje que empieza por una barra como uno de sus propios comandos." + } +} diff --git a/addons/discord/ui/src/i18n/discord.fr.json b/addons/discord/ui/src/i18n/discord.fr.json new file mode 100644 index 0000000000..da7e81816c --- /dev/null +++ b/addons/discord/ui/src/i18n/discord.fr.json @@ -0,0 +1,87 @@ +{ + "title": "Discord", + "intro": "Associez un bot Discord à l’un de vos agents enregistrés. On parle à l’agent en message privé avec le bot, ou en le mentionnant dans un canal ou un fil. Chaque personne, et chaque fil, a son propre chat.", + "new": "Nouveau bot Discord", + "edit": "Modifier le bot Discord", + "name": "Nom", + "token": "Jeton du bot", + "tokenHelp": "Se trouve sur la page Bot de votre application dans le portail développeur Discord (Reset Token). Il est conservé scellé et n’est plus jamais affiché.", + "tokenSaved": "Enregistré. Saisissez un nouveau jeton pour le remplacer.", + "tokenSpace": "Le jeton ne contient pas d’espace. Collez uniquement le jeton, sans le mot Bot.", + "tokenShape": "C’est trop court pour un jeton de bot.", + "agent": "Agent", + "agentNone": "Choisir un agent", + "agentNoModel": "{{name}} (a besoin d’un modèle)", + "who": "Qui peut l’utiliser", + "onlyListed": "Seulement les personnes et canaux que j’indique", + "anyone": "Toute personne pouvant écrire au bot, ou le mentionner dans un canal qu’il peut lire", + "anyoneWarning": "Ceci est pour les canaux publics et les agents qui ne font que lire. Toute personne pouvant joindre le bot peut parler à cet agent : en message privé si elle partage un serveur avec le bot, et dans chaque canal où le bot peut lire et où elle le mentionne. Les personnes absentes d’une liste n’ont aucune autorisation propre : l’agent ne peut pas enregistrer en mémoire, proposer des déclencheurs ni prendre leurs fichiers pour elles, et les actions sensibles restent fermées. Les autres outils de l’agent fonctionnent pour tout le monde. Ce que les gens écrivent dans un canal public est un texte non fiable, qui peut tenter de détourner l’agent (injection de prompt). Donnez à l’agent des outils en lecture seule pour cette connexion, et aucun qui envoie des messages, dépense de l’argent ou modifie des fichiers. Chaque personne a son propre chat, et le nombre de messages qu’une personne peut envoyer par minute est limité.", + "anyoneConfirm": "Je comprends que n’importe qui peut utiliser cet agent, et que ses outils doivent être en lecture seule", + "allowIds": "Personnes et canaux autorisés", + "allowIdsHint": "Un ID d’utilisateur ou un ID de canal (des chiffres)", + "rules": "Les personnes de la liste peuvent écrire en message privé et mentionner le bot dans n’importe quel canal. Un canal de la liste permet à tous ses membres d’y mentionner le bot, mais ils n’ont aucune autorisation propre. Sur un serveur, le bot ne répond que lorsqu’on le mentionne ou qu’on lui répond, ou dans un fil où il a été mentionné. Il ne répond jamais aux bots ni aux webhooks, et ses messages ne notifient personne. Utilisez des ID de canal : un ID de serveur (guild) ne correspond à rien. Des ID uniquement, pas de noms : dans Discord, activez le mode développeur (Paramètres, Avancé), puis faites un clic droit sur une personne ou un canal et choisissez Copier l’ID.", + "badIds": "Ce ne sont pas des ID Discord : {{list}}. Utilisez le nombre obtenu avec Copier l’ID (17 à 20 chiffres). Les noms ne sont pas acceptés.", + "notifyChat": "Envoyer mes notifications à ce canal ou à cette personne", + "notifyHelp": "Facultatif. Un ID de canal, ou user: suivi de votre ID d’utilisateur pour les recevoir en messages privés.", + "test": "Tester", + "testOk": "Connecté à {{bot}}.", + "checkFailed": "Enregistré. La vérification du jeton a échoué : {{reason}}", + "senders": "Expéditeurs", + "noSenders": "Personne n’a encore écrit à ce bot.", + "allowButton": "Autoriser", + "ignored": "Ignoré", + "accepted": "Accepté", + "save": "Enregistrer", + "cancel": "Annuler", + "editButton": "Modifier", + "delete": "Supprimer", + "empty": "Aucun bot Discord pour le moment.", + "agentOf": "Agent : {{name}}", + "accepts": "Accepte : {{who}}", + "modes": { + "anyone": "tout le monde", + "nobody": "personne pour l’instant", + "listed_one": "1 entrée dans la liste", + "listed_other": "{{count}} entrées dans la liste" + }, + "on": "Activé", + "onFor": "{{name}} activé", + "inactive": "À l’arrêt : {{reason}}.", + "confirmDelete": { + "title": "Supprimer {{name}} ?", + "message": "Le bot cesse de répondre, et le jeton enregistré avec lui est supprimé. Pour le reconfigurer, vous devrez saisir de nouveau le jeton.", + "confirm": "Supprimer" + }, + "setup": { + "title": "Comment créer le bot Discord", + "step1": "Sur discord.com/developers/applications, choisissez New Application et donnez-lui un nom.", + "step2": "Sur la page Bot, choisissez Reset Token, copiez le jeton une seule fois et collez-le dans le champ Jeton du bot ici. Gardez-le secret.", + "step3": "Sur la même page, sous Privileged Gateway Intents, activez Message Content Intent. Sans lui, le bot ne peut pas lire ce que les gens écrivent sur un serveur. Laissez les deux autres intents désactivés.", + "step4": "Facultatif : désactivez Public Bot, pour que vous seul puissiez ajouter le bot à un serveur.", + "step5": "Saisissez ci-dessous l’Application ID (General Information), puis ouvrez le lien qu’il construit. Choisissez le serveur et laissez les autorisations telles quelles.", + "step6": "Sur le serveur, vérifiez que le rôle du bot peut voir les canaux où il doit répondre. Indiquez ensuite ici les ID de ces canaux, puis mentionnez le bot pour l’essayer.", + "appId": "Application ID", + "appIdHelp": "Le nombre indiqué dans General Information de votre application. Il n’est pas secret. Il sert seulement à construire le lien ; rien n’est enregistré.", + "appIdBad": "L’Application ID est un nombre de 17 à 20 chiffres.", + "invite": "Lien qui ajoute le bot à un serveur", + "copy": "Copier le lien", + "copied": "Copié", + "permissions": "Le lien demande ces autorisations (le nombre {{number}}) :", + "perm": { + "viewChannels": "View Channels : pour voir où il est mentionné", + "sendMessages": "Send Messages : pour répondre", + "sendInThreads": "Send Messages in Threads : pour répondre dans les fils", + "attachFiles": "Attach Files : pour envoyer les fichiers créés par l’agent", + "readHistory": "Read Message History : pour lire un fil et les fichiers d’un message" + }, + "never": "Ne donnez jamais au bot Administrator, Manage Messages ni Mention Everyone. Il n’en a besoin d’aucune." + }, + "back": "Retour aux connecteurs", + "answer": { + "reply": { + "off": "La réponse seulement (Discord n’a pas d’indicateur de saisie ici)" + }, + "replyHelp": "Progression et En direct affichent un seul message de travail, que la réponse remplace. Discord limite les modifications, donc En direct se met à jour environ une fois par seconde. Une longue réponse continue dans de nouveaux messages de 2000 caractères au plus.", + "conversationHelp": "Envoyez !new dans Discord pour démarrer un nouveau chat. Un message qui commence par une barre oblique peut être pris par Discord pour l’une de ses propres commandes." + } +} diff --git a/addons/discord/ui/src/i18n/discord.id.json b/addons/discord/ui/src/i18n/discord.id.json new file mode 100644 index 0000000000..15de6eb9c9 --- /dev/null +++ b/addons/discord/ui/src/i18n/discord.id.json @@ -0,0 +1,87 @@ +{ + "title": "Discord", + "intro": "Hubungkan bot Discord ke salah satu agen tersimpan Anda. Orang berbicara dengan agen lewat pesan langsung ke bot, atau dengan menyebutnya di kanal atau thread. Setiap orang, dan setiap thread, mendapat chat tersendiri.", + "new": "Bot Discord baru", + "edit": "Ubah bot Discord", + "name": "Nama", + "token": "Token bot", + "tokenHelp": "Dari halaman Bot pada aplikasi Anda di portal pengembang Discord (Reset Token). Disimpan terenkripsi dan tidak pernah ditampilkan lagi.", + "tokenSaved": "Tersimpan. Ketik token baru untuk menggantinya.", + "tokenSpace": "Token tidak memiliki spasi. Tempel hanya tokennya, tanpa kata Bot.", + "tokenShape": "Ini terlalu pendek untuk token bot.", + "agent": "Agen", + "agentNone": "Pilih agen", + "agentNoModel": "{{name}} (perlu model)", + "who": "Siapa yang boleh memakai", + "onlyListed": "Hanya orang dan kanal yang saya daftarkan", + "anyone": "Siapa saja yang dapat mengirim pesan ke bot, atau menyebutnya di kanal yang dapat dibacanya", + "anyoneWarning": "Ini untuk kanal publik dan agen yang hanya membaca. Siapa saja yang dapat menjangkau bot dapat berbicara dengan agen ini: lewat pesan langsung jika berbagi server dengan bot, dan di setiap kanal tempat bot dapat membaca dan mereka menyebutnya. Orang yang tidak ada di daftar tidak memegang izin sendiri: agen tidak dapat menyimpan memori, mengusulkan pemicu, atau menerima file mereka, dan tindakan sensitif tetap tertutup. Alat agen lainnya tetap berfungsi untuk semua orang. Apa yang ditulis orang di kanal publik adalah teks yang tidak tepercaya, dan dapat mencoba mengarahkan agen (prompt injection). Berikan agen alat yang hanya membaca untuk koneksi ini, dan tidak ada yang mengirim pesan, membelanjakan uang, atau mengubah file.", + "anyoneConfirm": "Saya mengerti bahwa siapa saja dapat memakai agen ini, dan alatnya sebaiknya hanya-baca", + "allowIds": "Orang dan kanal yang diizinkan", + "allowIdsHint": "ID pengguna atau ID kanal (angka)", + "rules": "Orang yang terdaftar dapat menulis lewat pesan langsung dan menyebut bot di kanal mana pun. Kanal yang terdaftar membuat semua orang di dalamnya boleh menyebut bot di sana, tetapi mereka tidak mendapat izin sendiri. Di server, bot hanya menjawab saat disebut atau dibalas, atau di dalam thread tempat ia disebut. Bot tidak pernah menjawab bot atau webhook, dan pesannya tidak pernah memanggil siapa pun. Gunakan ID kanal: ID server (guild) tidak cocok dengan apa pun. Hanya ID, bukan nama: di Discord aktifkan Developer Mode (Settings, Advanced), lalu klik kanan orang atau kanal dan pilih Copy ID.", + "badIds": "Ini bukan ID Discord: {{list}}. Gunakan angka dari Copy ID (17 sampai 20 digit). Nama tidak diterima.", + "notifyChat": "Kirim notifikasi saya ke kanal atau orang ini", + "notifyHelp": "Opsional. ID kanal, atau user: diikuti ID pengguna Anda untuk menerimanya sebagai pesan langsung.", + "test": "Uji", + "testOk": "Terhubung ke {{bot}}.", + "checkFailed": "Tersimpan. Pemeriksaan token gagal: {{reason}}", + "senders": "Pengirim", + "noSenders": "Belum ada yang menulis ke bot ini.", + "allowButton": "Izinkan", + "ignored": "Diabaikan", + "accepted": "Diterima", + "save": "Simpan", + "cancel": "Batal", + "editButton": "Ubah", + "delete": "Hapus", + "empty": "Belum ada bot Discord.", + "agentOf": "Agen: {{name}}", + "accepts": "Menerima: {{who}}", + "modes": { + "anyone": "siapa saja", + "nobody": "belum ada", + "listed_one": "1 entri terdaftar", + "listed_other": "{{count}} entri terdaftar" + }, + "on": "Aktif", + "onFor": "{{name}} aktif", + "inactive": "Tidak berjalan: {{reason}}.", + "confirmDelete": { + "title": "Hapus {{name}}?", + "message": "Bot berhenti menjawab, dan token yang tersimpan bersamanya dihapus. Untuk menyiapkannya lagi, Anda harus memasukkan token lagi.", + "confirm": "Hapus" + }, + "setup": { + "title": "Cara membuat bot Discord", + "step1": "Di discord.com/developers/applications pilih New Application dan beri nama.", + "step2": "Di halaman Bot pilih Reset Token, salin token sekali saja, lalu tempel ke kolom Token bot di sini. Jaga kerahasiaannya.", + "step3": "Di halaman yang sama, di bawah Privileged Gateway Intents, aktifkan Message Content Intent. Tanpa itu bot tidak dapat membaca apa yang ditulis orang di server. Biarkan dua intent lainnya nonaktif.", + "step4": "Opsional: matikan Public Bot, agar hanya Anda yang dapat menambahkan bot ke server.", + "step5": "Ketik Application ID (General Information) di bawah, lalu buka tautan yang dibuatnya. Pilih server, dan biarkan izin apa adanya.", + "step6": "Di server, pastikan peran bot dapat melihat kanal tempat ia harus menjawab. Lalu daftarkan ID kanal di sini, dan sebut bot untuk mencobanya.", + "appId": "Application ID", + "appIdHelp": "Angka dari General Information pada aplikasi Anda. Ini tidak rahasia. Hanya dipakai untuk membuat tautan; tidak ada yang disimpan.", + "appIdBad": "Application ID adalah angka 17 sampai 20 digit.", + "invite": "Tautan untuk menambahkan bot ke server", + "copy": "Salin tautan", + "copied": "Tersalin", + "permissions": "Tautan meminta izin berikut (angka {{number}}):", + "perm": { + "viewChannels": "View Channels: untuk melihat tempat ia disebut", + "sendMessages": "Send Messages: untuk menjawab", + "sendInThreads": "Send Messages in Threads: untuk menjawab di thread", + "attachFiles": "Attach Files: untuk mengirim file yang dibuat agen", + "readHistory": "Read Message History: untuk membaca thread dan file dari sebuah pesan" + }, + "never": "Jangan pernah memberi bot Administrator, Manage Messages, atau Mention Everyone. Bot tidak memerlukan satu pun." + }, + "back": "Kembali ke konektor", + "answer": { + "reply": { + "off": "Hanya jawabannya (Discord tidak punya indikator mengetik di sini)" + }, + "replyHelp": "Progres dan Langsung menampilkan satu pesan proses, lalu jawaban menggantikannya. Pengeditan dibatasi oleh Discord, jadi Langsung diperbarui sekitar sekali per detik. Jawaban yang panjang dilanjutkan di pesan baru sepanjang paling banyak 2000 karakter.", + "conversationHelp": "Kirim !new di Discord untuk memulai chat baru. Pesan yang diawali garis miring bisa saja dianggap Discord sebagai salah satu perintahnya sendiri." + } +} diff --git a/addons/discord/ui/src/i18n/discord.it.json b/addons/discord/ui/src/i18n/discord.it.json new file mode 100644 index 0000000000..c28a9f3805 --- /dev/null +++ b/addons/discord/ui/src/i18n/discord.it.json @@ -0,0 +1,87 @@ +{ + "title": "Discord", + "intro": "Collega un bot Discord a uno dei tuoi agenti salvati. Le persone parlano con l'agente in un messaggio diretto con il bot, oppure menzionandolo in un canale o in un thread. Ogni persona, e ogni thread, ha una chat a sé.", + "new": "Nuovo bot Discord", + "edit": "Modifica il bot Discord", + "name": "Nome", + "token": "Token del bot", + "tokenHelp": "Dalla pagina Bot della tua applicazione nel portale sviluppatori di Discord (Reset Token). Viene cifrato e non viene più mostrato.", + "tokenSaved": "Salvato. Scrivi per sostituirlo.", + "tokenSpace": "Il token non contiene spazi. Incolla solo il token, senza la parola Bot.", + "tokenShape": "È troppo corto per essere il token di un bot.", + "agent": "Agente", + "agentNone": "Scegli un agente", + "agentNoModel": "{{name}} (serve un modello)", + "who": "Chi può usarlo", + "onlyListed": "Solo le persone e i canali che elenco", + "anyone": "Chiunque possa scrivere al bot, o menzionarlo in un canale che può leggere", + "anyoneWarning": "Pensato per canali pubblici e per agenti che si limitano a leggere. Chiunque raggiunga il bot può parlare con questo agente: con un messaggio diretto se condivide un server con il bot, e in ogni canale dove il bot può leggere e lo menziona. Chi non è in un elenco non ha alcun permesso proprio: l'agente non può salvare in memoria, proporre trigger né ricevere i suoi file, e le azioni sensibili restano chiuse. Gli altri strumenti dell'agente funzionano comunque per tutti. Ciò che le persone scrivono in un canale pubblico è testo non attendibile e può cercare di manovrare l'agente (prompt injection). Dai all'agente per questa connessione solo strumenti di sola lettura, e nessuno che invii messaggi, spenda denaro o modifichi file. Ogni persona ha una chat a sé, e il numero di messaggi che una persona può inviare in un minuto è limitato.", + "anyoneConfirm": "Ho capito che chiunque può usare questo agente e che i suoi strumenti dovrebbero essere di sola lettura", + "allowIds": "Persone e canali consentiti", + "allowIdsHint": "Un ID utente o un ID canale (numeri)", + "rules": "Le persone in elenco possono scrivere in un messaggio diretto e menzionare il bot in qualsiasi canale. Un canale in elenco permette a tutti i suoi membri di menzionare il bot lì, ma non dà loro alcun permesso proprio. In un server il bot risponde solo quando viene menzionato o gli si risponde, o dentro un thread in cui è stato menzionato. Non risponde mai a bot o webhook, e i suoi messaggi non avvisano mai nessuno. Usa gli ID dei canali: l'ID di un server (guild) non corrisponde a nulla. Solo ID, niente nomi: in Discord attiva la Modalità sviluppatore (Impostazioni, Avanzate), poi fai clic destro su una persona o un canale e scegli Copia ID.", + "badIds": "Questi non sono ID Discord: {{list}}. Usa il numero di Copia ID (da 17 a 20 cifre). I nomi non sono accettati.", + "notifyChat": "Invia le mie notifiche a questo canale o persona", + "notifyHelp": "Facoltativo. Un ID di canale, oppure user: seguito dal tuo ID utente per riceverle come messaggi diretti.", + "test": "Prova", + "testOk": "Connesso a {{bot}}.", + "checkFailed": "Salvato. La verifica del token non è riuscita: {{reason}}", + "senders": "Mittenti", + "noSenders": "Nessuno ha ancora scritto a questo bot.", + "allowButton": "Consenti", + "ignored": "Ignorato", + "accepted": "Accettato", + "save": "Salva", + "cancel": "Annulla", + "editButton": "Modifica", + "delete": "Elimina", + "empty": "Ancora nessun bot Discord.", + "agentOf": "Agente: {{name}}", + "accepts": "Accetta: {{who}}", + "modes": { + "anyone": "chiunque", + "nobody": "nessuno per ora", + "listed_one": "1 voce in elenco", + "listed_other": "{{count}} voci in elenco" + }, + "on": "Attivo", + "onFor": "{{name}} attivo", + "inactive": "Non in esecuzione: {{reason}}.", + "confirmDelete": { + "title": "Eliminare {{name}}?", + "message": "Il bot smette di rispondere e il token salvato con lui viene eliminato. Per configurarlo di nuovo dovrai inserire di nuovo il token.", + "confirm": "Elimina" + }, + "setup": { + "title": "Come creare il bot Discord", + "step1": "Su discord.com/developers/applications scegli New Application e dagli un nome.", + "step2": "Nella pagina Bot scegli Reset Token, copia il token una sola volta e incollalo qui nel campo Token del bot. Tienilo segreto.", + "step3": "Nella stessa pagina, in Privileged Gateway Intents, attiva Message Content Intent. Senza, il bot non può leggere ciò che le persone scrivono in un server. Lascia disattivati gli altri due intent.", + "step4": "Facoltativo: disattiva Public Bot, così solo tu puoi aggiungere il bot a un server.", + "step5": "Scrivi qui sotto l'Application ID (General Information) e apri il link che ne risulta. Scegli il server e lascia i permessi come sono.", + "step6": "Nel server, controlla che il ruolo del bot veda i canali in cui deve rispondere. Poi elenca qui gli ID di quei canali e menziona il bot per provarlo.", + "appId": "Application ID", + "appIdHelp": "Il numero in General Information della tua applicazione. Non è segreto. Serve solo a costruire il link; non viene salvato nulla.", + "appIdBad": "L'Application ID è un numero di 17-20 cifre.", + "invite": "Link per aggiungere il bot a un server", + "copy": "Copia il link", + "copied": "Copiato", + "permissions": "Il link richiede questi permessi (il numero {{number}}):", + "perm": { + "viewChannels": "View Channels: per vedere dove viene menzionato", + "sendMessages": "Send Messages: per rispondere", + "sendInThreads": "Send Messages in Threads: per rispondere nei thread", + "attachFiles": "Attach Files: per inviare i file creati dall'agente", + "readHistory": "Read Message History: per leggere un thread e i file di un messaggio" + }, + "never": "Non dare mai al bot Administrator, Manage Messages o Mention Everyone. Non gli serve nessuno dei tre." + }, + "back": "Torna ai connettori", + "answer": { + "reply": { + "off": "Solo la risposta (qui Discord non ha l'indicatore «sta scrivendo»)" + }, + "replyHelp": "Avanzamento e In diretta mostrano un solo messaggio di lavoro, che la risposta poi sostituisce. Le modifiche sono limitate da Discord, quindi In diretta si aggiorna circa una volta al secondo. Una risposta lunga continua in nuovi messaggi di al massimo 2000 caratteri.", + "conversationHelp": "Invia !new in Discord per iniziare una nuova chat. Un messaggio che inizia con una barra può essere preso da Discord per un suo comando." + } +} diff --git a/addons/discord/ui/src/i18n/discord.ko.json b/addons/discord/ui/src/i18n/discord.ko.json new file mode 100644 index 0000000000..48383b014e --- /dev/null +++ b/addons/discord/ui/src/i18n/discord.ko.json @@ -0,0 +1,87 @@ +{ + "title": "Discord", + "intro": "Discord 봇을 저장된 에이전트 중 하나에 연결하세요. 사람들은 봇과의 다이렉트 메시지로 대화하거나 채널 또는 스레드에서 봇을 멘션해 에이전트와 대화합니다. 사람마다, 스레드마다 별개의 채팅이 생깁니다.", + "new": "새 Discord 봇", + "edit": "Discord 봇 편집", + "name": "이름", + "token": "봇 토큰", + "tokenHelp": "Discord 개발자 포털에서 애플리케이션의 Bot 페이지(Reset Token)에서 받습니다. 봉인되어 보관되며 다시 표시되지 않습니다.", + "tokenSaved": "저장됨. 교체하려면 새 토큰을 입력하세요.", + "tokenSpace": "토큰에는 공백이 없습니다. Bot이라는 단어 없이 토큰만 붙여 넣으세요.", + "tokenShape": "봇 토큰으로 보기에는 너무 짧습니다.", + "agent": "에이전트", + "agentNone": "에이전트 고르기", + "agentNoModel": "{{name}}(모델 필요)", + "who": "사용할 수 있는 사람", + "onlyListed": "내가 나열한 사람과 채널만", + "anyone": "봇에 메시지를 보내거나, 봇이 읽을 수 있는 채널에서 봇을 멘션할 수 있는 모든 사람", + "anyoneWarning": "공개 채널과 읽기만 하는 에이전트용입니다. 봇에 닿을 수 있는 사람은 누구나 이 에이전트와 대화할 수 있습니다. 봇과 같은 서버에 있으면 다이렉트 메시지로, 봇이 읽을 수 있는 모든 채널에서는 멘션해서 대화합니다. 목록에 없는 사람은 자신만의 권한이 없습니다. 에이전트는 그들을 위해 메모리를 저장하거나, 트리거를 제안하거나, 파일을 받을 수 없고, 민감한 동작은 계속 막혀 있습니다. 에이전트의 다른 도구는 모두에게 그대로 작동합니다. 공개 채널에 사람들이 쓰는 내용은 신뢰할 수 없는 텍스트이며 에이전트를 조종하려 할 수 있습니다(프롬프트 인젝션). 이 연결에는 읽기 전용 도구만 주고, 메시지를 보내거나 비용을 쓰거나 파일을 바꾸는 도구는 주지 마세요. 사람마다 자신만의 채팅이 생기며, 한 사람이 1분에 보낼 수 있는 메시지 수는 제한됩니다.", + "anyoneConfirm": "누구나 이 에이전트를 사용할 수 있으며 도구는 읽기 전용이어야 한다는 것을 이해했습니다", + "allowIds": "허용된 사람과 채널", + "allowIdsHint": "사용자 ID 또는 채널 ID(숫자)", + "rules": "나열된 사람은 다이렉트 메시지로 쓰거나 아무 채널에서나 봇을 멘션할 수 있습니다. 나열된 채널은 그 채널의 모든 사람이 거기서 봇을 멘션할 수 있게 하지만 자신만의 권한은 없습니다. 서버에서 봇은 멘션되거나 답글을 받았을 때, 또는 멘션된 스레드 안에서만 답변합니다. 봇이나 웹훅에는 답변하지 않으며 메시지가 누구에게도 알림을 보내지 않습니다. 채널 ID를 사용하세요. 서버(길드) ID는 아무것도 일치하지 않습니다. 이름은 안 되고 ID만 가능합니다. Discord에서 개발자 모드(설정, 고급)를 켠 다음 사람이나 채널을 마우스 오른쪽 버튼으로 눌러 ID 복사를 고르세요.", + "badIds": "Discord ID가 아닙니다: {{list}}. ID 복사로 얻은 숫자(17~20자리)를 사용하세요. 이름은 사용할 수 없습니다.", + "notifyChat": "내 알림을 이 채널 또는 사람에게 보내기", + "notifyHelp": "선택 사항입니다. 채널 ID 또는 user: 뒤에 내 사용자 ID를 붙이면 다이렉트 메시지로 받습니다.", + "test": "테스트", + "testOk": "{{bot}}에 연결되었습니다.", + "checkFailed": "저장했습니다. 토큰 확인에 실패했습니다: {{reason}}", + "senders": "보낸 사람", + "noSenders": "아직 이 봇에 메시지를 보낸 사람이 없습니다.", + "allowButton": "허용", + "ignored": "무시됨", + "accepted": "수락됨", + "save": "저장", + "cancel": "취소", + "editButton": "편집", + "delete": "삭제", + "empty": "아직 Discord 봇이 없습니다.", + "agentOf": "에이전트: {{name}}", + "accepts": "수락: {{who}}", + "modes": { + "anyone": "모든 사람", + "nobody": "아직 없음", + "listed_one": "나열된 항목 1개", + "listed_other": "나열된 항목 {{count}}개" + }, + "on": "켜짐", + "onFor": "{{name}} 켜짐", + "inactive": "실행 중이 아님: {{reason}}.", + "confirmDelete": { + "title": "{{name}}을(를) 삭제할까요?", + "message": "봇이 답변을 멈추고 함께 저장된 토큰이 삭제됩니다. 다시 설정하려면 토큰을 다시 입력해야 합니다.", + "confirm": "삭제" + }, + "setup": { + "title": "Discord 봇 만드는 방법", + "step1": "discord.com/developers/applications에서 New Application을 고르고 이름을 지정하세요.", + "step2": "Bot 페이지에서 Reset Token을 고르고 토큰을 한 번만 복사해 여기 봇 토큰 필드에 붙여 넣으세요. 비밀로 유지하세요.", + "step3": "같은 페이지의 Privileged Gateway Intents에서 Message Content Intent를 켜세요. 켜지 않으면 봇이 서버에서 사람들이 쓰는 내용을 읽을 수 없습니다. 나머지 두 intent는 꺼 두세요.", + "step4": "선택 사항: Public Bot을 꺼서 나만 봇을 서버에 추가할 수 있게 하세요.", + "step5": "아래에 Application ID(General Information)를 입력하고 만들어진 링크를 여세요. 서버를 고르고 권한은 그대로 두세요.", + "step6": "서버에서 봇의 역할이 답변해야 할 채널을 볼 수 있는지 확인하세요. 그런 다음 여기에 채널 ID를 나열하고 봇을 멘션해 시험해 보세요.", + "appId": "Application ID", + "appIdHelp": "애플리케이션의 General Information에 있는 숫자입니다. 비밀이 아닙니다. 링크를 만드는 데만 쓰이며 저장되지 않습니다.", + "appIdBad": "Application ID는 17~20자리 숫자입니다.", + "invite": "봇을 서버에 추가하는 링크", + "copy": "링크 복사", + "copied": "복사됨", + "permissions": "이 링크는 다음 권한을 요청합니다(번호 {{number}}):", + "perm": { + "viewChannels": "View Channels: 멘션된 곳을 보기 위해", + "sendMessages": "Send Messages: 답변하기 위해", + "sendInThreads": "Send Messages in Threads: 스레드에서 답변하기 위해", + "attachFiles": "Attach Files: 에이전트가 만든 파일을 보내기 위해", + "readHistory": "Read Message History: 스레드와 메시지의 파일을 읽기 위해" + }, + "never": "봇에 Administrator, Manage Messages, Mention Everyone 권한은 절대 주지 마세요. 어느 것도 필요하지 않습니다." + }, + "back": "커넥터로 돌아가기", + "answer": { + "reply": { + "off": "답변만(여기서 Discord는 입력 중 표시가 없습니다)" + }, + "replyHelp": "진행 상황과 실시간은 작업 중 메시지 하나를 보여 주고, 답변이 나오면 그 메시지를 대체합니다. Discord가 편집 횟수를 제한하므로 실시간은 약 1초에 한 번 갱신됩니다. 긴 답변은 최대 2000자의 새 메시지로 이어집니다.", + "conversationHelp": "Discord에서 !new를 보내면 새 채팅이 시작됩니다. 슬래시로 시작하는 메시지는 Discord가 자체 명령으로 처리할 수 있습니다." + } +} diff --git a/addons/discord/ui/src/i18n/discord.pt-BR.json b/addons/discord/ui/src/i18n/discord.pt-BR.json new file mode 100644 index 0000000000..33336e5c3a --- /dev/null +++ b/addons/discord/ui/src/i18n/discord.pt-BR.json @@ -0,0 +1,87 @@ +{ + "title": "Discord", + "intro": "Vincule um bot do Discord a um dos seus agentes salvos. As pessoas conversam com o agente em uma mensagem direta com o bot, ou mencionando-o em um canal ou thread. Cada pessoa, e cada thread, tem um chat próprio.", + "new": "Novo bot do Discord", + "edit": "Editar bot do Discord", + "name": "Nome", + "token": "Token do bot", + "tokenHelp": "Vem da página Bot da sua aplicação no portal do desenvolvedor do Discord (Reset Token). É guardado com segurança e nunca mais é mostrado.", + "tokenSaved": "Salvo. Digite um novo token para substituí-lo.", + "tokenSpace": "O token não tem espaços. Cole só o token, sem a palavra Bot.", + "tokenShape": "Isto é curto demais para um token de bot.", + "agent": "Agente", + "agentNone": "Escolha um agente", + "agentNoModel": "{{name}} (precisa de um modelo)", + "who": "Quem pode usar", + "onlyListed": "Só as pessoas e os canais que eu listar", + "anyone": "Qualquer pessoa que possa enviar mensagem ao bot, ou mencioná-lo em um canal que ele consiga ler", + "anyoneWarning": "Isto serve para canais públicos e para agentes que só leem. Qualquer pessoa que alcance o bot pode conversar com este agente: por mensagem direta, se compartilhar um servidor com o bot, e em todo canal onde o bot pode ler e ela o menciona. Quem não está em uma lista não tem permissão própria: o agente não pode salvar memória, propor gatilhos nem receber arquivos por essa pessoa, e as ações sensíveis continuam fechadas. As outras ferramentas do agente continuam funcionando para todos. O que as pessoas escrevem em um canal público é texto não confiável e pode tentar conduzir o agente (prompt injection). Dê ao agente ferramentas somente de leitura para esta conexão, e nenhuma que envie mensagens, gaste dinheiro ou altere arquivos. Cada pessoa tem um chat próprio, e o número de mensagens que uma pessoa pode enviar por minuto é limitado.", + "anyoneConfirm": "Entendi que qualquer pessoa pode usar este agente e que as ferramentas dele devem ser somente de leitura", + "allowIds": "Pessoas e canais permitidos", + "allowIdsHint": "Um ID de usuário ou de canal (números)", + "rules": "As pessoas da lista podem escrever em mensagem direta e mencionar o bot em qualquer canal. Um canal da lista deixa todos nele mencionarem o bot ali, mas sem permissão própria. Em um servidor, o bot só responde quando é mencionado ou respondido, ou dentro de uma thread onde foi mencionado. Ele nunca responde a bots nem a webhooks, e as mensagens dele nunca notificam ninguém. Use IDs de canal: o ID de um servidor (guild) não corresponde a nada. Só IDs, sem nomes: no Discord ative o Modo Desenvolvedor (Configurações, Avançado), depois clique com o botão direito em uma pessoa ou canal e escolha Copiar ID.", + "badIds": "Estes não são IDs do Discord: {{list}}. Use o número de Copiar ID (17 a 20 dígitos). Nomes não são aceitos.", + "notifyChat": "Enviar minhas notificações para este canal ou pessoa", + "notifyHelp": "Opcional. Um ID de canal, ou user: seguido do seu ID de usuário para recebê-las em mensagem direta.", + "test": "Testar", + "testOk": "Conectado a {{bot}}.", + "checkFailed": "Salvo. A verificação do token falhou: {{reason}}", + "senders": "Remetentes", + "noSenders": "Ninguém escreveu para este bot ainda.", + "allowButton": "Permitir", + "ignored": "Ignorado", + "accepted": "Aceito", + "save": "Salvar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Excluir", + "empty": "Ainda não há bots do Discord.", + "agentOf": "Agente: {{name}}", + "accepts": "Aceita: {{who}}", + "modes": { + "anyone": "qualquer pessoa", + "nobody": "ninguém ainda", + "listed_one": "1 entrada na lista", + "listed_other": "{{count}} entradas na lista" + }, + "on": "Ligado", + "onFor": "{{name}} ligado", + "inactive": "Não está rodando: {{reason}}.", + "confirmDelete": { + "title": "Excluir {{name}}?", + "message": "O bot deixa de responder, e o token salvo com ele é excluído. Para configurá-lo de novo, você precisará informar o token outra vez.", + "confirm": "Excluir" + }, + "setup": { + "title": "Como criar o bot do Discord", + "step1": "Em discord.com/developers/applications escolha New Application e dê um nome a ela.", + "step2": "Na página Bot escolha Reset Token, copie o token uma única vez e cole no campo Token do bot aqui. Mantenha-o em segredo.", + "step3": "Na mesma página, em Privileged Gateway Intents, ative Message Content Intent. Sem isso o bot não consegue ler o que as pessoas escrevem em um servidor. Deixe as outras duas intents desligadas.", + "step4": "Opcional: desligue Public Bot, para que só você possa adicionar o bot a um servidor.", + "step5": "Digite abaixo o Application ID (General Information) e abra o link que ele monta. Escolha o servidor e mantenha as permissões como estão.", + "step6": "No servidor, confira se o cargo do bot enxerga os canais em que ele deve responder. Depois liste os IDs dos canais aqui e mencione o bot para testar.", + "appId": "Application ID", + "appIdHelp": "O número de General Information da sua aplicação. Não é secreto. Serve só para montar o link; nada é salvo.", + "appIdBad": "O Application ID é um número de 17 a 20 dígitos.", + "invite": "Link que adiciona o bot a um servidor", + "copy": "Copiar o link", + "copied": "Copiado", + "permissions": "O link pede estas permissões (o número {{number}}):", + "perm": { + "viewChannels": "View Channels: para ver onde é mencionado", + "sendMessages": "Send Messages: para responder", + "sendInThreads": "Send Messages in Threads: para responder em threads", + "attachFiles": "Attach Files: para enviar os arquivos que o agente cria", + "readHistory": "Read Message History: para ler uma thread e os arquivos de uma mensagem" + }, + "never": "Nunca dê ao bot Administrator, Manage Messages nem Mention Everyone. Ele não precisa de nenhuma delas." + }, + "back": "Voltar aos conectores", + "answer": { + "reply": { + "off": "Só a resposta (o Discord não tem indicador de digitação aqui)" + }, + "replyHelp": "Progresso e Ao vivo mostram uma única mensagem de trabalho, que a resposta substitui. As edições são limitadas pelo Discord, então Ao vivo atualiza cerca de uma vez por segundo. Uma resposta longa continua em novas mensagens de no máximo 2000 caracteres.", + "conversationHelp": "Envie !new no Discord para iniciar um novo chat. Uma mensagem que começa com barra pode ser tomada pelo Discord como um comando dele." + } +} diff --git a/addons/discord/ui/src/i18n/discord.zh-CN.json b/addons/discord/ui/src/i18n/discord.zh-CN.json new file mode 100644 index 0000000000..d2e54e917b --- /dev/null +++ b/addons/discord/ui/src/i18n/discord.zh-CN.json @@ -0,0 +1,87 @@ +{ + "title": "Discord", + "intro": "把 Discord 机器人绑定到你已保存的某个智能体。人们可以在与机器人的私信中与智能体交谈,也可以在频道或讨论串中提及它。每个人和每个讨论串都有自己的对话。", + "new": "新建 Discord 机器人", + "edit": "编辑 Discord 机器人", + "name": "名称", + "token": "机器人 token", + "tokenHelp": "来自 Discord 开发者门户中你的应用的 Bot 页面(Reset Token)。它会被加密保存,之后不再显示。", + "tokenSaved": "已保存。输入新的 token 可替换它。", + "tokenSpace": "token 不含空格。请只粘贴 token,不要带 Bot 这个词。", + "tokenShape": "对于机器人 token 来说,这太短了。", + "agent": "智能体", + "agentNone": "选择智能体", + "agentNoModel": "{{name}}(需要模型)", + "who": "谁可以使用", + "onlyListed": "仅我列出的人员和频道", + "anyone": "任何能给机器人发消息的人,或在它能读取的频道中提及它的人", + "anyoneWarning": "这适用于公开频道和只读取的智能体。任何能联系到机器人的人都可以与此智能体交谈:与机器人共处同一服务器的人可以私信,在机器人能读取的每个频道中也可以提及它。不在名单上的人没有自己的权限:智能体不会替他们保存记忆、提议触发器或接收文件,敏感操作保持关闭。智能体的其他工具对所有人仍然可用。人们在公开频道中写的内容是不可信的文本,可能试图操控智能体(提示词注入)。请只给这个连接配备只读工具,不要配备会发送消息、花钱或修改文件的工具。每个人有自己的对话,每人每分钟可发送的消息数有限制。", + "anyoneConfirm": "我了解任何人都可以使用此智能体,其工具应当是只读的", + "allowIds": "允许的人员和频道", + "allowIdsHint": "用户 ID 或频道 ID(数字)", + "rules": "名单中的人可以私信,也可以在任何频道中提及机器人。名单中的频道允许其中所有人在那里提及机器人,但他们没有自己的权限。在服务器中,机器人只在被提及、被回复,或位于它被提及过的讨论串内时才回答。它从不回应机器人或 webhook,它的消息也从不 @ 任何人。请使用频道 ID:服务器(guild)ID 不会匹配任何内容。只能用 ID,不能用名称:在 Discord 中开启开发者模式(设置,高级),然后右键点击人员或频道并选择 Copy ID。", + "badIds": "这些不是 Discord ID:{{list}}。请使用 Copy ID 得到的数字(17 到 20 位)。不接受名称。", + "notifyChat": "把我的通知发送到此频道或用户", + "notifyHelp": "可选。填频道 ID,或填 user: 加上你的用户 ID,以私信形式接收。", + "test": "测试", + "testOk": "已连接到 {{bot}}。", + "checkFailed": "已保存。token 检查失败:{{reason}}", + "senders": "发送者", + "noSenders": "还没有人给此机器人发过消息。", + "allowButton": "允许", + "ignored": "已忽略", + "accepted": "已接受", + "save": "保存", + "cancel": "取消", + "editButton": "编辑", + "delete": "删除", + "empty": "还没有 Discord 机器人。", + "agentOf": "智能体:{{name}}", + "accepts": "接受:{{who}}", + "modes": { + "anyone": "任何人", + "nobody": "暂无", + "listed_one": "1 个已列出的条目", + "listed_other": "{{count}} 个已列出的条目" + }, + "on": "已开启", + "onFor": "{{name}} 已开启", + "inactive": "未运行:{{reason}}。", + "confirmDelete": { + "title": "删除 {{name}}?", + "message": "机器人将停止回答,随之保存的 token 也会被删除。若要重新设置,你必须再次输入 token。", + "confirm": "删除" + }, + "setup": { + "title": "如何创建 Discord 机器人", + "step1": "在 discord.com/developers/applications 选择 New Application 并为它命名。", + "step2": "在 Bot 页面选择 Reset Token,复制一次 token,并粘贴到这里的“机器人 token”字段。请保密。", + "step3": "在同一页面的 Privileged Gateway Intents 下,开启 Message Content Intent。没有它,机器人无法读取人们在服务器中写的内容。另外两个 intent 保持关闭。", + "step4": "可选:关闭 Public Bot,这样只有你能把机器人添加到服务器。", + "step5": "在下面输入 Application ID(General Information),然后打开它生成的链接。选择服务器,并保持权限不变。", + "step6": "在服务器中,检查机器人的角色能看到它应该回答的频道。然后在这里列出这些频道 ID,并提及机器人来试用。", + "appId": "Application ID", + "appIdHelp": "你的应用 General Information 中的数字。它不是机密,只用来生成链接,不会保存任何内容。", + "appIdBad": "Application ID 是 17 到 20 位的数字。", + "invite": "把机器人添加到服务器的链接", + "copy": "复制链接", + "copied": "已复制", + "permissions": "该链接请求以下权限(数值 {{number}}):", + "perm": { + "viewChannels": "View Channels:查看它在哪里被提及", + "sendMessages": "Send Messages:用于回答", + "sendInThreads": "Send Messages in Threads:用于在讨论串中回答", + "attachFiles": "Attach Files:用于发送智能体生成的文件", + "readHistory": "Read Message History:用于读取讨论串和消息中的文件" + }, + "never": "绝不要给机器人 Administrator、Manage Messages 或 Mention Everyone 权限。它不需要其中任何一项。" + }, + "back": "返回连接器", + "answer": { + "reply": { + "off": "只显示回答(这里 Discord 没有正在输入的提示)" + }, + "replyHelp": "“进度”和“实时”会显示一条处理中的消息,回答出来后替换它。Discord 限制编辑频率,因此“实时”大约每秒更新一次。较长的回答会继续发成新消息,每条最多 2000 个字符。", + "conversationHelp": "在 Discord 中发送 !new 即可开始新的对话。以斜杠开头的消息可能被 Discord 当作它自己的命令。" + } +} diff --git a/addons/github/ui/src/addon.jsx b/addons/github/ui/src/addon.jsx index 3c772c3be7..cd92e29350 100644 --- a/addons/github/ui/src/addon.jsx +++ b/addons/github/ui/src/addon.jsx @@ -5,15 +5,25 @@ import { getSdk, } from '../../../internal/host-compat/src/runtime.js' import { registerLocales } from '../../../internal/host-compat/src/i18n.js' import Icon from '../../../internal/host-compat/src/components/Icon' import ConnectorPage from '../../../internal/connector-ui/src/ConnectorPage' +import { pageLocale } from '../../../internal/connector-ui/src/connector' import { connectorsApi } from '../../../internal/connector-ui/src/api' import { Field, Notice, RadioCards, TextInput } from '../../../internal/connector-ui/src/controls' import en from './i18n/github.en.json' +import it from './i18n/github.it.json' +import es from './i18n/github.es.json' +import de from './i18n/github.de.json' +import fr from './i18n/github.fr.json' +import zh from './i18n/github.zh-CN.json' +import id from './i18n/github.id.json' +import ko from './i18n/github.ko.json' +import pt from './i18n/github.pt-BR.json' import ProposalCard from './ProposalCard' import { checkFailure, checkItem, extraBody, extraForm, extraValid, followName, githubSecret, parseRepos, PERMISSIONS, reposIssue, tokenProblem, WRITE_MODES, writeModeOf } from './repos' import { finishTest, resultOf, startTest, subscribe } from './results' import './github.css' -registerLocales({ github: { en } }) +// The words of the page, in every language of the web UI; a key a language lacks reads in English. +registerLocales({ github: { en, it, es, de, fr, 'zh-CN': zh, id, ko, 'pt-BR': pt } }) const TOKEN_URL = 'https://github.com/settings/personal-access-tokens/new' @@ -139,7 +149,7 @@ function Checks({ c, t }) { ))} - {r.at > 0 &&

{t('check.at', { time: new Date(r.at).toLocaleTimeString() })}

} + {r.at > 0 &&

{t('check.at', { time: new Date(r.at).toLocaleTimeString(pageLocale()) })}

} ) } diff --git a/addons/github/ui/src/i18n/github.de.json b/addons/github/ui/src/i18n/github.de.json new file mode 100644 index 0000000000..1e7a93a922 --- /dev/null +++ b/addons/github/ui/src/i18n/github.de.json @@ -0,0 +1,131 @@ +{ + "title": "GitHub", + "intro": "Lass deine Agenten den Code, die Issues und die Pull Requests der Repositories lesen, die du aufführst. Wenn du es erlaubst, können sie dort auch kommentieren und Issues eröffnen.", + "limits": "Diese Verbindung beantwortet @Erwähnungen auf GitHub noch nicht. Ein Agent arbeitet auf GitHub nur, wenn du ihn in einem Chat darum bittest.", + "new": "Neue GitHub-Verbindung", + "edit": "GitHub-Verbindung bearbeiten", + "name": "Name", + "token": "Fine-grained Access Token", + "tokenHelp": "Beginnt mit github_pat_. Es wird versiegelt gespeichert und nie wieder angezeigt.", + "tokenLink": "Token auf GitHub erstellen", + "tokenSaved": "Gespeichert. Gib ein Token ein, um es zu ersetzen.", + "tokenShape": "Nutze ein Fine-grained Token: Es beginnt mit github_pat_.", + "tokenSpace": "Das Token enthält keine Leerzeichen. Kopiere es erneut ohne die Leerzeichen davor und danach.", + "repos": "Repositories, die der Agent nutzen darf", + "reposHelp": "Eines pro Zeile, als owner/name. {{count}} aufgeführt. Jedes andere Repository wird abgelehnt.", + "reposShape": "„{{entry}}“ ist nicht als owner/name geschrieben, zum Beispiel mudler/LocalAI.", + "reposMany": "Höchstens 50 Repositories.", + "agent": "Agent", + "agentNone": "Agent wählen", + "agentCredentialsOnly": "Kein Agent (nur Zugangsdaten)", + "agentOptionalHint": "Optional. Ohne Agent hält diese Verbindung nur das Token, die Repository-Liste und den Schreibmodus für deine Chats. Sie beantwortet keine Nachrichten und hört auf nichts.", + "credentialsOnly": "Nur Zugangsdaten", + "agentNoModel": "{{name}} (braucht ein Modell)", + "test": "Test", + "testOk": "Verbunden als {{bot}}.", + "checkFailed": "Gespeichert. Die Prüfung des Tokens ist fehlgeschlagen: {{reason}}", + "checkSeveral": "Die Repository-Prüfung läuft, wenn du genau eine GitHub-Verbindung hast, denn eine Prüfung von dieser Seite aus kann nicht erkennen, welche du meinst. Aktiviere die Verbindungen in einem Chat im Menü Werkzeuge.", + "check": { + "title": "Prüfung von {{name}}", + "running": "Die Repositories werden geprüft...", + "at": "Geprüft um {{time}}", + "unreachable": "{{repo}}: Dieses Token erreicht es nicht. Füge es auf GitHub zu den Repositories des Tokens hinzu.", + "readOnly": "{{repo}}: erreichbar, nur lesend.", + "readWrite": "{{repo}}: erreichbar, und das Token kann schreiben.", + "refused": "{{repo}}: GitHub hat dieses Token abgelehnt. Prüfe, dass es nicht abgelaufen ist und dieses Repository abdeckt.", + "failed": "{{repo}}: Die Prüfung wurde nicht abgeschlossen. Versuche es gleich noch einmal." + }, + "save": "Speichern", + "cancel": "Abbrechen", + "editButton": "Bearbeiten", + "delete": "Löschen", + "empty": "Noch keine GitHub-Verbindungen.", + "noRepos": "Keine Repositories aufgeführt: Der Agent kann keines lesen.", + "reposOf": "{{count}} Repositories: {{list}}", + "agentOf": "Agent: {{name}}", + "inactive": "Läuft nicht: {{reason}}.", + "confirmDelete": { + "title": "{{name}} löschen?", + "message": "Der Agent erreicht diese Repositories nicht mehr, und das mit der Verbindung gespeicherte Token wird gelöscht. Um sie erneut einzurichten, musst du das Token noch einmal eingeben.", + "confirm": "Löschen" + }, + "setup": { + "title": "So erstellst du das Token", + "link": "Token auf GitHub erstellen", + "linkLabel": "Token auf GitHub erstellen (öffnet in einem neuen Tab)", + "step1": "Öffne GitHub mit der Schaltfläche oben. Gib dem Token einen Namen, zum Beispiel LocalAI, und setze ein Ablaufdatum. Neunzig Tage sind ein guter Anfang.", + "step2": "Wähle unter Repository access die Option Only select repositories und die Repositories, die du hier aufführst.", + "step3": "Setze unter Permissions, was die Tabelle für den gewünschten Modus nennt. Lass alles andere aus.", + "step4": "Erstelle das Token, kopiere es und füge es in das Token-Feld ein. GitHub zeigt es nur einmal an.", + "table": { + "caption": "Repository-Berechtigungen für das Token", + "permission": "Berechtigung", + "read": "Nur lesen", + "write": "Fragt zuerst oder handelt ohne Rückfrage", + "contents": "Contents", + "issues": "Issues", + "pulls": "Pull requests", + "metadata": "Metadata", + "levels": { + "read": "Lesen", + "readwrite": "Lesen und schreiben", + "auto": "Lesen (GitHub setzt es)" + } + } + }, + "writeMode": { + "label": "Was der Agent auf GitHub ändern darf", + "hint": "Kommentare, neue Issues, Labels und das Schließen von Issues. Er führt nie einen Merge aus, löscht nichts und pusht keinen Code.", + "off": "Nur lesen", + "propose": "Fragt dich zuerst", + "auto": "Handelt ohne Rückfrage", + "line": { + "off": "Er kann auf GitHub nichts ändern.", + "propose": "Jede Änderung wartet auf dein OK.", + "auto": "Deine Chats posten sofort." + }, + "note": { + "off": "Schreibzugriff ist aus: Diese Verbindung liest nur.", + "propose": "Jede Änderung wartet in deinen Freigaben, bis du sie bestätigst. Alles, was ein Agent schreibt, ist für alle öffentlich, die das Repository sehen können.", + "auto": "Ein Chat von dir postet die Änderung sofort, ohne Rückfrage. Alles, was der Agent schreibt, ist für alle öffentlich, die das Repository sehen können. Läufe ohne dich warten weiterhin auf deine Bestätigung." + }, + "row": { + "off": "Nur lesen", + "propose": "Fragt dich zuerst", + "auto": "Handelt ohne Rückfrage" + } + }, + "approval": { + "action": { + "comment-issue": "Issue #{{number}} kommentieren", + "comment-pr": "Pull Request #{{number}} kommentieren", + "create-issue": "Neues Issue eröffnen", + "edit-issue": "#{{number}} ändern", + "add-labels": "Labels zu #{{number}} hinzufügen", + "review-comment-pr": "Eine Zeile von Pull Request #{{number}} kommentieren" + }, + "where": "Wo", + "repo": "In {{repo}}", + "open": "#{{number}} auf GitHub öffnen", + "as": "Als", + "asOne": "Die Verbindung {{name}}", + "asFirst": "Die erste Verbindung, die {{repo}} aufführt", + "asUnknown": "Eine GitHub-Verbindung, die {{repo}} aufführt", + "text": "Text, der gepostet wird", + "title": "Titel", + "newTitle": "Neuer Titel", + "labels": "Labels", + "file": "Datei und Zeile", + "fileLine": "{{path}}, Zeile {{line}} ({{side}})", + "sideRight": "neue Datei", + "sideLeft": "alte Datei", + "state": "Änderung", + "close": "Schließen", + "closeReason": "Schließen als {{reason}}", + "reopen": "Wieder öffnen", + "completed": "erledigt", + "not_planned": "nicht geplant", + "public": "Öffentlich: Alle, die {{repo}} sehen können, sehen das.", + "cut": "Der hier gezeigte Text ist gekürzt. Der vollständige Text mit {{bytes}} Bytes wird gepostet." + } +} diff --git a/addons/github/ui/src/i18n/github.es.json b/addons/github/ui/src/i18n/github.es.json new file mode 100644 index 0000000000..e2cd3f7810 --- /dev/null +++ b/addons/github/ui/src/i18n/github.es.json @@ -0,0 +1,131 @@ +{ + "title": "GitHub", + "intro": "Deja que tus agentes lean el código, las issues y los pull requests de los repositorios que añadas. Si lo permites, también pueden comentar y abrir issues en ellos.", + "limits": "Esta conexión aún no responde a las @menciones en GitHub. Un agente trabaja en GitHub solo cuando se lo pides en un chat.", + "new": "Nueva conexión de GitHub", + "edit": "Editar conexión de GitHub", + "name": "Nombre", + "token": "Token de acceso de grano fino", + "tokenHelp": "Empieza por github_pat_. Se guarda cifrado y no se vuelve a mostrar.", + "tokenLink": "Crear un token en GitHub", + "tokenSaved": "Guardado. Escribe un token para reemplazarlo.", + "tokenShape": "Usa un token de grano fino: empieza por github_pat_.", + "tokenSpace": "El token no tiene espacios. Cópialo de nuevo sin los espacios alrededor.", + "repos": "Repositorios que el agente puede usar", + "reposHelp": "Uno por línea, con el formato propietario/nombre. {{count}} en la lista. Cualquier otro repositorio se rechaza.", + "reposShape": "«{{entry}}» no tiene el formato propietario/nombre, por ejemplo mudler/LocalAI.", + "reposMany": "Máximo 50 repositorios.", + "agent": "Agente", + "agentNone": "Elige un agente", + "agentCredentialsOnly": "Sin agente (solo credenciales)", + "agentOptionalHint": "Opcional. Sin agente, esta conexión solo guarda el token, la lista de repositorios y el modo de escritura para tus chats. No responde a ningún mensaje y no escucha nada.", + "credentialsOnly": "Solo credenciales", + "agentNoModel": "{{name}} (necesita un modelo)", + "test": "Probar", + "testOk": "Conectado como {{bot}}.", + "checkFailed": "Guardado. Falló la comprobación del token: {{reason}}", + "checkSeveral": "Las comprobaciones de repositorios se ejecutan cuando tienes una sola conexión de GitHub, porque una comprobación desde esta página no puede saber a cuál te refieres. En un chat, activa las conexiones en el menú Herramientas.", + "check": { + "title": "Comprobación de {{name}}", + "running": "Comprobando los repositorios...", + "at": "Comprobado a las {{time}}", + "unreachable": "{{repo}}: este token no puede acceder a él. Añádelo a los repositorios del token en GitHub.", + "readOnly": "{{repo}}: accesible, solo lectura.", + "readWrite": "{{repo}}: accesible, y el token puede escribir.", + "refused": "{{repo}}: GitHub rechazó este token. Comprueba que no ha caducado y que cubre este repositorio.", + "failed": "{{repo}}: la comprobación no terminó. Inténtalo de nuevo en un momento." + }, + "save": "Guardar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Eliminar", + "empty": "Aún no hay conexiones de GitHub.", + "noRepos": "No hay repositorios en la lista: el agente no puede leer ninguno.", + "reposOf": "{{count}} repositorios: {{list}}", + "agentOf": "Agente: {{name}}", + "inactive": "No se está ejecutando: {{reason}}.", + "confirmDelete": { + "title": "¿Eliminar {{name}}?", + "message": "El agente ya no puede acceder a estos repositorios y se elimina el token guardado con la conexión. Para configurarla de nuevo tendrás que volver a introducir el token.", + "confirm": "Eliminar" + }, + "setup": { + "title": "Cómo crear el token", + "link": "Crear un token en GitHub", + "linkLabel": "Crear un token en GitHub (se abre en una pestaña nueva)", + "step1": "Abre GitHub con el botón de arriba. Ponle un nombre al token, por ejemplo LocalAI, y fija una fecha de caducidad. Noventa días es un buen comienzo.", + "step2": "En Repository access, elige Only select repositories y selecciona los que añadas aquí.", + "step3": "En Permissions, fija lo que dice la tabla para el modo que quieras. Deja todo lo demás desactivado.", + "step4": "Crea el token, cópialo y pégalo en el campo del token. GitHub lo muestra una sola vez.", + "table": { + "caption": "Permisos de repositorio que dar al token", + "permission": "Permiso", + "read": "Solo lectura", + "write": "Pregunta antes, o actúa sin preguntar", + "contents": "Contents", + "issues": "Issues", + "pulls": "Pull requests", + "metadata": "Metadata", + "levels": { + "read": "Lectura", + "readwrite": "Lectura y escritura", + "auto": "Lectura (la fija GitHub)" + } + } + }, + "writeMode": { + "label": "Qué puede cambiar el agente en GitHub", + "hint": "Comentarios, issues nuevas, etiquetas y cierre de issues. Nunca fusiona, elimina ni sube código.", + "off": "Solo lectura", + "propose": "Te pregunta antes", + "auto": "Actúa sin preguntar", + "line": { + "off": "No puede cambiar nada en GitHub.", + "propose": "Cada cambio espera tu visto bueno.", + "auto": "Tus chats publican al instante." + }, + "note": { + "off": "El acceso de escritura está desactivado: esta conexión solo lee.", + "propose": "Cada cambio espera en tus aprobaciones hasta que lo confirmes. Todo lo que escriba un agente es público para quien pueda ver el repositorio.", + "auto": "Un chat tuyo publica el cambio al instante, sin preguntar. Todo lo que escriba el agente es público para quien pueda ver el repositorio. Las ejecuciones sin ti presente siguen esperando tu confirmación." + }, + "row": { + "off": "Solo lectura", + "propose": "Te pregunta antes", + "auto": "Actúa sin preguntar" + } + }, + "approval": { + "action": { + "comment-issue": "Comentar en la issue #{{number}}", + "comment-pr": "Comentar en el pull request #{{number}}", + "create-issue": "Abrir una issue nueva", + "edit-issue": "Cambiar #{{number}}", + "add-labels": "Añadir etiquetas a #{{number}}", + "review-comment-pr": "Comentar una línea del pull request #{{number}}" + }, + "where": "Dónde", + "repo": "En {{repo}}", + "open": "Abrir #{{number}} en GitHub", + "as": "Como", + "asOne": "La conexión {{name}}", + "asFirst": "La primera conexión que incluye {{repo}}", + "asUnknown": "Una conexión de GitHub que incluye {{repo}}", + "text": "Texto que se publicará", + "title": "Título", + "newTitle": "Título nuevo", + "labels": "Etiquetas", + "file": "Archivo y línea", + "fileLine": "{{path}}, línea {{line}} ({{side}})", + "sideRight": "archivo nuevo", + "sideLeft": "archivo antiguo", + "state": "Cambio", + "close": "Cerrarla", + "closeReason": "Cerrarla, como {{reason}}", + "reopen": "Reabrirla", + "completed": "completada", + "not_planned": "no planificada", + "public": "Público: todos los que puedan ver {{repo}} lo verán.", + "cut": "El texto mostrado aquí está recortado. Se publica el texto completo, {{bytes}} bytes." + } +} diff --git a/addons/github/ui/src/i18n/github.fr.json b/addons/github/ui/src/i18n/github.fr.json new file mode 100644 index 0000000000..974930ddfa --- /dev/null +++ b/addons/github/ui/src/i18n/github.fr.json @@ -0,0 +1,131 @@ +{ + "title": "GitHub", + "intro": "Laissez vos agents lire le code, les issues et les pull requests des dépôts que vous indiquez. Si vous l’autorisez, ils peuvent aussi y commenter et y ouvrir des issues.", + "limits": "Cette connexion ne répond pas encore aux @mentions sur GitHub. Un agent ne travaille sur GitHub que lorsque vous le lui demandez dans un chat.", + "new": "Nouvelle connexion GitHub", + "edit": "Modifier la connexion GitHub", + "name": "Nom", + "token": "Jeton d’accès à granularité fine", + "tokenHelp": "Commence par github_pat_. Il est conservé scellé et n’est plus jamais affiché.", + "tokenLink": "Créer un jeton sur GitHub", + "tokenSaved": "Enregistré. Saisissez un jeton pour le remplacer.", + "tokenShape": "Utilisez un jeton à granularité fine : il commence par github_pat_.", + "tokenSpace": "Le jeton ne contient pas d’espace. Copiez-le de nouveau sans les espaces autour.", + "repos": "Dépôts que l’agent peut utiliser", + "reposHelp": "Un par ligne, sous la forme owner/name. {{count}} dans la liste. Tout autre dépôt est refusé.", + "reposShape": "« {{entry}} » n’est pas écrit owner/name, par exemple mudler/LocalAI.", + "reposMany": "50 dépôts au plus.", + "agent": "Agent", + "agentNone": "Choisir un agent", + "agentCredentialsOnly": "Aucun agent (identifiants uniquement)", + "agentOptionalHint": "Facultatif. Sans agent, cette connexion ne garde que le jeton, la liste des dépôts et le mode d’écriture pour vos chats. Elle ne répond à aucun message et n’écoute rien.", + "credentialsOnly": "Identifiants uniquement", + "agentNoModel": "{{name}} (a besoin d’un modèle)", + "test": "Tester", + "testOk": "Connecté en tant que {{bot}}.", + "checkFailed": "Enregistré. La vérification du jeton a échoué : {{reason}}", + "checkSeveral": "La vérification des dépôts s’exécute lorsque vous avez une seule connexion GitHub, car une vérification lancée depuis cette page ne peut pas savoir laquelle vous voulez. Dans un chat, activez les connexions dans le menu Outils.", + "check": { + "title": "Vérification de {{name}}", + "running": "Vérification des dépôts...", + "at": "Vérifié à {{time}}", + "unreachable": "{{repo}} : ce jeton ne peut pas l’atteindre. Ajoutez-le aux dépôts du jeton sur GitHub.", + "readOnly": "{{repo}} : accessible, en lecture seule.", + "readWrite": "{{repo}} : accessible, et le jeton peut écrire.", + "refused": "{{repo}} : GitHub a refusé ce jeton. Vérifiez qu’il n’a pas expiré et qu’il couvre ce dépôt.", + "failed": "{{repo}} : la vérification ne s’est pas terminée. Réessayez dans un instant." + }, + "save": "Enregistrer", + "cancel": "Annuler", + "editButton": "Modifier", + "delete": "Supprimer", + "empty": "Aucune connexion GitHub pour le moment.", + "noRepos": "Aucun dépôt dans la liste : l’agent ne peut en lire aucun.", + "reposOf": "{{count}} dépôts : {{list}}", + "agentOf": "Agent : {{name}}", + "inactive": "À l’arrêt : {{reason}}.", + "confirmDelete": { + "title": "Supprimer {{name}} ?", + "message": "L’agent ne peut plus atteindre ces dépôts, et le jeton enregistré avec la connexion est supprimé. Pour la reconfigurer, vous devrez saisir de nouveau le jeton.", + "confirm": "Supprimer" + }, + "setup": { + "title": "Comment créer le jeton", + "link": "Créer un jeton sur GitHub", + "linkLabel": "Créer un jeton sur GitHub (s’ouvre dans un nouvel onglet)", + "step1": "Ouvrez GitHub avec le bouton ci-dessus. Donnez un nom au jeton, par exemple LocalAI, et définissez une date d’expiration. Quatre-vingt-dix jours est un bon début.", + "step2": "Sous Repository access, choisissez Only select repositories et sélectionnez ceux que vous indiquez ici.", + "step3": "Sous Permissions, réglez ce que dit le tableau pour le mode voulu. Laissez tout le reste désactivé.", + "step4": "Créez le jeton, copiez-le et collez-le dans le champ du jeton. GitHub ne l’affiche qu’une seule fois.", + "table": { + "caption": "Autorisations de dépôt à donner au jeton", + "permission": "Autorisation", + "read": "Lecture seule", + "write": "Demande d’abord, ou agit sans demander", + "contents": "Contents", + "issues": "Issues", + "pulls": "Pull requests", + "metadata": "Metadata", + "levels": { + "read": "Lecture", + "readwrite": "Lecture et écriture", + "auto": "Lecture (défini par GitHub)" + } + } + }, + "writeMode": { + "label": "Ce que l’agent peut modifier sur GitHub", + "hint": "Commentaires, nouvelles issues, étiquettes et fermeture d’issues. Il ne fusionne, ne supprime et ne pousse jamais de code.", + "off": "Lecture seule", + "propose": "Vous demande d’abord", + "auto": "Agit sans demander", + "line": { + "off": "Il ne peut rien modifier sur GitHub.", + "propose": "Chaque modification attend votre accord.", + "auto": "Vos chats publient tout de suite." + }, + "note": { + "off": "L’accès en écriture est désactivé : cette connexion ne fait que lire.", + "propose": "Chaque modification attend dans vos approbations jusqu’à votre confirmation. Tout ce qu’écrit un agent est public pour quiconque peut voir le dépôt.", + "auto": "Un de vos chats publie la modification tout de suite, sans demander. Tout ce qu’écrit l’agent est public pour quiconque peut voir le dépôt. Les exécutions sans vous attendent toujours votre confirmation." + }, + "row": { + "off": "Lecture seule", + "propose": "Vous demande d’abord", + "auto": "Agit sans demander" + } + }, + "approval": { + "action": { + "comment-issue": "Commenter l’issue #{{number}}", + "comment-pr": "Commenter la pull request #{{number}}", + "create-issue": "Ouvrir une nouvelle issue", + "edit-issue": "Modifier #{{number}}", + "add-labels": "Ajouter des étiquettes à #{{number}}", + "review-comment-pr": "Commenter une ligne de la pull request #{{number}}" + }, + "where": "Où", + "repo": "Dans {{repo}}", + "open": "Ouvrir #{{number}} sur GitHub", + "as": "En tant que", + "asOne": "La connexion {{name}}", + "asFirst": "La première connexion qui liste {{repo}}", + "asUnknown": "Une connexion GitHub qui liste {{repo}}", + "text": "Texte qui sera publié", + "title": "Titre", + "newTitle": "Nouveau titre", + "labels": "Étiquettes", + "file": "Fichier et ligne", + "fileLine": "{{path}}, ligne {{line}} ({{side}})", + "sideRight": "nouveau fichier", + "sideLeft": "ancien fichier", + "state": "Modification", + "close": "La fermer", + "closeReason": "La fermer, comme {{reason}}", + "reopen": "La rouvrir", + "completed": "terminée", + "not_planned": "non planifiée", + "public": "Public : toute personne pouvant voir {{repo}} verra ceci.", + "cut": "Le texte affiché ici est tronqué. Le texte complet, {{bytes}} octets, sera publié." + } +} diff --git a/addons/github/ui/src/i18n/github.id.json b/addons/github/ui/src/i18n/github.id.json new file mode 100644 index 0000000000..5fea09c84c --- /dev/null +++ b/addons/github/ui/src/i18n/github.id.json @@ -0,0 +1,131 @@ +{ + "title": "GitHub", + "intro": "Biarkan agen Anda membaca kode, issue, dan pull request dari repositori yang Anda daftarkan. Jika Anda mengizinkan, mereka juga dapat berkomentar dan membuka issue di sana.", + "limits": "Koneksi ini belum menjawab @mention di GitHub. Agen bekerja di GitHub hanya saat Anda memintanya di chat.", + "new": "Koneksi GitHub baru", + "edit": "Ubah koneksi GitHub", + "name": "Nama", + "token": "Token akses fine-grained", + "tokenHelp": "Diawali github_pat_. Disimpan terenkripsi dan tidak pernah ditampilkan lagi.", + "tokenLink": "Buat token di GitHub", + "tokenSaved": "Tersimpan. Ketik token untuk menggantinya.", + "tokenShape": "Gunakan token fine-grained: diawali github_pat_.", + "tokenSpace": "Token tidak memiliki spasi. Salin lagi tanpa spasi di sekitarnya.", + "repos": "Repositori yang boleh dipakai agen", + "reposHelp": "Satu per baris, dalam format owner/name. {{count}} terdaftar. Repositori lain ditolak.", + "reposShape": "“{{entry}}” tidak ditulis dalam format owner/name, misalnya mudler/LocalAI.", + "reposMany": "Paling banyak 50 repositori.", + "agent": "Agen", + "agentNone": "Pilih agen", + "agentCredentialsOnly": "Tanpa agen (hanya kredensial)", + "agentOptionalHint": "Opsional. Tanpa agen, koneksi ini hanya menyimpan token, daftar repositori, dan mode tulis untuk chat Anda. Koneksi tidak menjawab pesan dan tidak mendengarkan apa pun.", + "credentialsOnly": "Hanya kredensial", + "agentNoModel": "{{name}} (perlu model)", + "test": "Uji", + "testOk": "Terhubung sebagai {{bot}}.", + "checkFailed": "Tersimpan. Pemeriksaan token gagal: {{reason}}", + "checkSeveral": "Pemeriksaan repositori berjalan jika Anda hanya punya satu koneksi GitHub, karena pemeriksaan dari halaman ini tidak dapat tahu koneksi mana yang Anda maksud. Di chat, aktifkan koneksi lewat menu Alat.", + "check": { + "title": "Pemeriksaan {{name}}", + "running": "Memeriksa repositori...", + "at": "Diperiksa pada {{time}}", + "unreachable": "{{repo}}: token ini tidak dapat menjangkaunya. Tambahkan ke repositori token di GitHub.", + "readOnly": "{{repo}}: terjangkau, hanya baca.", + "readWrite": "{{repo}}: terjangkau, dan token dapat menulis.", + "refused": "{{repo}}: GitHub menolak token ini. Periksa bahwa token belum kedaluwarsa dan mencakup repositori ini.", + "failed": "{{repo}}: pemeriksaan tidak selesai. Coba lagi sebentar lagi." + }, + "save": "Simpan", + "cancel": "Batal", + "editButton": "Ubah", + "delete": "Hapus", + "empty": "Belum ada koneksi GitHub.", + "noRepos": "Tidak ada repositori terdaftar: agen tidak dapat membaca satu pun.", + "reposOf": "{{count}} repositori: {{list}}", + "agentOf": "Agen: {{name}}", + "inactive": "Tidak berjalan: {{reason}}.", + "confirmDelete": { + "title": "Hapus {{name}}?", + "message": "Agen tidak dapat lagi menjangkau repositori ini, dan token yang tersimpan bersamanya dihapus. Untuk menyiapkannya lagi, Anda harus memasukkan token lagi.", + "confirm": "Hapus" + }, + "setup": { + "title": "Cara membuat token", + "link": "Buat token di GitHub", + "linkLabel": "Buat token di GitHub (dibuka di tab baru)", + "step1": "Buka GitHub dengan tombol di atas. Beri nama token, misalnya LocalAI, dan tetapkan masa berlaku. Sembilan puluh hari adalah awal yang baik.", + "step2": "Di bawah Repository access, pilih Only select repositories dan pilih yang Anda daftarkan di sini.", + "step3": "Di bawah Permissions, atur sesuai tabel untuk mode yang Anda inginkan. Biarkan yang lain nonaktif.", + "step4": "Buat token, salin, dan tempel di kolom token. GitHub hanya menampilkannya sekali.", + "table": { + "caption": "Izin repositori yang diberikan ke token", + "permission": "Izin", + "read": "Hanya baca", + "write": "Tanya dulu, atau bertindak tanpa bertanya", + "contents": "Contents", + "issues": "Issues", + "pulls": "Pull requests", + "metadata": "Metadata", + "levels": { + "read": "Baca", + "readwrite": "Baca dan tulis", + "auto": "Baca (diatur GitHub)" + } + } + }, + "writeMode": { + "label": "Apa yang boleh diubah agen di GitHub", + "hint": "Komentar, issue baru, label, dan menutup issue. Agen tidak pernah melakukan merge, menghapus, atau mendorong kode.", + "off": "Hanya baca", + "propose": "Tanya Anda dulu", + "auto": "Bertindak tanpa bertanya", + "line": { + "off": "Agen tidak dapat mengubah apa pun di GitHub.", + "propose": "Setiap perubahan menunggu persetujuan Anda.", + "auto": "Chat Anda langsung memposting." + }, + "note": { + "off": "Akses tulis nonaktif: koneksi ini hanya membaca.", + "propose": "Setiap perubahan menunggu di persetujuan Anda sampai Anda mengonfirmasinya. Apa pun yang ditulis agen bersifat publik bagi semua orang yang dapat melihat repositori.", + "auto": "Chat Anda langsung memposting perubahan, tanpa bertanya. Apa pun yang ditulis agen bersifat publik bagi semua orang yang dapat melihat repositori. Jalan tanpa kehadiran Anda tetap menunggu konfirmasi Anda." + }, + "row": { + "off": "Hanya baca", + "propose": "Tanya Anda dulu", + "auto": "Bertindak tanpa bertanya" + } + }, + "approval": { + "action": { + "comment-issue": "Berkomentar di issue #{{number}}", + "comment-pr": "Berkomentar di pull request #{{number}}", + "create-issue": "Buka issue baru", + "edit-issue": "Ubah #{{number}}", + "add-labels": "Tambah label ke #{{number}}", + "review-comment-pr": "Berkomentar pada satu baris pull request #{{number}}" + }, + "where": "Di mana", + "repo": "Di {{repo}}", + "open": "Buka #{{number}} di GitHub", + "as": "Sebagai", + "asOne": "Koneksi {{name}}", + "asFirst": "Koneksi pertama yang mencantumkan {{repo}}", + "asUnknown": "Koneksi GitHub yang mencantumkan {{repo}}", + "text": "Teks yang akan diposting", + "title": "Judul", + "newTitle": "Judul baru", + "labels": "Label", + "file": "File dan baris", + "fileLine": "{{path}}, baris {{line}} ({{side}})", + "sideRight": "file baru", + "sideLeft": "file lama", + "state": "Perubahan", + "close": "Tutup", + "closeReason": "Tutup, sebagai {{reason}}", + "reopen": "Buka kembali", + "completed": "selesai", + "not_planned": "tidak direncanakan", + "public": "Publik: semua orang yang dapat melihat {{repo}} akan melihat ini.", + "cut": "Teks yang ditampilkan di sini dipotong. Teks lengkap, {{bytes}} byte, yang diposting." + } +} diff --git a/addons/github/ui/src/i18n/github.it.json b/addons/github/ui/src/i18n/github.it.json new file mode 100644 index 0000000000..1ed11ce29d --- /dev/null +++ b/addons/github/ui/src/i18n/github.it.json @@ -0,0 +1,131 @@ +{ + "title": "GitHub", + "intro": "Permetti ai tuoi agenti di leggere codice, issue e pull request dei repository che elenchi. Se lo consenti, possono anche commentare e aprire issue.", + "limits": "Questa connessione non risponde ancora alle @menzioni su GitHub. Un agente lavora su GitHub solo quando glielo chiedi in una chat.", + "new": "Nuova connessione GitHub", + "edit": "Modifica la connessione GitHub", + "name": "Nome", + "token": "Token di accesso fine-grained", + "tokenHelp": "Inizia con github_pat_. Viene cifrato e non viene più mostrato.", + "tokenLink": "Crea un token su GitHub", + "tokenSaved": "Salvato. Scrivi per sostituirlo.", + "tokenShape": "Usa un token fine-grained: inizia con github_pat_.", + "tokenSpace": "Il token non contiene spazi. Copialo di nuovo senza gli spazi attorno.", + "repos": "Repository che l'agente può usare", + "reposHelp": "Uno per riga, nella forma proprietario/nome. {{count}} in elenco. Ogni altro repository viene rifiutato.", + "reposShape": "«{{entry}}» non è scritto nella forma proprietario/nome, per esempio mudler/LocalAI.", + "reposMany": "Al massimo 50 repository.", + "agent": "Agente", + "agentNone": "Scegli un agente", + "agentCredentialsOnly": "Nessun agente (solo credenziali)", + "agentOptionalHint": "Facoltativo. Senza un agente questa connessione conserva solo il token, l'elenco dei repository e la modalità di scrittura per le tue chat. Non risponde ad alcun messaggio e non resta in ascolto.", + "credentialsOnly": "Solo credenziali", + "agentNoModel": "{{name}} (serve un modello)", + "test": "Prova", + "testOk": "Connesso come {{bot}}.", + "checkFailed": "Salvato. La verifica del token non è riuscita: {{reason}}", + "checkSeveral": "La verifica dei repository funziona quando hai una sola connessione GitHub, perché da questa pagina non si capisce a quale ti riferisci. In una chat, attiva le connessioni nel menu Strumenti.", + "check": { + "title": "Verifica di {{name}}", + "running": "Verifica dei repository...", + "at": "Verificato alle {{time}}", + "unreachable": "{{repo}}: questo token non lo raggiunge. Aggiungilo ai repository del token su GitHub.", + "readOnly": "{{repo}}: raggiungibile, sola lettura.", + "readWrite": "{{repo}}: raggiungibile, e il token può scrivere.", + "refused": "{{repo}}: GitHub ha rifiutato questo token. Controlla che non sia scaduto e che copra questo repository.", + "failed": "{{repo}}: la verifica non è terminata. Riprova tra un momento." + }, + "save": "Salva", + "cancel": "Annulla", + "editButton": "Modifica", + "delete": "Elimina", + "empty": "Ancora nessuna connessione GitHub.", + "noRepos": "Nessun repository in elenco: l'agente non può leggerne nessuno.", + "reposOf": "{{count}} repository: {{list}}", + "agentOf": "Agente: {{name}}", + "inactive": "Non in esecuzione: {{reason}}.", + "confirmDelete": { + "title": "Eliminare {{name}}?", + "message": "L'agente non può più raggiungere questi repository e il token salvato con la connessione viene eliminato. Per configurarla di nuovo dovrai inserire di nuovo il token.", + "confirm": "Elimina" + }, + "setup": { + "title": "Come creare il token", + "link": "Crea un token su GitHub", + "linkLabel": "Crea un token su GitHub (si apre in una nuova scheda)", + "step1": "Apri GitHub con il pulsante qui sopra. Dai un nome al token, per esempio LocalAI, e imposta una scadenza. Novanta giorni sono un buon inizio.", + "step2": "In Repository access scegli Only select repositories e seleziona quelli che elenchi qui.", + "step3": "In Permissions imposta ciò che indica la tabella per la modalità che vuoi. Lascia tutto il resto disattivato.", + "step4": "Crea il token, copialo e incollalo nel campo del token. GitHub lo mostra una sola volta.", + "table": { + "caption": "Permessi sui repository da dare al token", + "permission": "Permesso", + "read": "Sola lettura", + "write": "Chiede prima, o agisce senza chiedere", + "contents": "Contents", + "issues": "Issues", + "pulls": "Pull requests", + "metadata": "Metadata", + "levels": { + "read": "Lettura", + "readwrite": "Lettura e scrittura", + "auto": "Lettura (la imposta GitHub)" + } + } + }, + "writeMode": { + "label": "Cosa può modificare l'agente su GitHub", + "hint": "Commenti, nuove issue, etichette e chiusura di issue. Non fa mai merge, non elimina e non invia codice.", + "off": "Sola lettura", + "propose": "Chiede prima a te", + "auto": "Agisce senza chiedere", + "line": { + "off": "Non può cambiare nulla su GitHub.", + "propose": "Ogni modifica aspetta il tuo OK.", + "auto": "Le tue chat pubblicano subito." + }, + "note": { + "off": "L'accesso in scrittura è disattivato: questa connessione si limita a leggere.", + "propose": "Ogni modifica aspetta tra le tue approvazioni finché non la confermi. Tutto ciò che un agente scrive è pubblico per chiunque veda il repository.", + "auto": "Una tua chat pubblica la modifica subito, senza chiedere. Tutto ciò che l'agente scrive è pubblico per chiunque veda il repository. Le esecuzioni senza di te presente aspettano comunque la tua conferma." + }, + "row": { + "off": "Sola lettura", + "propose": "Chiede prima a te", + "auto": "Agisce senza chiedere" + } + }, + "approval": { + "action": { + "comment-issue": "Commentare l'issue #{{number}}", + "comment-pr": "Commentare la pull request #{{number}}", + "create-issue": "Aprire una nuova issue", + "edit-issue": "Modificare #{{number}}", + "add-labels": "Aggiungere etichette a #{{number}}", + "review-comment-pr": "Commentare una riga della pull request #{{number}}" + }, + "where": "Dove", + "repo": "In {{repo}}", + "open": "Apri #{{number}} su GitHub", + "as": "Come", + "asOne": "La connessione {{name}}", + "asFirst": "La prima connessione che elenca {{repo}}", + "asUnknown": "Una connessione GitHub che elenca {{repo}}", + "text": "Testo che verrà pubblicato", + "title": "Titolo", + "newTitle": "Nuovo titolo", + "labels": "Etichette", + "file": "File e riga", + "fileLine": "{{path}}, riga {{line}} ({{side}})", + "sideRight": "file nuovo", + "sideLeft": "file vecchio", + "state": "Modifica", + "close": "Chiudila", + "closeReason": "Chiudila, come {{reason}}", + "reopen": "Riaprila", + "completed": "completata", + "not_planned": "non pianificata", + "public": "Pubblico: chiunque veda {{repo}} lo vedrà.", + "cut": "Il testo mostrato qui è troncato. Viene pubblicato il testo completo, {{bytes}} byte." + } +} diff --git a/addons/github/ui/src/i18n/github.ko.json b/addons/github/ui/src/i18n/github.ko.json new file mode 100644 index 0000000000..453a6bb9c5 --- /dev/null +++ b/addons/github/ui/src/i18n/github.ko.json @@ -0,0 +1,131 @@ +{ + "title": "GitHub", + "intro": "에이전트가 나열한 저장소의 코드, 이슈, 풀 리퀘스트를 읽을 수 있게 합니다. 허용하면 거기에 댓글을 달고 이슈를 열 수도 있습니다.", + "limits": "이 연결은 아직 GitHub의 @멘션에 답변하지 않습니다. 에이전트는 채팅에서 요청할 때만 GitHub에서 작업합니다.", + "new": "새 GitHub 연결", + "edit": "GitHub 연결 편집", + "name": "이름", + "token": "세분화된 액세스 토큰", + "tokenHelp": "github_pat_로 시작합니다. 봉인되어 보관되며 다시 표시되지 않습니다.", + "tokenLink": "GitHub에서 토큰 만들기", + "tokenSaved": "저장됨. 교체하려면 토큰을 입력하세요.", + "tokenShape": "세분화된 토큰을 사용하세요. github_pat_로 시작합니다.", + "tokenSpace": "토큰에는 공백이 없습니다. 앞뒤 공백 없이 다시 복사하세요.", + "repos": "에이전트가 사용할 수 있는 저장소", + "reposHelp": "한 줄에 하나씩 owner/name 형식으로 입력합니다. {{count}}개 나열됨. 그 외의 저장소는 거부됩니다.", + "reposShape": "\"{{entry}}\"은(는) owner/name 형식이 아닙니다. 예: mudler/LocalAI.", + "reposMany": "저장소는 최대 50개입니다.", + "agent": "에이전트", + "agentNone": "에이전트 고르기", + "agentCredentialsOnly": "에이전트 없음(자격 증명만)", + "agentOptionalHint": "선택 사항입니다. 에이전트가 없으면 이 연결은 토큰, 저장소 목록, 내 채팅의 쓰기 모드만 보관합니다. 메시지에 답변하지 않고 아무것도 수신하지 않습니다.", + "credentialsOnly": "자격 증명만", + "agentNoModel": "{{name}}(모델 필요)", + "test": "테스트", + "testOk": "{{bot}}(으)로 연결되었습니다.", + "checkFailed": "저장했습니다. 토큰 확인에 실패했습니다: {{reason}}", + "checkSeveral": "이 페이지의 확인으로는 어느 연결인지 알 수 없으므로, 저장소 확인은 GitHub 연결이 하나일 때만 실행됩니다. 채팅에서는 도구 메뉴에서 연결을 켜세요.", + "check": { + "title": "{{name}} 확인", + "running": "저장소를 확인하는 중...", + "at": "{{time}}에 확인함", + "unreachable": "{{repo}}: 이 토큰으로 접근할 수 없습니다. GitHub에서 토큰의 저장소에 추가하세요.", + "readOnly": "{{repo}}: 접근 가능, 읽기 전용.", + "readWrite": "{{repo}}: 접근 가능, 토큰으로 쓸 수 있습니다.", + "refused": "{{repo}}: GitHub가 이 토큰을 거부했습니다. 만료되지 않았는지, 이 저장소를 포함하는지 확인하세요.", + "failed": "{{repo}}: 확인이 끝나지 않았습니다. 잠시 후 다시 시도하세요." + }, + "save": "저장", + "cancel": "취소", + "editButton": "편집", + "delete": "삭제", + "empty": "아직 GitHub 연결이 없습니다.", + "noRepos": "나열된 저장소가 없습니다. 에이전트가 읽을 수 있는 것이 없습니다.", + "reposOf": "저장소 {{count}}개: {{list}}", + "agentOf": "에이전트: {{name}}", + "inactive": "실행 중이 아님: {{reason}}.", + "confirmDelete": { + "title": "{{name}}을(를) 삭제할까요?", + "message": "에이전트가 더 이상 이 저장소에 접근할 수 없고, 함께 저장된 토큰이 삭제됩니다. 다시 설정하려면 토큰을 다시 입력해야 합니다.", + "confirm": "삭제" + }, + "setup": { + "title": "토큰 만드는 방법", + "link": "GitHub에서 토큰 만들기", + "linkLabel": "GitHub에서 토큰 만들기(새 탭에서 열림)", + "step1": "위의 버튼으로 GitHub를 여세요. 토큰 이름(예: LocalAI)을 정하고 만료일을 설정하세요. 90일이 무난합니다.", + "step2": "Repository access에서 Only select repositories를 고르고 여기에 나열한 저장소를 선택하세요.", + "step3": "Permissions에서 원하는 모드에 맞게 표에 나온 대로 설정하세요. 나머지는 모두 꺼 두세요.", + "step4": "토큰을 만들어 복사한 다음 토큰 필드에 붙여 넣으세요. GitHub는 토큰을 한 번만 보여 줍니다.", + "table": { + "caption": "토큰에 부여할 저장소 권한", + "permission": "권한", + "read": "읽기 전용", + "write": "먼저 묻기 또는 묻지 않고 실행", + "contents": "Contents", + "issues": "Issues", + "pulls": "Pull requests", + "metadata": "Metadata", + "levels": { + "read": "읽기", + "readwrite": "읽기 및 쓰기", + "auto": "읽기(GitHub가 설정)" + } + } + }, + "writeMode": { + "label": "에이전트가 GitHub에서 바꿀 수 있는 것", + "hint": "댓글, 새 이슈, 라벨, 이슈 닫기. 병합, 삭제, 코드 푸시는 하지 않습니다.", + "off": "읽기 전용", + "propose": "먼저 묻기", + "auto": "묻지 않고 실행", + "line": { + "off": "GitHub에서 아무것도 바꿀 수 없습니다.", + "propose": "변경마다 내 승인을 기다립니다.", + "auto": "내 채팅은 바로 게시합니다." + }, + "note": { + "off": "쓰기 접근이 꺼져 있습니다. 이 연결은 읽기만 합니다.", + "propose": "변경마다 내가 확인할 때까지 승인 대기열에서 기다립니다. 에이전트가 쓰는 내용은 저장소를 볼 수 있는 모든 사람에게 공개됩니다.", + "auto": "내 채팅은 묻지 않고 변경을 바로 게시합니다. 에이전트가 쓰는 내용은 저장소를 볼 수 있는 모든 사람에게 공개됩니다. 내가 없는 상태에서 실행되는 작업은 계속 내 확인을 기다립니다." + }, + "row": { + "off": "읽기 전용", + "propose": "먼저 묻기", + "auto": "묻지 않고 실행" + } + }, + "approval": { + "action": { + "comment-issue": "이슈 #{{number}}에 댓글 달기", + "comment-pr": "풀 리퀘스트 #{{number}}에 댓글 달기", + "create-issue": "새 이슈 열기", + "edit-issue": "#{{number}} 변경", + "add-labels": "#{{number}}에 라벨 추가", + "review-comment-pr": "풀 리퀘스트 #{{number}}의 한 줄에 댓글 달기" + }, + "where": "위치", + "repo": "{{repo}}에서", + "open": "GitHub에서 #{{number}} 열기", + "as": "사용할 연결", + "asOne": "연결 {{name}}", + "asFirst": "{{repo}}을(를) 나열한 첫 번째 연결", + "asUnknown": "{{repo}}을(를) 나열한 GitHub 연결", + "text": "게시될 텍스트", + "title": "제목", + "newTitle": "새 제목", + "labels": "라벨", + "file": "파일과 줄", + "fileLine": "{{path}}, {{line}}번째 줄({{side}})", + "sideRight": "새 파일", + "sideLeft": "이전 파일", + "state": "변경", + "close": "닫기", + "closeReason": "닫기, 사유: {{reason}}", + "reopen": "다시 열기", + "completed": "완료", + "not_planned": "계획 없음", + "public": "공개: {{repo}}을(를) 볼 수 있는 모든 사람이 이 내용을 보게 됩니다.", + "cut": "여기에 표시된 텍스트는 잘렸습니다. 전체 텍스트 {{bytes}}바이트가 게시됩니다." + } +} diff --git a/addons/github/ui/src/i18n/github.pt-BR.json b/addons/github/ui/src/i18n/github.pt-BR.json new file mode 100644 index 0000000000..7a53024023 --- /dev/null +++ b/addons/github/ui/src/i18n/github.pt-BR.json @@ -0,0 +1,131 @@ +{ + "title": "GitHub", + "intro": "Deixe seus agentes lerem o código, as issues e os pull requests dos repositórios que você listar. Se você permitir, eles também podem comentar e abrir issues lá.", + "limits": "Esta conexão ainda não responde a @menções no GitHub. Um agente só trabalha no GitHub quando você pede em um chat.", + "new": "Nova conexão do GitHub", + "edit": "Editar conexão do GitHub", + "name": "Nome", + "token": "Token de acesso fine-grained", + "tokenHelp": "Começa com github_pat_. É guardado com segurança e nunca mais é mostrado.", + "tokenLink": "Criar um token no GitHub", + "tokenSaved": "Salvo. Digite um token para substituí-lo.", + "tokenShape": "Use um token fine-grained: ele começa com github_pat_.", + "tokenSpace": "O token não tem espaços. Copie-o de novo sem os espaços ao redor.", + "repos": "Repositórios que o agente pode usar", + "reposHelp": "Um por linha, como owner/name. {{count}} na lista. Qualquer outro repositório é recusado.", + "reposShape": "\"{{entry}}\" não está escrito como owner/name, por exemplo mudler/LocalAI.", + "reposMany": "No máximo 50 repositórios.", + "agent": "Agente", + "agentNone": "Escolha um agente", + "agentCredentialsOnly": "Sem agente (só credenciais)", + "agentOptionalHint": "Opcional. Sem um agente, esta conexão só guarda o token, a lista de repositórios e o modo de escrita dos seus chats. Ela não responde a mensagens e não escuta nada.", + "credentialsOnly": "Só credenciais", + "agentNoModel": "{{name}} (precisa de um modelo)", + "test": "Testar", + "testOk": "Conectado como {{bot}}.", + "checkFailed": "Salvo. A verificação do token falhou: {{reason}}", + "checkSeveral": "A verificação dos repositórios roda quando você tem uma única conexão do GitHub, porque uma verificação feita desta página não sabe de qual você fala. Em um chat, ative as conexões no menu Ferramentas.", + "check": { + "title": "Verificação de {{name}}", + "running": "Verificando os repositórios...", + "at": "Verificado às {{time}}", + "unreachable": "{{repo}}: este token não consegue acessá-lo. Adicione-o aos repositórios do token no GitHub.", + "readOnly": "{{repo}}: acessível, somente leitura.", + "readWrite": "{{repo}}: acessível, e o token pode escrever.", + "refused": "{{repo}}: o GitHub recusou este token. Confira se ele não expirou e se cobre este repositório.", + "failed": "{{repo}}: a verificação não terminou. Tente de novo em instantes." + }, + "save": "Salvar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Excluir", + "empty": "Ainda não há conexões do GitHub.", + "noRepos": "Nenhum repositório na lista: o agente não pode ler nenhum.", + "reposOf": "{{count}} repositórios: {{list}}", + "agentOf": "Agente: {{name}}", + "inactive": "Não está rodando: {{reason}}.", + "confirmDelete": { + "title": "Excluir {{name}}?", + "message": "O agente não alcança mais estes repositórios, e o token salvo com a conexão é excluído. Para configurá-la de novo, você precisará informar o token outra vez.", + "confirm": "Excluir" + }, + "setup": { + "title": "Como criar o token", + "link": "Criar um token no GitHub", + "linkLabel": "Criar um token no GitHub (abre em uma nova aba)", + "step1": "Abra o GitHub com o botão acima. Dê um nome ao token, por exemplo LocalAI, e defina uma validade. Noventa dias é um bom começo.", + "step2": "Em Repository access, escolha Only select repositories e marque os que você listar aqui.", + "step3": "Em Permissions, defina o que a tabela indica para o modo desejado. Deixe todo o resto desligado.", + "step4": "Crie o token, copie-o e cole no campo do token. O GitHub o mostra só uma vez.", + "table": { + "caption": "Permissões de repositório a dar ao token", + "permission": "Permissão", + "read": "Somente leitura", + "write": "Pergunta antes, ou age sem perguntar", + "contents": "Contents", + "issues": "Issues", + "pulls": "Pull requests", + "metadata": "Metadata", + "levels": { + "read": "Leitura", + "readwrite": "Leitura e escrita", + "auto": "Leitura (o GitHub define)" + } + } + }, + "writeMode": { + "label": "O que o agente pode alterar no GitHub", + "hint": "Comentários, novas issues, labels e fechamento de issues. Ele nunca faz merge, exclui nem envia código.", + "off": "Somente leitura", + "propose": "Pergunta antes", + "auto": "Age sem perguntar", + "line": { + "off": "Ele não pode alterar nada no GitHub.", + "propose": "Cada alteração espera o seu OK.", + "auto": "Seus chats publicam na hora." + }, + "note": { + "off": "O acesso de escrita está desligado: esta conexão só lê.", + "propose": "Cada alteração espera nas suas aprovações até você confirmar. Tudo o que um agente escreve é público para quem pode ver o repositório.", + "auto": "Um chat seu publica a alteração na hora, sem perguntar. Tudo o que o agente escreve é público para quem pode ver o repositório. Execuções sem você presente ainda esperam a sua confirmação." + }, + "row": { + "off": "Somente leitura", + "propose": "Pergunta antes", + "auto": "Age sem perguntar" + } + }, + "approval": { + "action": { + "comment-issue": "Comentar na issue #{{number}}", + "comment-pr": "Comentar no pull request #{{number}}", + "create-issue": "Abrir uma nova issue", + "edit-issue": "Alterar #{{number}}", + "add-labels": "Adicionar labels a #{{number}}", + "review-comment-pr": "Comentar uma linha do pull request #{{number}}" + }, + "where": "Onde", + "repo": "Em {{repo}}", + "open": "Abrir #{{number}} no GitHub", + "as": "Como", + "asOne": "A conexão {{name}}", + "asFirst": "A primeira conexão que lista {{repo}}", + "asUnknown": "Uma conexão do GitHub que lista {{repo}}", + "text": "Texto que será publicado", + "title": "Título", + "newTitle": "Novo título", + "labels": "Labels", + "file": "Arquivo e linha", + "fileLine": "{{path}}, linha {{line}} ({{side}})", + "sideRight": "arquivo novo", + "sideLeft": "arquivo antigo", + "state": "Alteração", + "close": "Fechar", + "closeReason": "Fechar, como {{reason}}", + "reopen": "Reabrir", + "completed": "concluída", + "not_planned": "não planejada", + "public": "Público: todos que podem ver {{repo}} verão isto.", + "cut": "O texto mostrado aqui está cortado. O texto completo, {{bytes}} bytes, é publicado." + } +} diff --git a/addons/github/ui/src/i18n/github.zh-CN.json b/addons/github/ui/src/i18n/github.zh-CN.json new file mode 100644 index 0000000000..d277eff700 --- /dev/null +++ b/addons/github/ui/src/i18n/github.zh-CN.json @@ -0,0 +1,131 @@ +{ + "title": "GitHub", + "intro": "让你的智能体读取你列出的仓库中的代码、issue 和拉取请求。如果你允许,它们还可以在那里评论和创建 issue。", + "limits": "此连接暂时不会回应 GitHub 上的 @提及。只有当你在聊天中要求时,智能体才会在 GitHub 上工作。", + "new": "新建 GitHub 连接", + "edit": "编辑 GitHub 连接", + "name": "名称", + "token": "细粒度访问 token", + "tokenHelp": "以 github_pat_ 开头。它会被加密保存,之后不再显示。", + "tokenLink": "在 GitHub 上创建 token", + "tokenSaved": "已保存。输入 token 可替换它。", + "tokenShape": "请使用细粒度 token:它以 github_pat_ 开头。", + "tokenSpace": "token 不含空格。请重新复制,不要带前后的空格。", + "repos": "智能体可以使用的仓库", + "reposHelp": "每行一个,格式为 owner/name。已列出 {{count}} 个。其他仓库一律拒绝。", + "reposShape": "“{{entry}}”的格式不是 owner/name,例如 mudler/LocalAI。", + "reposMany": "最多 50 个仓库。", + "agent": "智能体", + "agentNone": "选择智能体", + "agentCredentialsOnly": "没有智能体(仅凭据)", + "agentOptionalHint": "可选。没有智能体时,此连接只保存 token、仓库列表和用于你的聊天的写入模式。它不回答任何消息,也不监听任何内容。", + "credentialsOnly": "仅凭据", + "agentNoModel": "{{name}}(需要模型)", + "test": "测试", + "testOk": "已作为 {{bot}} 连接。", + "checkFailed": "已保存。token 检查失败:{{reason}}", + "checkSeveral": "仓库检查只在你有一个 GitHub 连接时运行,因为从此页面发起的检查无法判断你指的是哪一个。在聊天中,请通过“工具”菜单启用这些连接。", + "check": { + "title": "{{name}} 的检查结果", + "running": "正在检查仓库...", + "at": "检查时间:{{time}}", + "unreachable": "{{repo}}:此 token 无法访问它。请在 GitHub 上把它加入该 token 的仓库范围。", + "readOnly": "{{repo}}:可访问,只读。", + "readWrite": "{{repo}}:可访问,且该 token 可以写入。", + "refused": "{{repo}}:GitHub 拒绝了此 token。请检查它是否已过期,以及是否涵盖此仓库。", + "failed": "{{repo}}:检查未完成。请稍后再试。" + }, + "save": "保存", + "cancel": "取消", + "editButton": "编辑", + "delete": "删除", + "empty": "还没有 GitHub 连接。", + "noRepos": "没有列出仓库:智能体什么都读不了。", + "reposOf": "{{count}} 个仓库:{{list}}", + "agentOf": "智能体:{{name}}", + "inactive": "未运行:{{reason}}。", + "confirmDelete": { + "title": "删除 {{name}}?", + "message": "智能体将无法再访问这些仓库,随之保存的 token 也会被删除。若要重新设置,你必须再次输入 token。", + "confirm": "删除" + }, + "setup": { + "title": "如何创建 token", + "link": "在 GitHub 上创建 token", + "linkLabel": "在 GitHub 上创建 token(在新标签页中打开)", + "step1": "用上面的按钮打开 GitHub。给 token 起个名字,例如 LocalAI,并设置有效期。九十天是个不错的起点。", + "step2": "在 Repository access 下选择 Only select repositories,并选中你在这里列出的仓库。", + "step3": "在 Permissions 下,按你想要的模式,设置表格中所列的权限。其余全部保持关闭。", + "step4": "创建 token,复制它并粘贴到 token 字段中。GitHub 只显示一次。", + "table": { + "caption": "要授予 token 的仓库权限", + "permission": "权限", + "read": "只读", + "write": "先询问,或不经询问直接执行", + "contents": "Contents", + "issues": "Issues", + "pulls": "Pull requests", + "metadata": "Metadata", + "levels": { + "read": "读取", + "readwrite": "读取和写入", + "auto": "读取(由 GitHub 设置)" + } + } + }, + "writeMode": { + "label": "智能体可以在 GitHub 上更改什么", + "hint": "评论、新建 issue、标签和关闭 issue。它绝不会合并、删除或推送代码。", + "off": "只读", + "propose": "先问你", + "auto": "不经询问直接执行", + "line": { + "off": "它不能在 GitHub 上更改任何内容。", + "propose": "每项更改都等待你的确认。", + "auto": "你的聊天会立即发布。" + }, + "note": { + "off": "写入权限已关闭:此连接只读取。", + "propose": "每项更改都在你的审批中等待,直到你确认。智能体写的任何内容,对所有能看到该仓库的人都是公开的。", + "auto": "你的聊天会立即发布更改,不会询问。智能体写的任何内容,对所有能看到该仓库的人都是公开的。你不在场时的运行仍然要等待你的确认。" + }, + "row": { + "off": "只读", + "propose": "先问你", + "auto": "不经询问直接执行" + } + }, + "approval": { + "action": { + "comment-issue": "评论 issue #{{number}}", + "comment-pr": "评论拉取请求 #{{number}}", + "create-issue": "新建 issue", + "edit-issue": "修改 #{{number}}", + "add-labels": "为 #{{number}} 添加标签", + "review-comment-pr": "评论拉取请求 #{{number}} 的某一行" + }, + "where": "位置", + "repo": "在 {{repo}} 中", + "open": "在 GitHub 上打开 #{{number}}", + "as": "身份", + "asOne": "连接 {{name}}", + "asFirst": "第一个列出 {{repo}} 的连接", + "asUnknown": "列出 {{repo}} 的某个 GitHub 连接", + "text": "将发布的文字", + "title": "标题", + "newTitle": "新标题", + "labels": "标签", + "file": "文件和行", + "fileLine": "{{path}},第 {{line}} 行({{side}})", + "sideRight": "新文件", + "sideLeft": "旧文件", + "state": "更改", + "close": "关闭它", + "closeReason": "关闭它,原因:{{reason}}", + "reopen": "重新打开它", + "completed": "已完成", + "not_planned": "不计划处理", + "public": "公开:所有能看到 {{repo}} 的人都会看到这些内容。", + "cut": "这里显示的文字已被截断。会发布完整的 {{bytes}} 字节文字。" + } +} diff --git a/addons/home-assistant/ui/src/addon.jsx b/addons/home-assistant/ui/src/addon.jsx index f8ff85b6c8..963135ba21 100644 --- a/addons/home-assistant/ui/src/addon.jsx +++ b/addons/home-assistant/ui/src/addon.jsx @@ -5,16 +5,26 @@ import { getSdk } from '../../../internal/host-compat/src/runtime.js' import { registerLocales } from '../../../internal/host-compat/src/i18n.js' import Icon from '../../../internal/host-compat/src/components/Icon' import ConnectorPage from '../../../internal/connector-ui/src/ConnectorPage' +import { pageLocale } from '../../../internal/connector-ui/src/connector' import { connectorsApi } from '../../../internal/connector-ui/src/api' import { Field, Notice, RadioCards, TextInput } from '../../../internal/connector-ui/src/controls' import en from './i18n/home-assistant.en.json' +import it from './i18n/home-assistant.it.json' +import es from './i18n/home-assistant.es.json' +import de from './i18n/home-assistant.de.json' +import fr from './i18n/home-assistant.fr.json' +import zh from './i18n/home-assistant.zh-CN.json' +import id from './i18n/home-assistant.id.json' +import ko from './i18n/home-assistant.ko.json' +import pt from './i18n/home-assistant.pt-BR.json' import PolicyEditor from './PolicyEditor' import ProposalCard from './ProposalCard' import { addressProblem, checkFailure, checkItem, extraBody, extraForm, extraValid, followName, haSecret, parseAddress, tokenProblem, WRITE_MODES, writeModeOf } from './ha' import { finishTest, resultOf, startTest, subscribe } from './results' import './home-assistant.css' -registerLocales({ 'home-assistant': { en } }) +// The words of the page, in every language of the web UI; a key a language lacks reads in English. +registerLocales({ 'home-assistant': { en, it, es, de, fr, 'zh-CN': zh, id, ko, 'pt-BR': pt } }) // The long-lived access token. It is typed only to replace it. function HaToken({ form, setForm, t }) { @@ -115,7 +125,7 @@ function Checks({ c, t }) { ))} - {r.at > 0 &&

{t('check.at', { time: new Date(r.at).toLocaleTimeString() })}

} + {r.at > 0 &&

{t('check.at', { time: new Date(r.at).toLocaleTimeString(pageLocale()) })}

} ) } diff --git a/addons/home-assistant/ui/src/i18n/home-assistant.de.json b/addons/home-assistant/ui/src/i18n/home-assistant.de.json new file mode 100644 index 0000000000..2f109cd5d3 --- /dev/null +++ b/addons/home-assistant/ui/src/i18n/home-assistant.de.json @@ -0,0 +1,140 @@ +{ + "title": "Home Assistant", + "intro": "Lass deine Chats und Agenten dein Smart Home lesen. Wenn du es erlaubst, können sie auch Dinge schalten, innerhalb der Limits, die du unten setzt.", + "limits": "Diese Verbindung beantwortet keine Nachrichten. Ein Agent arbeitet an deinem Zuhause nur, wenn du ihn in einem Chat darum bittest.", + "new": "Neue Home-Assistant-Verbindung", + "edit": "Home-Assistant-Verbindung bearbeiten", + "name": "Name", + "token": "Langlebiges Zugriffstoken", + "tokenHelp": "Wird in deinem Home-Assistant-Profil erstellt. Es wird versiegelt gespeichert und nie wieder angezeigt.", + "tokenSaved": "Gespeichert. Gib ein Token ein, um es zu ersetzen.", + "tokenSpace": "Das Token enthält keine Leerzeichen. Kopiere es erneut ohne die Leerzeichen davor und danach.", + "tokenShort": "Das ist zu kurz für ein langlebiges Zugriffstoken. Kopiere das ganze Token.", + "address": "Adresse deines Home Assistant", + "addressHelp": "Die Adresse, unter der du Home Assistant im Browser öffnest, zum Beispiel https://homeassistant.local:8123. Eine Verbindung ist ein Zuhause. Jede andere Adresse wird abgelehnt.", + "addressShape": "Das ist keine Adresse. Schreibe sie als https://name:8123.", + "addressHttp": "Einfaches http wird nur für eine Adresse in deinem eigenen Netzwerk akzeptiert. Nutze für alles andere https.", + "agent": "Agent", + "agentNone": "Agent wählen", + "agentCredentialsOnly": "Kein Agent (nur Zugangsdaten)", + "agentOptionalHint": "Optional. Ohne Agent hält diese Verbindung nur das Token, die Adresse und den Schreibmodus für deine Chats. Sie beantwortet keine Nachrichten und hört auf nichts.", + "credentialsOnly": "Nur Zugangsdaten", + "agentNoModel": "{{name}} (braucht ein Modell)", + "test": "Test", + "testOk": "Das Token ist gespeichert.", + "checkFailed": "Gespeichert. Die Prüfung des Tokens ist fehlgeschlagen: {{reason}}", + "checkSeveral": "Die Prüfung der Adresse läuft, wenn du genau eine Home-Assistant-Verbindung hast, denn eine Prüfung von dieser Seite aus kann nicht erkennen, welche du meinst. Aktiviere die Verbindungen in einem Chat im Menü Werkzeuge.", + "check": { + "title": "Prüfung von {{name}}", + "running": "Dein Home Assistant wird geprüft...", + "at": "Geprüft um {{time}}", + "ok": "Erreichbar. Home Assistant {{version}}, {{count}} Integrationen.", + "refused": "Home Assistant hat dieses Token abgelehnt. Prüfe, dass es nicht gelöscht wurde und zu einem Nutzer gehört, der sehen kann, was du teilen willst.", + "certificate": "Dem Zertifikat wird nicht vertraut. Wenn es selbstsigniert ist, fixiere unten seinen Fingerabdruck.", + "failed": "Die Prüfung wurde nicht abgeschlossen. Prüfe die Adresse und versuche es gleich noch einmal." + }, + "save": "Speichern", + "cancel": "Abbrechen", + "editButton": "Bearbeiten", + "delete": "Löschen", + "empty": "Noch keine Home-Assistant-Verbindungen.", + "noAddress": "Keine Adresse: Diese Verbindung erreicht nichts.", + "addressOf": "Adresse: {{address}}", + "agentOf": "Agent: {{name}}", + "inactive": "Läuft nicht: {{reason}}.", + "confirmDelete": { + "title": "{{name}} löschen?", + "message": "Deine Chats erreichen dieses Zuhause nicht mehr, und das mit der Verbindung gespeicherte Token wird gelöscht. Um sie erneut einzurichten, musst du das Token noch einmal eingeben.", + "confirm": "Löschen" + }, + "setup": { + "title": "So erstellst du das Token", + "step1": "Öffne in Home Assistant dein Profil (dein Name unten in der Seitenleiste) und dann den Tab Sicherheit.", + "step2": "Wähle unter Langlebige Zugangstoken die Option Token erstellen. Gib ihm einen Namen, zum Beispiel LocalAI, und kopiere es. Home Assistant zeigt es nur einmal an.", + "step3": "Füge es hier ein und schreibe die Adresse, unter der du Home Assistant öffnest. Das Token hat die Rechte des Nutzers, der es erstellt hat: Erstelle es nach Möglichkeit mit einem Nutzer, der nur sehen kann, was du teilen willst.", + "step4": "Lass den Schreibmodus auf Nur lesen, bis du die Limits unten gelesen hast. Das Add-on bedient nie Schlösser, Alarmanlagen, Garagentore oder Sirenen, egal was du wählst.", + "tls": "Home Assistant mit selbstsigniertem Zertifikat: Fixiere seinen SHA-256-Fingerabdruck im Abschnitt unten. Das Add-on akzeptiert dann dieses eine Zertifikat für diese eine Adresse und prüft weiterhin alles andere." + }, + "writeMode": { + "label": "Was der Agent in deinem Zuhause ändern darf", + "hint": "Nur die Dienste auf der Zulassungsliste unten, bei Entitäten, die nicht ausgeschlossen sind.", + "off": "Nur lesen", + "propose": "Fragt dich zuerst", + "auto": "Handelt ohne Rückfrage", + "line": { + "off": "Er kann nichts schalten.", + "propose": "Jede Änderung wartet auf dein OK.", + "auto": "Deine Chats schalten Lichter und Ähnliches sofort." + }, + "note": { + "off": "Schreibzugriff ist aus: Diese Verbindung liest nur.", + "propose": "Jede Änderung wartet in deinen Freigaben, bis du sie bestätigst.", + "auto": "Ein Chat von dir schaltet Lichter, Schalter und Medien sofort, ohne Rückfrage. Alles mit physischer Wirkung, etwa Heizung oder Jalousie, eine Szene oder ein Skript, wartet weiterhin jedes Mal auf deine Bestätigung. Läufe ohne dich warten ebenfalls." + }, + "row": { + "off": "Nur lesen", + "propose": "Fragt dich zuerst", + "auto": "Handelt ohne Rückfrage" + } + }, + "policy": { + "title": "Was Chats in deinem Zuhause tun dürfen", + "loading": "Die Limits werden geladen...", + "loadFailed": "Die Limits konnten nicht geladen werden.", + "saveFailed": "Die Limits konnten nicht gespeichert werden.", + "intro": "Diese Limits gelten für jede Home-Assistant-Verbindung dieses Servers. Der Schreibmodus einer Verbindung entscheidet, ob ein Chat überhaupt etwas ändern darf; diese Limits entscheiden, was.", + "physicalTitle": "Physische Wirkung.", + "physical": "Ein Dienst, den du hier erlaubst, kann eine Jalousie bewegen, einen Raum heizen, ein Skript ausführen oder eine Nachricht senden. Erlaube nur, was du einem Fremden in deinem Haus erlauben würdest. Jeder Dienst, der kein Licht, kein Schalter, kein Input Boolean und keine Medienwiedergabe ist, wartet jedes Mal auf deine Bestätigung, egal welcher Schreibmodus gilt.", + "readOnly": "Nur ein Administrator kann diese Limits ändern.", + "broken": "Die gespeicherten Limits konnten nicht gelesen werden, daher verweigern die Werkzeuge ihre Arbeit. Speichere die Limits erneut, um sie zu reparieren.", + "saved": "Gespeichert. Der nächste Aufruf nutzt die neuen Limits.", + "allowTitle": "Dienste, die ein Chat aufrufen darf", + "allow": "Zulassungsliste", + "allowHint": "Eine pro Zeile, als domain.service, oder domain.* für die Dienste einer Domain (die Dienste zum Neuladen und Neustarten sind nie enthalten). Alles, was nicht aufgeführt ist, wird abgelehnt.", + "allowShape": "„{{entry}}“ ist nicht als domain.service oder domain.* geschrieben.", + "allowHard": "„{{entry}}“ kann nie erlaubt werden. Siehe die Liste der Dienste, die nie verfügbar sind.", + "allowNone": "Nichts ist erlaubt: Die Schreibwerkzeuge lehnen jeden Aufruf ab.", + "allowSummary": "Erlaubte Dienste", + "class": { + "quiet": "Handelt, wenn der Schreibmodus es erlaubt", + "confirm": "Fragt dich immer" + }, + "confirmNote": "Diese brauchen bei jedem Aufruf deine Bestätigung: {{domains}}.", + "maxTargets": "Höchstzahl Entitäten in einem Aufruf", + "maxTargetsHint": "Zwischen 1 und 20. Ein Aufruf nennt seine Entitäten einzeln: Bereiche, Geräte und all werden nicht akzeptiert.", + "targetsShape": "Gib eine ganze Zahl von 1 bis 20 ein.", + "excludeTitle": "Was die Werkzeuge nie zeigen oder anfassen", + "excludeIntro": "Ausgeschlossene Entitäten bleiben in jeder Suche, jedem Status und jedem Verlauf außen vor, und kein Aufruf kann sie schalten. Das Lesen eines Schlosses ist standardmäßig ausgeschlossen; entfernst du es aus der Liste, kann ein Chat lesen, was das Schloss meldet, es aber nie bedienen.", + "excludeDomains": "Entitäts-Domains", + "excludeDomainsHint": "Eine pro Zeile, zum Beispiel lock, alarm_control_panel, camera.", + "excludeAreas": "Bereiche", + "excludeAreasHint": "Eine pro Zeile, nach Name. Die Entitäten dieser Bereiche werden ausgeblendet. Kann Home Assistant nicht sagen, welche Entitäten das sind, lehnen die Werkzeuge ab, statt zu raten.", + "excludePatterns": "Muster für Entitäts-IDs", + "excludePatternsHint": "Ein regulärer Ausdruck pro Zeile, der auf die ganze Entitäts-ID angewendet wird, zum Beispiel sensor\\..*_bedroom.", + "patternShape": "„{{entry}}“ ist kein gültiges Muster.", + "excludeLabel": "Label für Sensibles", + "excludeLabelHint": "Entitäten mit diesem Home-Assistant-Label werden ausgeblendet. Lass es leer, um die Prüfung auszuschalten.", + "pinsTitle": "Zertifikate", + "pins": "Fixierte Zertifikate", + "pinsHint": "Für ein selbstsigniertes Zertifikat: host:port=fingerprint, eines pro Zeile. Der Fingerabdruck ist der SHA-256 des Zertifikats, 64 Ziffern, mit oder ohne Doppelpunkte. Ermittle ihn mit openssl x509 -noout -fingerprint -sha256.", + "pinShape": "„{{entry}}“ ist nicht als host:port=fingerprint mit einem SHA-256 aus 64 Ziffern geschrieben.", + "save": "Limits speichern", + "restore": "Empfohlene wiederherstellen", + "hardTitle": "Nie verfügbar", + "hardIntro": "Das Add-on lehnt diese ab, egal welche Einstellungen gelten, und kein Modell kann das ändern: Schlösser, Alarmanlagen, Sirenen, Ventile, Warmwasserbereiter, Kameras, Neustarts und Neuladen, Shell-Befehle, Skripte des Systems sowie System- und Add-on-Verwaltung. Garagentore, Tore, Türen und Abdeckungen unbekannten Typs werden nie bewegt.", + "hardCover": "Garagentore, Tore und Türen" + }, + "approval": { + "title": "Home Assistant: {{action}}", + "targets": "Was sich ändert", + "now": "Jetzt: {{state}}", + "as": "Als", + "asOne": "Die Verbindung {{name}}", + "asFirst": "Die erste Verbindung, die diese Adresse aufführt", + "asUnknown": "Eine Home-Assistant-Verbindung für diese Adresse", + "data": "Gesendete Einstellungen", + "cut": "Gekürzt angezeigt. Der ganze Wert wird gesendet.", + "physical": "Das hat eine physische Wirkung oder führt eine Automatisierung in deinem Zuhause aus. Es passiert, sobald du bestätigst.", + "quiet": "Das schaltet Dinge in deinem Zuhause, sobald du bestätigst." + } +} diff --git a/addons/home-assistant/ui/src/i18n/home-assistant.es.json b/addons/home-assistant/ui/src/i18n/home-assistant.es.json new file mode 100644 index 0000000000..417852127f --- /dev/null +++ b/addons/home-assistant/ui/src/i18n/home-assistant.es.json @@ -0,0 +1,140 @@ +{ + "title": "Home Assistant", + "intro": "Deja que tus chats y agentes lean tu casa inteligente. Si lo permites, también pueden accionar dispositivos, dentro de los límites que fijes abajo.", + "limits": "Esta conexión no responde a mensajes. Un agente trabaja en tu casa solo cuando se lo pides en un chat.", + "new": "Nueva conexión de Home Assistant", + "edit": "Editar conexión de Home Assistant", + "name": "Nombre", + "token": "Token de acceso de larga duración", + "tokenHelp": "Se crea en tu perfil de Home Assistant. Se guarda cifrado y no se vuelve a mostrar.", + "tokenSaved": "Guardado. Escribe un token para reemplazarlo.", + "tokenSpace": "El token no tiene espacios. Cópialo de nuevo sin los espacios alrededor.", + "tokenShort": "Esto es demasiado corto para ser un token de acceso de larga duración. Copia el token completo.", + "address": "Dirección de tu Home Assistant", + "addressHelp": "Donde abres Home Assistant en un navegador, por ejemplo https://homeassistant.local:8123. Una conexión es una casa. Cualquier otra dirección se rechaza.", + "addressShape": "Esto no es una dirección. Escríbela como https://nombre:8123.", + "addressHttp": "http sin cifrar solo se acepta para una dirección de tu propia red. Usa https para cualquier otra.", + "agent": "Agente", + "agentNone": "Elige un agente", + "agentCredentialsOnly": "Sin agente (solo credenciales)", + "agentOptionalHint": "Opcional. Sin agente, esta conexión solo guarda el token, la dirección y el modo de escritura para tus chats. No responde a ningún mensaje y no escucha nada.", + "credentialsOnly": "Solo credenciales", + "agentNoModel": "{{name}} (necesita un modelo)", + "test": "Probar", + "testOk": "El token está guardado.", + "checkFailed": "Guardado. Falló la comprobación del token: {{reason}}", + "checkSeveral": "La comprobación de la dirección se ejecuta cuando tienes una sola conexión de Home Assistant, porque una comprobación desde esta página no puede saber a cuál te refieres. En un chat, activa las conexiones en el menú Herramientas.", + "check": { + "title": "Comprobación de {{name}}", + "running": "Comprobando tu Home Assistant...", + "at": "Comprobado a las {{time}}", + "ok": "Accesible. Home Assistant {{version}}, {{count}} integraciones.", + "refused": "Home Assistant rechazó este token. Comprueba que no se haya eliminado y que pertenezca a un usuario que pueda ver lo que quieres compartir.", + "certificate": "El certificado no es de confianza. Si es autofirmado, fija su huella abajo.", + "failed": "La comprobación no terminó. Revisa la dirección e inténtalo de nuevo en un momento." + }, + "save": "Guardar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Eliminar", + "empty": "Aún no hay conexiones de Home Assistant.", + "noAddress": "Sin dirección: esta conexión no llega a nada.", + "addressOf": "Dirección: {{address}}", + "agentOf": "Agente: {{name}}", + "inactive": "No se está ejecutando: {{reason}}.", + "confirmDelete": { + "title": "¿Eliminar {{name}}?", + "message": "Tus chats ya no pueden acceder a esta casa y se elimina el token guardado con la conexión. Para configurarla de nuevo tendrás que volver a introducir el token.", + "confirm": "Eliminar" + }, + "setup": { + "title": "Cómo crear el token", + "step1": "En Home Assistant, abre tu perfil (tu nombre al final de la barra lateral) y luego la pestaña Seguridad.", + "step2": "En Tokens de acceso de larga duración, elige Crear token. Ponle un nombre, por ejemplo LocalAI, y cópialo. Home Assistant lo muestra una sola vez.", + "step3": "Pégalo aquí y escribe la dirección con la que abres Home Assistant. El token tiene los derechos del usuario que lo creó: créalo con un usuario que vea solo lo que quieres compartir, si puedes.", + "step4": "Deja el modo de escritura en Solo lectura hasta que hayas leído los límites de abajo. El complemento nunca maneja cerraduras, alarmas, puertas de garaje ni sirenas, elijas lo que elijas.", + "tls": "Home Assistant con un certificado autofirmado: fija su huella SHA-256 en la sección de abajo. El complemento acepta entonces ese certificado para esa dirección y sigue comprobando todo lo demás." + }, + "writeMode": { + "label": "Qué puede cambiar el agente en tu casa", + "hint": "Solo los servicios de la lista de permitidos de abajo, sobre entidades que no estén excluidas.", + "off": "Solo lectura", + "propose": "Te pregunta antes", + "auto": "Actúa sin preguntar", + "line": { + "off": "No puede accionar nada.", + "propose": "Cada cambio espera tu visto bueno.", + "auto": "Tus chats accionan luces y similares al instante." + }, + "note": { + "off": "El acceso de escritura está desactivado: esta conexión solo lee.", + "propose": "Cada cambio espera en tus aprobaciones hasta que lo confirmes.", + "auto": "Un chat tuyo acciona luces, interruptores y reproductores multimedia al instante, sin preguntar. Todo lo que tenga un efecto físico, como la calefacción o una persiana, una escena o un script, sigue esperando tu confirmación cada vez. Las ejecuciones sin ti presente también esperan." + }, + "row": { + "off": "Solo lectura", + "propose": "Te pregunta antes", + "auto": "Actúa sin preguntar" + } + }, + "policy": { + "title": "Qué pueden hacer los chats en tu casa", + "loading": "Cargando los límites...", + "loadFailed": "No se pudieron cargar los límites.", + "saveFailed": "No se pudieron guardar los límites.", + "intro": "Estos límites se aplican a todas las conexiones de Home Assistant de este servidor. El modo de escritura de una conexión decide si un chat puede cambiar algo; estos límites deciden qué.", + "physicalTitle": "Efectos físicos.", + "physical": "Un servicio que permitas aquí puede mover una persiana, calentar una habitación, ejecutar un script o enviar un mensaje. Permite solo lo que dejarías hacer a un desconocido en tu casa. Todo servicio que no sea una luz, un interruptor, un input boolean o la reproducción multimedia espera tu confirmación cada vez, sea cual sea el modo de escritura.", + "readOnly": "Solo un administrador puede cambiar estos límites.", + "broken": "No se pudieron leer los límites guardados, así que las herramientas se niegan a funcionar. Guarda los límites de nuevo para repararlos.", + "saved": "Guardado. La próxima llamada usa los límites nuevos.", + "allowTitle": "Servicios a los que puede llamar un chat", + "allow": "Lista de permitidos", + "allowHint": "Uno por línea, con el formato dominio.servicio, o dominio.* para los servicios de un dominio (los servicios reload y restart nunca se incluyen). Lo que no esté en la lista se rechaza.", + "allowShape": "«{{entry}}» no tiene el formato dominio.servicio ni dominio.*.", + "allowHard": "«{{entry}}» no se puede permitir nunca. Mira la lista de servicios que nunca están disponibles.", + "allowNone": "No se permite nada: las herramientas de escritura rechazan toda llamada.", + "allowSummary": "Servicios permitidos", + "class": { + "quiet": "Actúa cuando lo permite el modo de escritura", + "confirm": "Siempre te pregunta" + }, + "confirmNote": "Estos necesitan tu confirmación en cada llamada: {{domains}}.", + "maxTargets": "Máximo de entidades en una llamada", + "maxTargetsHint": "Entre 1 y 20. Una llamada nombra sus entidades una por una: no se aceptan áreas, dispositivos ni all.", + "targetsShape": "Introduce un número entero del 1 al 20.", + "excludeTitle": "Lo que las herramientas nunca muestran ni tocan", + "excludeIntro": "Las entidades excluidas quedan fuera de toda búsqueda, estado e historial, y ninguna llamada puede accionarlas. Leer una cerradura está excluido por defecto; si la quitas de la lista, un chat puede leer lo que informa la cerradura, nunca manejarla.", + "excludeDomains": "Dominios de entidades", + "excludeDomainsHint": "Uno por línea, por ejemplo lock, alarm_control_panel, camera.", + "excludeAreas": "Áreas", + "excludeAreasHint": "Una por línea, por nombre. Las entidades de estas áreas quedan ocultas. Si Home Assistant no puede decir cuáles son esas entidades, las herramientas se niegan en vez de adivinar.", + "excludePatterns": "Patrones de ID de entidad", + "excludePatternsHint": "Una expresión regular por línea, comparada con el ID de entidad completo, por ejemplo sensor\\..*_bedroom.", + "patternShape": "«{{entry}}» no es un patrón válido.", + "excludeLabel": "Etiqueta sensible", + "excludeLabelHint": "Las entidades que llevan esta etiqueta de Home Assistant quedan ocultas. Déjala vacía para desactivar la comprobación.", + "pinsTitle": "Certificados", + "pins": "Certificados fijados", + "pinsHint": "Para un certificado autofirmado: host:puerto=huella, uno por línea. La huella es el SHA-256 del certificado, 64 dígitos, con o sin dos puntos. Obtenla con openssl x509 -noout -fingerprint -sha256.", + "pinShape": "«{{entry}}» no tiene el formato host:puerto=huella con un SHA-256 de 64 dígitos.", + "save": "Guardar límites", + "restore": "Restaurar los recomendados", + "hardTitle": "Nunca disponibles", + "hardIntro": "El complemento rechaza esto sea cual sea la configuración, y ningún modelo puede cambiarlo: cerraduras, alarmas, sirenas, válvulas, calentadores de agua, cámaras, reinicios y recargas, comandos de shell, scripts del sistema y gestión del sistema y de complementos. Las puertas de garaje, portones, puertas y cubiertas de tipo desconocido nunca se mueven.", + "hardCover": "cubiertas de garaje, portón y puerta" + }, + "approval": { + "title": "Home Assistant: {{action}}", + "targets": "Qué cambia", + "now": "Ahora: {{state}}", + "as": "Como", + "asOne": "La conexión {{name}}", + "asFirst": "La primera conexión que incluye esta dirección", + "asUnknown": "Una conexión de Home Assistant para esta dirección", + "data": "Ajustes enviados", + "cut": "Se muestra recortado. Se envía el valor completo.", + "physical": "Esto tiene un efecto físico o ejecuta una automatización en tu casa. Ocurre en cuanto lo confirmes.", + "quiet": "Esto acciona dispositivos de tu casa en cuanto lo confirmes." + } +} diff --git a/addons/home-assistant/ui/src/i18n/home-assistant.fr.json b/addons/home-assistant/ui/src/i18n/home-assistant.fr.json new file mode 100644 index 0000000000..f0862439cc --- /dev/null +++ b/addons/home-assistant/ui/src/i18n/home-assistant.fr.json @@ -0,0 +1,140 @@ +{ + "title": "Home Assistant", + "intro": "Laissez vos chats et vos agents lire l’état de votre maison connectée. Si vous l’autorisez, ils peuvent aussi commander des appareils, dans les limites que vous définissez ci-dessous.", + "limits": "Cette connexion ne répond pas aux messages. Un agent ne travaille sur votre maison que lorsque vous le lui demandez dans un chat.", + "new": "Nouvelle connexion Home Assistant", + "edit": "Modifier la connexion Home Assistant", + "name": "Nom", + "token": "Jeton d’accès longue durée", + "tokenHelp": "Créé dans votre profil Home Assistant. Il est conservé scellé et n’est plus jamais affiché.", + "tokenSaved": "Enregistré. Saisissez un jeton pour le remplacer.", + "tokenSpace": "Le jeton ne contient pas d’espace. Copiez-le de nouveau sans les espaces autour.", + "tokenShort": "C’est trop court pour un jeton d’accès longue durée. Copiez le jeton en entier.", + "address": "Adresse de votre Home Assistant", + "addressHelp": "L’adresse avec laquelle vous ouvrez Home Assistant dans un navigateur, par exemple https://homeassistant.local:8123. Une connexion correspond à une maison. Toute autre adresse est refusée.", + "addressShape": "Ce n’est pas une adresse. Écrivez-la sous la forme https://nom:8123.", + "addressHttp": "Le http simple n’est accepté que pour une adresse de votre propre réseau. Utilisez https pour toute autre.", + "agent": "Agent", + "agentNone": "Choisir un agent", + "agentCredentialsOnly": "Aucun agent (identifiants uniquement)", + "agentOptionalHint": "Facultatif. Sans agent, cette connexion ne garde que le jeton, l’adresse et le mode d’écriture pour vos chats. Elle ne répond à aucun message et n’écoute rien.", + "credentialsOnly": "Identifiants uniquement", + "agentNoModel": "{{name}} (a besoin d’un modèle)", + "test": "Tester", + "testOk": "Le jeton est enregistré.", + "checkFailed": "Enregistré. La vérification du jeton a échoué : {{reason}}", + "checkSeveral": "La vérification de l’adresse s’exécute lorsque vous avez une seule connexion Home Assistant, car une vérification lancée depuis cette page ne peut pas savoir laquelle vous voulez. Dans un chat, activez les connexions dans le menu Outils.", + "check": { + "title": "Vérification de {{name}}", + "running": "Vérification de votre Home Assistant...", + "at": "Vérifié à {{time}}", + "ok": "Accessible. Home Assistant {{version}}, {{count}} intégrations.", + "refused": "Home Assistant a refusé ce jeton. Vérifiez qu’il n’a pas été supprimé et qu’il appartient à un utilisateur qui voit ce que vous voulez partager.", + "certificate": "Le certificat n’est pas approuvé. S’il est auto-signé, épinglez son empreinte ci-dessous.", + "failed": "La vérification ne s’est pas terminée. Vérifiez l’adresse et réessayez dans un instant." + }, + "save": "Enregistrer", + "cancel": "Annuler", + "editButton": "Modifier", + "delete": "Supprimer", + "empty": "Aucune connexion Home Assistant pour le moment.", + "noAddress": "Aucune adresse : cette connexion n’atteint rien.", + "addressOf": "Adresse : {{address}}", + "agentOf": "Agent : {{name}}", + "inactive": "À l’arrêt : {{reason}}.", + "confirmDelete": { + "title": "Supprimer {{name}} ?", + "message": "Vos chats ne peuvent plus atteindre cette maison, et le jeton enregistré avec la connexion est supprimé. Pour la reconfigurer, vous devrez saisir de nouveau le jeton.", + "confirm": "Supprimer" + }, + "setup": { + "title": "Comment créer le jeton", + "step1": "Dans Home Assistant, ouvrez votre profil (votre nom en bas de la barre latérale), puis l’onglet Sécurité.", + "step2": "Sous Jetons d’accès longue durée, choisissez Créer un jeton. Donnez-lui un nom, par exemple LocalAI, et copiez-le. Home Assistant ne l’affiche qu’une seule fois.", + "step3": "Collez-le ici et écrivez l’adresse avec laquelle vous ouvrez Home Assistant. Le jeton a les droits de l’utilisateur qui l’a créé : créez-le avec un utilisateur qui ne voit que ce que vous voulez partager, si possible.", + "step4": "Laissez le mode d’écriture sur Lecture seule tant que vous n’avez pas lu les limites ci-dessous. L’add-on ne commande jamais les serrures, alarmes, portes de garage ni sirènes, quel que soit votre choix.", + "tls": "Home Assistant avec un certificat auto-signé : épinglez son empreinte SHA-256 dans la section ci-dessous. L’add-on accepte alors ce certificat pour cette adresse uniquement et continue de tout vérifier par ailleurs." + }, + "writeMode": { + "label": "Ce que l’agent peut modifier dans votre maison", + "hint": "Seulement les services de la liste d’autorisation ci-dessous, sur des entités non exclues.", + "off": "Lecture seule", + "propose": "Vous demande d’abord", + "auto": "Agit sans demander", + "line": { + "off": "Il ne peut rien commander.", + "propose": "Chaque modification attend votre accord.", + "auto": "Vos chats allument les lumières et commandent des appareils similaires tout de suite." + }, + "note": { + "off": "L’accès en écriture est désactivé : cette connexion ne fait que lire.", + "propose": "Chaque modification attend dans vos approbations jusqu’à votre confirmation.", + "auto": "Un de vos chats commande tout de suite, sans demander, les lumières, les interrupteurs et les médias. Tout ce qui a un effet physique, comme le chauffage ou un store, une scène ou un script, attend toujours votre confirmation. Les exécutions sans vous attendent aussi." + }, + "row": { + "off": "Lecture seule", + "propose": "Vous demande d’abord", + "auto": "Agit sans demander" + } + }, + "policy": { + "title": "Ce que les chats peuvent faire dans votre maison", + "loading": "Chargement des limites...", + "loadFailed": "Les limites n’ont pas pu être chargées.", + "saveFailed": "Les limites n’ont pas pu être enregistrées.", + "intro": "Ces limites s’appliquent à toutes les connexions Home Assistant de ce serveur. Le mode d’écriture d’une connexion décide si un chat peut modifier quoi que ce soit ; ces limites décident quoi.", + "physicalTitle": "Effets physiques.", + "physical": "Un service que vous autorisez ici peut déplacer un store, chauffer une pièce, lancer un script ou envoyer un message. N’autorisez que ce que vous laisseriez faire à un inconnu chez vous. Tout service qui n’est pas une lumière, un interrupteur, un input boolean ou une lecture de média attend votre confirmation à chaque fois, quel que soit le mode d’écriture.", + "readOnly": "Seul un administrateur peut modifier ces limites.", + "broken": "Les limites enregistrées n’ont pas pu être lues, les outils refusent donc de fonctionner. Enregistrez de nouveau les limites pour les réparer.", + "saved": "Enregistré. Le prochain appel utilise les nouvelles limites.", + "allowTitle": "Services qu’un chat peut appeler", + "allow": "Liste d’autorisation", + "allowHint": "Un par ligne, sous la forme domain.service, ou domain.* pour les services d’un domaine (les services reload et restart n’en font jamais partie). Tout ce qui n’est pas listé est refusé.", + "allowShape": "« {{entry}} » n’est pas écrit domain.service ou domain.*.", + "allowHard": "« {{entry}} » ne peut jamais être autorisé. Voyez la liste des services jamais disponibles.", + "allowNone": "Rien n’est autorisé : les outils d’écriture refusent tous les appels.", + "allowSummary": "Services autorisés", + "class": { + "quiet": "Agit quand le mode d’écriture le permet", + "confirm": "Vous demande toujours" + }, + "confirmNote": "Ceux-ci exigent votre confirmation à chaque appel : {{domains}}.", + "maxTargets": "Nombre maximal d’entités par appel", + "maxTargetsHint": "Entre 1 et 20. Un appel nomme ses entités une par une : les zones, les appareils et all ne sont pas acceptés.", + "targetsShape": "Saisissez un nombre entier de 1 à 20.", + "excludeTitle": "Ce que les outils ne montrent ni ne touchent jamais", + "excludeIntro": "Les entités exclues sont absentes de toutes les recherches, états et historiques, et aucun appel ne peut les commander. La lecture d’une serrure est exclue par défaut ; la retirer de la liste permet à un chat de lire ce que la serrure signale, jamais de la commander.", + "excludeDomains": "Domaines d’entités", + "excludeDomainsHint": "Un par ligne, par exemple lock, alarm_control_panel, camera.", + "excludeAreas": "Zones", + "excludeAreasHint": "Une par ligne, par nom. Les entités de ces zones sont masquées. Si Home Assistant ne peut pas dire quelles sont ces entités, les outils refusent au lieu de deviner.", + "excludePatterns": "Motifs d’ID d’entité", + "excludePatternsHint": "Une expression régulière par ligne, comparée à l’ID d’entité entier, par exemple sensor\\..*_bedroom.", + "patternShape": "« {{entry}} » n’est pas un motif valide.", + "excludeLabel": "Étiquette sensible", + "excludeLabelHint": "Les entités qui portent cette étiquette Home Assistant sont masquées. Laissez vide pour désactiver la vérification.", + "pinsTitle": "Certificats", + "pins": "Certificats épinglés", + "pinsHint": "Pour un certificat auto-signé : host:port=empreinte, un par ligne. L’empreinte est le SHA-256 du certificat, 64 chiffres, avec ou sans deux-points. Obtenez-la avec openssl x509 -noout -fingerprint -sha256.", + "pinShape": "« {{entry}} » n’est pas écrit host:port=empreinte avec un SHA-256 de 64 chiffres.", + "save": "Enregistrer les limites", + "restore": "Rétablir les valeurs recommandées", + "hardTitle": "Jamais disponible", + "hardIntro": "L’add-on refuse ceci quels que soient les réglages, et aucun modèle ne peut le changer : serrures, alarmes, sirènes, vannes, chauffe-eau, caméras, redémarrages et rechargements, commandes shell, scripts du système, et gestion du système et des add-ons. Les portes de garage, portails, portes et ouvrants de type inconnu ne sont jamais déplacés.", + "hardCover": "portes de garage, portails et portes" + }, + "approval": { + "title": "Home Assistant : {{action}}", + "targets": "Ce qui change", + "now": "Actuellement : {{state}}", + "as": "En tant que", + "asOne": "La connexion {{name}}", + "asFirst": "La première connexion qui liste cette adresse", + "asUnknown": "Une connexion Home Assistant pour cette adresse", + "data": "Paramètres envoyés", + "cut": "Affiché tronqué. La valeur entière est envoyée.", + "physical": "Ceci a un effet physique ou lance une automatisation chez vous. Cela se produit dès que vous confirmez.", + "quiet": "Ceci commande des appareils chez vous dès que vous confirmez." + } +} diff --git a/addons/home-assistant/ui/src/i18n/home-assistant.id.json b/addons/home-assistant/ui/src/i18n/home-assistant.id.json new file mode 100644 index 0000000000..c469dd2727 --- /dev/null +++ b/addons/home-assistant/ui/src/i18n/home-assistant.id.json @@ -0,0 +1,140 @@ +{ + "title": "Home Assistant", + "intro": "Biarkan chat dan agen Anda membaca rumah pintar Anda. Jika Anda mengizinkan, mereka juga dapat menyalakan atau mematikan perangkat, dalam batas yang Anda atur di bawah.", + "limits": "Koneksi ini tidak menjawab pesan. Agen bekerja pada rumah Anda hanya saat Anda memintanya di chat.", + "new": "Koneksi Home Assistant baru", + "edit": "Ubah koneksi Home Assistant", + "name": "Nama", + "token": "Token akses jangka panjang", + "tokenHelp": "Dibuat di profil Home Assistant Anda. Disimpan terenkripsi dan tidak pernah ditampilkan lagi.", + "tokenSaved": "Tersimpan. Ketik token untuk menggantinya.", + "tokenSpace": "Token tidak memiliki spasi. Salin lagi tanpa spasi di sekitarnya.", + "tokenShort": "Ini terlalu pendek untuk token akses jangka panjang. Salin seluruh token.", + "address": "Alamat Home Assistant Anda", + "addressHelp": "Tempat Anda membuka Home Assistant di browser, misalnya https://homeassistant.local:8123. Satu koneksi untuk satu rumah. Alamat lain ditolak.", + "addressShape": "Ini bukan alamat. Tulis seperti https://nama:8123.", + "addressHttp": "http biasa hanya diterima untuk alamat di jaringan Anda sendiri. Gunakan https untuk yang lain.", + "agent": "Agen", + "agentNone": "Pilih agen", + "agentCredentialsOnly": "Tanpa agen (hanya kredensial)", + "agentOptionalHint": "Opsional. Tanpa agen, koneksi ini hanya menyimpan token, alamat, dan mode tulis untuk chat Anda. Koneksi tidak menjawab pesan dan tidak mendengarkan apa pun.", + "credentialsOnly": "Hanya kredensial", + "agentNoModel": "{{name}} (perlu model)", + "test": "Uji", + "testOk": "Token tersimpan.", + "checkFailed": "Tersimpan. Pemeriksaan token gagal: {{reason}}", + "checkSeveral": "Pemeriksaan alamat berjalan jika Anda hanya punya satu koneksi Home Assistant, karena pemeriksaan dari halaman ini tidak dapat tahu koneksi mana yang Anda maksud. Di chat, aktifkan koneksi lewat menu Alat.", + "check": { + "title": "Pemeriksaan {{name}}", + "running": "Memeriksa Home Assistant Anda...", + "at": "Diperiksa pada {{time}}", + "ok": "Terjangkau. Home Assistant {{version}}, {{count}} integrasi.", + "refused": "Home Assistant menolak token ini. Periksa bahwa token belum dihapus dan milik pengguna yang dapat melihat apa yang ingin Anda bagikan.", + "certificate": "Sertifikat tidak tepercaya. Jika self-signed, sematkan fingerprint-nya di bawah.", + "failed": "Pemeriksaan tidak selesai. Periksa alamat dan coba lagi sebentar lagi." + }, + "save": "Simpan", + "cancel": "Batal", + "editButton": "Ubah", + "delete": "Hapus", + "empty": "Belum ada koneksi Home Assistant.", + "noAddress": "Tanpa alamat: koneksi ini tidak menjangkau apa pun.", + "addressOf": "Alamat: {{address}}", + "agentOf": "Agen: {{name}}", + "inactive": "Tidak berjalan: {{reason}}.", + "confirmDelete": { + "title": "Hapus {{name}}?", + "message": "Chat Anda tidak dapat lagi menjangkau rumah ini, dan token yang tersimpan bersamanya dihapus. Untuk menyiapkannya lagi, Anda harus memasukkan token lagi.", + "confirm": "Hapus" + }, + "setup": { + "title": "Cara membuat token", + "step1": "Di Home Assistant, buka profil Anda (nama Anda di bagian bawah sidebar) lalu tab Security.", + "step2": "Di bawah Long-lived access tokens, pilih Create token. Beri nama, misalnya LocalAI, lalu salin. Home Assistant hanya menampilkannya sekali.", + "step3": "Tempel di sini dan tulis alamat yang Anda pakai untuk membuka Home Assistant. Token memiliki hak pengguna yang membuatnya: buat dengan pengguna yang hanya dapat melihat apa yang ingin Anda bagikan, jika bisa.", + "step4": "Biarkan mode tulis pada Hanya baca sampai Anda membaca batas di bawah. Add-on tidak pernah mengoperasikan kunci pintu, alarm, pintu garasi, atau sirene, apa pun pilihan Anda.", + "tls": "Home Assistant dengan sertifikat self-signed: sematkan fingerprint SHA-256-nya di bagian di bawah. Add-on lalu menerima satu sertifikat itu untuk satu alamat itu dan tetap memeriksa semua hal lainnya." + }, + "writeMode": { + "label": "Apa yang boleh diubah agen di rumah Anda", + "hint": "Hanya layanan di daftar yang diizinkan di bawah, pada entitas yang tidak dikecualikan.", + "off": "Hanya baca", + "propose": "Tanya Anda dulu", + "auto": "Bertindak tanpa bertanya", + "line": { + "off": "Agen tidak dapat menyalakan atau mematikan apa pun.", + "propose": "Setiap perubahan menunggu persetujuan Anda.", + "auto": "Chat Anda langsung menyalakan atau mematikan lampu dan sejenisnya." + }, + "note": { + "off": "Akses tulis nonaktif: koneksi ini hanya membaca.", + "propose": "Setiap perubahan menunggu di persetujuan Anda sampai Anda mengonfirmasinya.", + "auto": "Chat Anda langsung menyalakan atau mematikan lampu, sakelar, dan media, tanpa bertanya. Apa pun yang berefek fisik, seperti pemanas atau tirai, sebuah scene atau skrip, tetap menunggu konfirmasi Anda setiap kali. Jalan tanpa kehadiran Anda juga menunggu." + }, + "row": { + "off": "Hanya baca", + "propose": "Tanya Anda dulu", + "auto": "Bertindak tanpa bertanya" + } + }, + "policy": { + "title": "Apa yang boleh dilakukan chat di rumah Anda", + "loading": "Memuat batas...", + "loadFailed": "Batas tidak dapat dimuat.", + "saveFailed": "Batas tidak dapat disimpan.", + "intro": "Batas ini berlaku untuk setiap koneksi Home Assistant di server ini. Mode tulis sebuah koneksi menentukan apakah chat boleh mengubah sesuatu sama sekali; batas ini menentukan apa saja.", + "physicalTitle": "Efek fisik.", + "physical": "Layanan yang Anda izinkan di sini dapat menggerakkan tirai, memanaskan ruangan, menjalankan skrip, atau mengirim pesan. Izinkan hanya apa yang Anda biarkan dilakukan orang asing di rumah Anda. Setiap layanan yang bukan lampu, sakelar, input boolean, atau pemutaran media menunggu konfirmasi Anda setiap kali, apa pun mode tulisnya.", + "readOnly": "Hanya administrator yang dapat mengubah batas ini.", + "broken": "Batas yang tersimpan tidak dapat dibaca, jadi alat menolak bekerja. Simpan batas lagi untuk memperbaikinya.", + "saved": "Tersimpan. Panggilan berikutnya memakai batas baru.", + "allowTitle": "Layanan yang boleh dipanggil chat", + "allow": "Daftar yang diizinkan", + "allowHint": "Satu per baris, dalam format domain.service, atau domain.* untuk layanan satu domain (layanan reload dan restart tidak pernah disertakan). Yang tidak terdaftar ditolak.", + "allowShape": "“{{entry}}” tidak ditulis dalam format domain.service atau domain.*.", + "allowHard": "“{{entry}}” tidak pernah dapat diizinkan. Lihat daftar layanan yang tidak pernah tersedia.", + "allowNone": "Tidak ada yang diizinkan: alat tulis menolak setiap panggilan.", + "allowSummary": "Layanan yang diizinkan", + "class": { + "quiet": "Bertindak jika mode tulis mengizinkan", + "confirm": "Selalu bertanya kepada Anda" + }, + "confirmNote": "Ini memerlukan konfirmasi Anda pada setiap panggilan: {{domains}}.", + "maxTargets": "Entitas terbanyak dalam satu panggilan", + "maxTargetsHint": "Antara 1 dan 20. Panggilan menyebut entitasnya satu per satu: area, perangkat, dan all tidak diterima.", + "targetsShape": "Masukkan bilangan bulat dari 1 sampai 20.", + "excludeTitle": "Apa yang tidak pernah ditampilkan atau disentuh alat", + "excludeIntro": "Entitas yang dikecualikan tidak masuk ke pencarian, status, dan riwayat mana pun, dan tidak ada panggilan yang dapat menyalakan atau mematikannya. Membaca kunci pintu dikecualikan secara bawaan; menghapusnya dari daftar membuat chat dapat membaca apa yang dilaporkan kunci itu, tidak pernah mengoperasikannya.", + "excludeDomains": "Domain entitas", + "excludeDomainsHint": "Satu per baris, misalnya lock, alarm_control_panel, camera.", + "excludeAreas": "Area", + "excludeAreasHint": "Satu per baris, menurut nama. Entitas di area ini disembunyikan. Jika Home Assistant tidak dapat menjawab entitas mana saja itu, alat menolak daripada menebak.", + "excludePatterns": "Pola ID entitas", + "excludePatternsHint": "Satu ekspresi reguler per baris, dicocokkan dengan seluruh ID entitas, misalnya sensor\\..*_bedroom.", + "patternShape": "“{{entry}}” bukan pola yang valid.", + "excludeLabel": "Label sensitif", + "excludeLabelHint": "Entitas yang memiliki label Home Assistant ini disembunyikan. Kosongkan untuk mematikan pemeriksaan.", + "pinsTitle": "Sertifikat", + "pins": "Sertifikat yang disematkan", + "pinsHint": "Untuk sertifikat self-signed: host:port=fingerprint, satu per baris. Fingerprint adalah SHA-256 sertifikat, 64 digit, dengan atau tanpa titik dua. Dapatkan dengan openssl x509 -noout -fingerprint -sha256.", + "pinShape": "“{{entry}}” tidak ditulis dalam format host:port=fingerprint dengan SHA-256 64 digit.", + "save": "Simpan batas", + "restore": "Pulihkan yang disarankan", + "hardTitle": "Tidak pernah tersedia", + "hardIntro": "Add-on menolak ini apa pun pengaturannya, dan tidak ada model yang dapat mengubahnya: kunci pintu, alarm, sirene, katup, pemanas air, kamera, restart dan reload, perintah shell, skrip sistem, serta pengelolaan sistem dan add-on. Pintu garasi, gerbang, pintu, dan penutup dengan jenis tidak dikenal tidak pernah digerakkan.", + "hardCover": "penutup garasi, gerbang, dan pintu" + }, + "approval": { + "title": "Home Assistant: {{action}}", + "targets": "Yang berubah", + "now": "Sekarang: {{state}}", + "as": "Sebagai", + "asOne": "Koneksi {{name}}", + "asFirst": "Koneksi pertama yang mencantumkan alamat ini", + "asUnknown": "Koneksi Home Assistant untuk alamat ini", + "data": "Pengaturan yang dikirim", + "cut": "Ditampilkan terpotong. Seluruh nilai dikirim.", + "physical": "Ini berefek fisik atau menjalankan otomasi di rumah Anda. Terjadi segera setelah Anda mengonfirmasi.", + "quiet": "Ini menyalakan atau mematikan perangkat di rumah Anda segera setelah Anda mengonfirmasi." + } +} diff --git a/addons/home-assistant/ui/src/i18n/home-assistant.it.json b/addons/home-assistant/ui/src/i18n/home-assistant.it.json new file mode 100644 index 0000000000..f594240fac --- /dev/null +++ b/addons/home-assistant/ui/src/i18n/home-assistant.it.json @@ -0,0 +1,140 @@ +{ + "title": "Home Assistant", + "intro": "Permetti alle tue chat e ai tuoi agenti di leggere la tua casa intelligente. Se lo consenti, possono anche azionare dispositivi, entro i limiti che imposti qui sotto.", + "limits": "Questa connessione non risponde ai messaggi. Un agente lavora sulla tua casa solo quando glielo chiedi in una chat.", + "new": "Nuova connessione Home Assistant", + "edit": "Modifica la connessione Home Assistant", + "name": "Nome", + "token": "Token di accesso a lungo termine", + "tokenHelp": "Si crea nel tuo profilo di Home Assistant. Viene cifrato e non viene più mostrato.", + "tokenSaved": "Salvato. Scrivi per sostituirlo.", + "tokenSpace": "Il token non contiene spazi. Copialo di nuovo senza gli spazi attorno.", + "tokenShort": "È troppo corto per essere un token di accesso a lungo termine. Copia il token intero.", + "address": "Indirizzo del tuo Home Assistant", + "addressHelp": "Dove apri Home Assistant in un browser, per esempio https://homeassistant.local:8123. Una connessione è una casa. Ogni altro indirizzo viene rifiutato.", + "addressShape": "Questo non è un indirizzo. Scrivilo come https://nome:8123.", + "addressHttp": "Il semplice http è accettato solo per un indirizzo della tua rete. Per tutto il resto usa https.", + "agent": "Agente", + "agentNone": "Scegli un agente", + "agentCredentialsOnly": "Nessun agente (solo credenziali)", + "agentOptionalHint": "Facoltativo. Senza un agente questa connessione conserva solo il token, l'indirizzo e la modalità di scrittura per le tue chat. Non risponde ad alcun messaggio e non resta in ascolto.", + "credentialsOnly": "Solo credenziali", + "agentNoModel": "{{name}} (serve un modello)", + "test": "Prova", + "testOk": "Il token è salvato.", + "checkFailed": "Salvato. La verifica del token non è riuscita: {{reason}}", + "checkSeveral": "La verifica dell'indirizzo funziona quando hai una sola connessione Home Assistant, perché da questa pagina non si capisce a quale ti riferisci. In una chat, attiva le connessioni nel menu Strumenti.", + "check": { + "title": "Verifica di {{name}}", + "running": "Verifica del tuo Home Assistant...", + "at": "Verificato alle {{time}}", + "ok": "Raggiungibile. Home Assistant {{version}}, {{count}} integrazioni.", + "refused": "Home Assistant ha rifiutato questo token. Controlla che non sia stato eliminato e che appartenga a un utente che vede ciò che vuoi condividere.", + "certificate": "Il certificato non è attendibile. Se è autofirmato, fissa la sua impronta qui sotto.", + "failed": "La verifica non è terminata. Controlla l'indirizzo e riprova tra un momento." + }, + "save": "Salva", + "cancel": "Annulla", + "editButton": "Modifica", + "delete": "Elimina", + "empty": "Ancora nessuna connessione Home Assistant.", + "noAddress": "Nessun indirizzo: questa connessione non raggiunge nulla.", + "addressOf": "Indirizzo: {{address}}", + "agentOf": "Agente: {{name}}", + "inactive": "Non in esecuzione: {{reason}}.", + "confirmDelete": { + "title": "Eliminare {{name}}?", + "message": "Le tue chat non possono più raggiungere questa casa e il token salvato con la connessione viene eliminato. Per configurarla di nuovo dovrai inserire di nuovo il token.", + "confirm": "Elimina" + }, + "setup": { + "title": "Come creare il token", + "step1": "In Home Assistant apri il tuo profilo (il tuo nome in fondo alla barra laterale) e poi la scheda Sicurezza.", + "step2": "In Token di accesso a lungo termine scegli Crea token. Dagli un nome, per esempio LocalAI, e copialo. Home Assistant lo mostra una sola volta.", + "step3": "Incollalo qui e scrivi l'indirizzo con cui apri Home Assistant. Il token ha i diritti dell'utente che lo ha creato: se puoi, crealo con un utente che vede solo ciò che vuoi condividere.", + "step4": "Lascia la modalità di scrittura su Sola lettura finché non hai letto i limiti qui sotto. Il componente aggiuntivo non aziona mai serrature, allarmi, porte del garage o sirene, qualunque cosa scelga.", + "tls": "Home Assistant con un certificato autofirmato: fissa la sua impronta SHA-256 nella sezione qui sotto. Il componente aggiuntivo accetta allora quel solo certificato per quel solo indirizzo e controlla comunque tutto il resto." + }, + "writeMode": { + "label": "Cosa può modificare l'agente nella tua casa", + "hint": "Solo i servizi dell'elenco dei consentiti qui sotto, su entità non escluse.", + "off": "Sola lettura", + "propose": "Chiede prima a te", + "auto": "Agisce senza chiedere", + "line": { + "off": "Non può azionare nulla.", + "propose": "Ogni modifica aspetta il tuo OK.", + "auto": "Le tue chat accendono luci e simili subito." + }, + "note": { + "off": "L'accesso in scrittura è disattivato: questa connessione si limita a leggere.", + "propose": "Ogni modifica aspetta tra le tue approvazioni finché non la confermi.", + "auto": "Una tua chat aziona subito luci, interruttori e contenuti multimediali, senza chiedere. Tutto ciò che ha un effetto fisico, come il riscaldamento o una tapparella, una scena o uno script, aspetta comunque ogni volta la tua conferma. Anche le esecuzioni senza di te presente aspettano." + }, + "row": { + "off": "Sola lettura", + "propose": "Chiede prima a te", + "auto": "Agisce senza chiedere" + } + }, + "policy": { + "title": "Cosa possono fare le chat nella tua casa", + "loading": "Caricamento dei limiti...", + "loadFailed": "Non è stato possibile caricare i limiti.", + "saveFailed": "Non è stato possibile salvare i limiti.", + "intro": "Questi limiti valgono per ogni connessione Home Assistant di questo server. La modalità di scrittura di una connessione decide se una chat può cambiare qualcosa; questi limiti decidono cosa.", + "physicalTitle": "Effetti fisici.", + "physical": "Un servizio che consenti qui può muovere una tapparella, scaldare una stanza, eseguire uno script o inviare un messaggio. Consenti solo ciò che lasceresti fare a un estraneo in casa tua. Ogni servizio che non sia una luce, un interruttore, un input boolean o la riproduzione multimediale aspetta ogni volta la tua conferma, qualunque sia la modalità di scrittura.", + "readOnly": "Solo un amministratore può cambiare questi limiti.", + "broken": "I limiti salvati non si sono potuti leggere, quindi gli strumenti si rifiutano di funzionare. Salva di nuovo i limiti per ripararli.", + "saved": "Salvato. La prossima chiamata usa i nuovi limiti.", + "allowTitle": "Servizi che una chat può chiamare", + "allow": "Elenco dei consentiti", + "allowHint": "Uno per riga, nella forma dominio.servizio, oppure dominio.* per i servizi di un dominio (i servizi di ricarica e riavvio non sono mai inclusi). Ciò che non è in elenco viene rifiutato.", + "allowShape": "«{{entry}}» non è scritto nella forma dominio.servizio o dominio.*.", + "allowHard": "«{{entry}}» non può mai essere consentito. Vedi l'elenco dei servizi mai disponibili.", + "allowNone": "Nulla è consentito: gli strumenti di scrittura rifiutano ogni chiamata.", + "allowSummary": "Servizi consentiti", + "class": { + "quiet": "Agisce quando la modalità di scrittura lo consente", + "confirm": "Chiede sempre a te" + }, + "confirmNote": "Questi richiedono la tua conferma a ogni chiamata: {{domains}}.", + "maxTargets": "Numero massimo di entità in una chiamata", + "maxTargetsHint": "Tra 1 e 20. Una chiamata nomina le sue entità una per una: aree, dispositivi e «tutto» non sono accettati.", + "targetsShape": "Inserisci un numero intero da 1 a 20.", + "excludeTitle": "Ciò che gli strumenti non mostrano né toccano mai", + "excludeIntro": "Le entità escluse restano fuori da ogni ricerca, stato e cronologia, e nessuna chiamata può azionarle. La lettura di una serratura è esclusa per impostazione predefinita; toglierla dall'elenco permette a una chat di leggere ciò che la serratura riporta, mai di azionarla.", + "excludeDomains": "Domini delle entità", + "excludeDomainsHint": "Uno per riga, per esempio lock, alarm_control_panel, camera.", + "excludeAreas": "Aree", + "excludeAreasHint": "Una per riga, per nome. Le entità di queste aree vengono nascoste. Se Home Assistant non sa dire quali entità sono, gli strumenti rifiutano invece di tirare a indovinare.", + "excludePatterns": "Pattern degli ID delle entità", + "excludePatternsHint": "Un'espressione regolare per riga, confrontata con l'intero ID dell'entità, per esempio sensor\\..*_bedroom.", + "patternShape": "«{{entry}}» non è un pattern valido.", + "excludeLabel": "Etichetta sensibile", + "excludeLabelHint": "Le entità con questa etichetta di Home Assistant vengono nascoste. Lasciala vuota per disattivare il controllo.", + "pinsTitle": "Certificati", + "pins": "Certificati fissati", + "pinsHint": "Per un certificato autofirmato: host:porta=impronta, uno per riga. L'impronta è lo SHA-256 del certificato, 64 cifre, con o senza due punti. Ottienila con openssl x509 -noout -fingerprint -sha256.", + "pinShape": "«{{entry}}» non è scritto nella forma host:porta=impronta con uno SHA-256 di 64 cifre.", + "save": "Salva i limiti", + "restore": "Ripristina i consigliati", + "hardTitle": "Mai disponibili", + "hardIntro": "Il componente aggiuntivo li rifiuta qualunque siano le impostazioni, e nessun modello può cambiarlo: serrature, allarmi, sirene, valvole, scaldacqua, telecamere, riavvii e ricariche, comandi di shell, script di sistema, gestione del sistema e dei componenti aggiuntivi. Porte del garage, cancelli, porte e coperture di tipo sconosciuto non vengono mai mosse.", + "hardCover": "coperture di garage, cancelli e porte" + }, + "approval": { + "title": "Home Assistant: {{action}}", + "targets": "Cosa cambia", + "now": "Ora: {{state}}", + "as": "Come", + "asOne": "La connessione {{name}}", + "asFirst": "La prima connessione che elenca questo indirizzo", + "asUnknown": "Una connessione Home Assistant per questo indirizzo", + "data": "Impostazioni inviate", + "cut": "Mostrato troncato. Viene inviato il valore intero.", + "physical": "Questo ha un effetto fisico o avvia un'automazione nella tua casa. Succede appena confermi.", + "quiet": "Questo aziona dispositivi nella tua casa appena confermi." + } +} diff --git a/addons/home-assistant/ui/src/i18n/home-assistant.ko.json b/addons/home-assistant/ui/src/i18n/home-assistant.ko.json new file mode 100644 index 0000000000..7286439454 --- /dev/null +++ b/addons/home-assistant/ui/src/i18n/home-assistant.ko.json @@ -0,0 +1,140 @@ +{ + "title": "Home Assistant", + "intro": "채팅과 에이전트가 스마트 홈을 읽을 수 있게 합니다. 허용하면 아래에서 정한 한도 안에서 기기를 켜고 끌 수도 있습니다.", + "limits": "이 연결은 메시지에 답변하지 않습니다. 에이전트는 채팅에서 요청할 때만 집에서 작업합니다.", + "new": "새 Home Assistant 연결", + "edit": "Home Assistant 연결 편집", + "name": "이름", + "token": "장기 액세스 토큰", + "tokenHelp": "Home Assistant 프로필에서 만듭니다. 봉인되어 보관되며 다시 표시되지 않습니다.", + "tokenSaved": "저장됨. 교체하려면 토큰을 입력하세요.", + "tokenSpace": "토큰에는 공백이 없습니다. 앞뒤 공백 없이 다시 복사하세요.", + "tokenShort": "장기 액세스 토큰으로 보기에는 너무 짧습니다. 토큰 전체를 복사하세요.", + "address": "Home Assistant 주소", + "addressHelp": "브라우저에서 Home Assistant를 여는 주소입니다. 예: https://homeassistant.local:8123. 연결 하나가 집 하나입니다. 다른 주소는 거부됩니다.", + "addressShape": "주소가 아닙니다. https://name:8123처럼 입력하세요.", + "addressHttp": "일반 http는 내 네트워크 안의 주소에만 허용됩니다. 그 외에는 https를 사용하세요.", + "agent": "에이전트", + "agentNone": "에이전트 고르기", + "agentCredentialsOnly": "에이전트 없음(자격 증명만)", + "agentOptionalHint": "선택 사항입니다. 에이전트가 없으면 이 연결은 토큰, 주소, 내 채팅의 쓰기 모드만 보관합니다. 메시지에 답변하지 않고 아무것도 수신하지 않습니다.", + "credentialsOnly": "자격 증명만", + "agentNoModel": "{{name}}(모델 필요)", + "test": "테스트", + "testOk": "토큰이 저장되었습니다.", + "checkFailed": "저장했습니다. 토큰 확인에 실패했습니다: {{reason}}", + "checkSeveral": "이 페이지의 확인으로는 어느 연결인지 알 수 없으므로, 주소 확인은 Home Assistant 연결이 하나일 때만 실행됩니다. 채팅에서는 도구 메뉴에서 연결을 켜세요.", + "check": { + "title": "{{name}} 확인", + "running": "Home Assistant를 확인하는 중...", + "at": "{{time}}에 확인함", + "ok": "접속 가능. Home Assistant {{version}}, 통합 구성요소 {{count}}개.", + "refused": "Home Assistant가 이 토큰을 거부했습니다. 삭제되지 않았는지, 공유하려는 항목을 볼 수 있는 사용자의 토큰인지 확인하세요.", + "certificate": "신뢰할 수 없는 인증서입니다. 자체 서명 인증서라면 아래에서 지문을 고정하세요.", + "failed": "확인이 끝나지 않았습니다. 주소를 확인하고 잠시 후 다시 시도하세요." + }, + "save": "저장", + "cancel": "취소", + "editButton": "편집", + "delete": "삭제", + "empty": "아직 Home Assistant 연결이 없습니다.", + "noAddress": "주소가 없습니다. 이 연결은 어디에도 접속하지 않습니다.", + "addressOf": "주소: {{address}}", + "agentOf": "에이전트: {{name}}", + "inactive": "실행 중이 아님: {{reason}}.", + "confirmDelete": { + "title": "{{name}}을(를) 삭제할까요?", + "message": "채팅이 더 이상 이 집에 접속할 수 없고, 함께 저장된 토큰이 삭제됩니다. 다시 설정하려면 토큰을 다시 입력해야 합니다.", + "confirm": "삭제" + }, + "setup": { + "title": "토큰 만드는 방법", + "step1": "Home Assistant에서 프로필(사이드바 아래쪽의 내 이름)을 열고 보안 탭으로 이동하세요.", + "step2": "장기 액세스 토큰에서 토큰 만들기를 고르세요. 이름(예: LocalAI)을 정하고 복사하세요. Home Assistant는 토큰을 한 번만 보여 줍니다.", + "step3": "여기에 붙여 넣고 Home Assistant를 여는 주소를 입력하세요. 토큰은 만든 사용자의 권한을 가집니다. 가능하면 공유하려는 항목만 볼 수 있는 사용자로 만드세요.", + "step4": "아래의 한도를 읽기 전에는 쓰기 모드를 읽기 전용으로 두세요. 어떤 설정을 고르든 이 애드온은 잠금장치, 경보, 차고 문, 사이렌을 조작하지 않습니다.", + "tls": "자체 서명 인증서를 쓰는 Home Assistant라면 아래 섹션에서 SHA-256 지문을 고정하세요. 그러면 애드온이 그 주소에 대해 그 인증서 하나만 받아들이고 나머지는 계속 모두 검사합니다." + }, + "writeMode": { + "label": "에이전트가 집에서 바꿀 수 있는 것", + "hint": "아래 허용 목록의 서비스만, 제외되지 않은 엔티티에 대해서만 가능합니다.", + "off": "읽기 전용", + "propose": "먼저 묻기", + "auto": "묻지 않고 실행", + "line": { + "off": "아무것도 켜고 끌 수 없습니다.", + "propose": "변경마다 내 승인을 기다립니다.", + "auto": "내 채팅은 조명 등을 바로 켜고 끕니다." + }, + "note": { + "off": "쓰기 접근이 꺼져 있습니다. 이 연결은 읽기만 합니다.", + "propose": "변경마다 내가 확인할 때까지 승인 대기열에서 기다립니다.", + "auto": "내 채팅은 묻지 않고 조명, 스위치, 미디어를 바로 켜고 끕니다. 난방이나 블라인드처럼 물리적 영향이 있는 것, 장면, 스크립트는 매번 내 확인을 기다립니다. 내가 없는 상태에서 실행되는 작업도 기다립니다." + }, + "row": { + "off": "읽기 전용", + "propose": "먼저 묻기", + "auto": "묻지 않고 실행" + } + }, + "policy": { + "title": "채팅이 집에서 할 수 있는 일", + "loading": "한도를 불러오는 중...", + "loadFailed": "한도를 불러올 수 없습니다.", + "saveFailed": "한도를 저장할 수 없습니다.", + "intro": "이 한도는 이 서버의 모든 Home Assistant 연결에 적용됩니다. 연결의 쓰기 모드는 채팅이 무언가를 바꿀 수 있는지 여부를 정하고, 이 한도는 무엇을 바꿀 수 있는지를 정합니다.", + "physicalTitle": "물리적 영향.", + "physical": "여기서 허용한 서비스는 블라인드를 움직이거나, 방을 데우거나, 스크립트를 실행하거나, 메시지를 보낼 수 있습니다. 집에 들어온 낯선 사람에게 허용할 만한 것만 허용하세요. 조명, 스위치, input boolean, 미디어 재생이 아닌 서비스는 쓰기 모드와 관계없이 매번 내 확인을 기다립니다.", + "readOnly": "관리자만 이 한도를 바꿀 수 있습니다.", + "broken": "저장된 한도를 읽을 수 없어서 도구가 작동을 거부합니다. 한도를 다시 저장해 복구하세요.", + "saved": "저장했습니다. 다음 호출부터 새 한도가 적용됩니다.", + "allowTitle": "채팅이 호출할 수 있는 서비스", + "allow": "허용 목록", + "allowHint": "한 줄에 하나씩 domain.service 형식으로, 또는 도메인의 서비스 전체는 domain.*로 입력합니다(reload와 restart 서비스는 포함되지 않음). 나열되지 않은 것은 거부됩니다.", + "allowShape": "\"{{entry}}\"은(는) domain.service 또는 domain.* 형식이 아닙니다.", + "allowHard": "\"{{entry}}\"은(는) 허용할 수 없습니다. 사용할 수 없는 서비스 목록을 확인하세요.", + "allowNone": "허용된 것이 없습니다. 쓰기 도구가 모든 호출을 거부합니다.", + "allowSummary": "허용된 서비스", + "class": { + "quiet": "쓰기 모드가 허용하면 실행", + "confirm": "항상 먼저 묻기" + }, + "confirmNote": "다음은 호출할 때마다 내 확인이 필요합니다: {{domains}}.", + "maxTargets": "한 번의 호출에 포함할 수 있는 최대 엔티티 수", + "maxTargetsHint": "1에서 20 사이입니다. 호출은 엔티티를 하나씩 지정해야 하며, 영역, 기기, all은 사용할 수 없습니다.", + "targetsShape": "1부터 20까지의 정수를 입력하세요.", + "excludeTitle": "도구가 절대 보여 주거나 건드리지 않는 것", + "excludeIntro": "제외된 엔티티는 모든 검색, 상태, 기록에서 빠지며 어떤 호출로도 조작할 수 없습니다. 잠금장치 읽기는 기본적으로 제외됩니다. 목록에서 빼면 채팅이 잠금장치가 보고하는 상태를 읽을 수 있지만 조작할 수는 없습니다.", + "excludeDomains": "엔티티 도메인", + "excludeDomainsHint": "한 줄에 하나씩. 예: lock, alarm_control_panel, camera.", + "excludeAreas": "영역", + "excludeAreasHint": "한 줄에 하나씩 이름으로 입력합니다. 이 영역의 엔티티는 숨겨집니다. Home Assistant가 어떤 엔티티인지 답하지 못하면 도구는 추측하지 않고 거부합니다.", + "excludePatterns": "엔티티 ID 패턴", + "excludePatternsHint": "한 줄에 정규식 하나씩, 엔티티 ID 전체와 대조합니다. 예: sensor\\..*_bedroom.", + "patternShape": "\"{{entry}}\"은(는) 올바른 패턴이 아닙니다.", + "excludeLabel": "민감 라벨", + "excludeLabelHint": "이 Home Assistant 라벨이 붙은 엔티티는 숨겨집니다. 비워 두면 이 검사를 끕니다.", + "pinsTitle": "인증서", + "pins": "고정된 인증서", + "pinsHint": "자체 서명 인증서용: host:port=지문 형식으로 한 줄에 하나씩. 지문은 인증서의 SHA-256 64자리이며 콜론은 있어도 없어도 됩니다. openssl x509 -noout -fingerprint -sha256으로 얻을 수 있습니다.", + "pinShape": "\"{{entry}}\"은(는) host:port=지문(SHA-256 64자리) 형식이 아닙니다.", + "save": "한도 저장", + "restore": "권장값 복원", + "hardTitle": "사용할 수 없는 항목", + "hardIntro": "설정과 관계없이 애드온은 다음을 거부하며 어떤 모델도 이를 바꿀 수 없습니다: 잠금장치, 경보, 사이렌, 밸브, 온수기, 카메라, 재시작과 reload, 셸 명령, 시스템 스크립트, 시스템 및 애드온 관리. 차고 문, 대문, 문, 종류를 알 수 없는 덮개는 절대 움직이지 않습니다.", + "hardCover": "차고, 대문, 문 덮개" + }, + "approval": { + "title": "Home Assistant: {{action}}", + "targets": "바뀌는 것", + "now": "현재: {{state}}", + "as": "사용할 연결", + "asOne": "연결 {{name}}", + "asFirst": "이 주소를 나열한 첫 번째 연결", + "asUnknown": "이 주소용 Home Assistant 연결", + "data": "전송되는 설정", + "cut": "잘려서 표시됩니다. 전체 값이 전송됩니다.", + "physical": "집에 물리적 영향을 주거나 자동화를 실행합니다. 확인하는 즉시 실행됩니다.", + "quiet": "확인하는 즉시 집의 기기를 켜거나 끕니다." + } +} diff --git a/addons/home-assistant/ui/src/i18n/home-assistant.pt-BR.json b/addons/home-assistant/ui/src/i18n/home-assistant.pt-BR.json new file mode 100644 index 0000000000..17fb2f324f --- /dev/null +++ b/addons/home-assistant/ui/src/i18n/home-assistant.pt-BR.json @@ -0,0 +1,140 @@ +{ + "title": "Home Assistant", + "intro": "Deixe seus chats e agentes lerem a sua casa inteligente. Se você permitir, eles também podem ligar e desligar coisas, dentro dos limites que você definir abaixo.", + "limits": "Esta conexão não responde a mensagens. Um agente só trabalha na sua casa quando você pede em um chat.", + "new": "Nova conexão do Home Assistant", + "edit": "Editar conexão do Home Assistant", + "name": "Nome", + "token": "Token de acesso de longa duração", + "tokenHelp": "Criado no seu perfil do Home Assistant. É guardado com segurança e nunca mais é mostrado.", + "tokenSaved": "Salvo. Digite um token para substituí-lo.", + "tokenSpace": "O token não tem espaços. Copie-o de novo sem os espaços ao redor.", + "tokenShort": "Isto é curto demais para ser um token de acesso de longa duração. Copie o token inteiro.", + "address": "Endereço do seu Home Assistant", + "addressHelp": "Onde você abre o Home Assistant no navegador, por exemplo https://homeassistant.local:8123. Uma conexão é uma casa. Qualquer outro endereço é recusado.", + "addressShape": "Isto não é um endereço. Escreva como https://nome:8123.", + "addressHttp": "http simples só é aceito para um endereço na sua própria rede. Use https para qualquer outro.", + "agent": "Agente", + "agentNone": "Escolha um agente", + "agentCredentialsOnly": "Sem agente (só credenciais)", + "agentOptionalHint": "Opcional. Sem um agente, esta conexão só guarda o token, o endereço e o modo de escrita dos seus chats. Ela não responde a mensagens e não escuta nada.", + "credentialsOnly": "Só credenciais", + "agentNoModel": "{{name}} (precisa de um modelo)", + "test": "Testar", + "testOk": "O token está salvo.", + "checkFailed": "Salvo. A verificação do token falhou: {{reason}}", + "checkSeveral": "A verificação do endereço roda quando você tem uma única conexão do Home Assistant, porque uma verificação feita desta página não sabe de qual você fala. Em um chat, ative as conexões no menu Ferramentas.", + "check": { + "title": "Verificação de {{name}}", + "running": "Verificando o seu Home Assistant...", + "at": "Verificado às {{time}}", + "ok": "Acessível. Home Assistant {{version}}, {{count}} integrações.", + "refused": "O Home Assistant recusou este token. Confira se ele não foi excluído e se pertence a um usuário que enxerga o que você quer compartilhar.", + "certificate": "O certificado não é confiável. Se for autoassinado, fixe a impressão digital dele abaixo.", + "failed": "A verificação não terminou. Confira o endereço e tente de novo em instantes." + }, + "save": "Salvar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Excluir", + "empty": "Ainda não há conexões do Home Assistant.", + "noAddress": "Sem endereço: esta conexão não alcança nada.", + "addressOf": "Endereço: {{address}}", + "agentOf": "Agente: {{name}}", + "inactive": "Não está rodando: {{reason}}.", + "confirmDelete": { + "title": "Excluir {{name}}?", + "message": "Seus chats não alcançam mais esta casa, e o token salvo com a conexão é excluído. Para configurá-la de novo, você precisará informar o token outra vez.", + "confirm": "Excluir" + }, + "setup": { + "title": "Como criar o token", + "step1": "No Home Assistant, abra o seu perfil (seu nome na parte de baixo da barra lateral) e depois a aba Segurança.", + "step2": "Em Tokens de acesso de longa duração, escolha Criar token. Dê um nome, por exemplo LocalAI, e copie-o. O Home Assistant o mostra só uma vez.", + "step3": "Cole-o aqui e escreva o endereço que você usa para abrir o Home Assistant. O token tem os direitos do usuário que o criou: se puder, crie-o com um usuário que enxergue só o que você quer compartilhar.", + "step4": "Deixe o modo de escrita em Somente leitura até ler os limites abaixo. O complemento nunca opera fechaduras, alarmes, portas de garagem nem sirenes, seja qual for a sua escolha.", + "tls": "Home Assistant com certificado autoassinado: fixe a impressão digital SHA-256 dele na seção abaixo. O complemento então aceita esse certificado para esse endereço e continua verificando todo o resto." + }, + "writeMode": { + "label": "O que o agente pode alterar na sua casa", + "hint": "Só os serviços da lista de permitidos abaixo, em entidades que não estejam excluídas.", + "off": "Somente leitura", + "propose": "Pergunta antes", + "auto": "Age sem perguntar", + "line": { + "off": "Ele não pode ligar nem desligar nada.", + "propose": "Cada alteração espera o seu OK.", + "auto": "Seus chats ligam luzes e coisas parecidas na hora." + }, + "note": { + "off": "O acesso de escrita está desligado: esta conexão só lê.", + "propose": "Cada alteração espera nas suas aprovações até você confirmar.", + "auto": "Um chat seu liga e desliga luzes, interruptores e mídia na hora, sem perguntar. Tudo o que tem efeito físico, como aquecimento ou uma persiana, uma cena ou um script, ainda espera a sua confirmação toda vez. Execuções sem você presente também esperam." + }, + "row": { + "off": "Somente leitura", + "propose": "Pergunta antes", + "auto": "Age sem perguntar" + } + }, + "policy": { + "title": "O que os chats podem fazer na sua casa", + "loading": "Carregando os limites...", + "loadFailed": "Não foi possível carregar os limites.", + "saveFailed": "Não foi possível salvar os limites.", + "intro": "Estes limites valem para toda conexão do Home Assistant deste servidor. O modo de escrita de uma conexão decide se um chat pode alterar alguma coisa; estes limites decidem o quê.", + "physicalTitle": "Efeitos físicos.", + "physical": "Um serviço que você permitir aqui pode mover uma persiana, aquecer um cômodo, rodar um script ou enviar uma mensagem. Permita só o que você deixaria um estranho fazer na sua casa. Todo serviço que não seja uma luz, um interruptor, um input boolean ou reprodução de mídia espera a sua confirmação toda vez, seja qual for o modo de escrita.", + "readOnly": "Só um administrador pode alterar estes limites.", + "broken": "Não foi possível ler os limites salvos, então as ferramentas se recusam a funcionar. Salve os limites de novo para repará-los.", + "saved": "Salvo. A próxima chamada usa os novos limites.", + "allowTitle": "Serviços que um chat pode chamar", + "allow": "Lista de permitidos", + "allowHint": "Um por linha, como domain.service, ou domain.* para os serviços de um domínio (os serviços reload e restart nunca entram). O que não estiver na lista é recusado.", + "allowShape": "\"{{entry}}\" não está escrito como domain.service nem domain.*.", + "allowHard": "\"{{entry}}\" nunca pode ser permitido. Veja a lista de serviços que nunca estão disponíveis.", + "allowNone": "Nada é permitido: as ferramentas de escrita recusam toda chamada.", + "allowSummary": "Serviços permitidos", + "class": { + "quiet": "Age quando o modo de escrita permite", + "confirm": "Sempre pergunta a você" + }, + "confirmNote": "Estes precisam da sua confirmação em toda chamada: {{domains}}.", + "maxTargets": "Máximo de entidades em uma chamada", + "maxTargetsHint": "Entre 1 e 20. Uma chamada nomeia as entidades uma a uma: áreas, dispositivos e all não são aceitos.", + "targetsShape": "Digite um número inteiro de 1 a 20.", + "excludeTitle": "O que as ferramentas nunca mostram nem tocam", + "excludeIntro": "As entidades excluídas ficam fora de toda busca, estado e histórico, e nenhuma chamada pode acioná-las. Ler uma fechadura é excluído por padrão; tirá-la da lista deixa um chat ler o que a fechadura informa, nunca operá-la.", + "excludeDomains": "Domínios de entidades", + "excludeDomainsHint": "Um por linha, por exemplo lock, alarm_control_panel, camera.", + "excludeAreas": "Áreas", + "excludeAreasHint": "Uma por linha, pelo nome. As entidades dessas áreas ficam ocultas. Se o Home Assistant não souber responder quais são essas entidades, as ferramentas se recusam em vez de adivinhar.", + "excludePatterns": "Padrões de ID de entidade", + "excludePatternsHint": "Uma expressão regular por linha, comparada com o ID inteiro da entidade, por exemplo sensor\\..*_bedroom.", + "patternShape": "\"{{entry}}\" não é um padrão válido.", + "excludeLabel": "Label sensível", + "excludeLabelHint": "As entidades que têm esta label do Home Assistant ficam ocultas. Deixe vazio para desligar a verificação.", + "pinsTitle": "Certificados", + "pins": "Certificados fixados", + "pinsHint": "Para um certificado autoassinado: host:port=fingerprint, um por linha. A impressão digital é o SHA-256 do certificado, 64 dígitos, com ou sem dois-pontos. Obtenha com openssl x509 -noout -fingerprint -sha256.", + "pinShape": "\"{{entry}}\" não está escrito como host:port=fingerprint com um SHA-256 de 64 dígitos.", + "save": "Salvar limites", + "restore": "Restaurar o recomendado", + "hardTitle": "Nunca disponíveis", + "hardIntro": "O complemento recusa estes seja qual for a configuração, e nenhum modelo pode mudar isso: fechaduras, alarmes, sirenes, válvulas, aquecedores de água, câmeras, reinicializações e recarregamentos, comandos de shell, scripts do sistema, e gerenciamento do sistema e de complementos. Portas de garagem, portões, portas e coberturas de tipo desconhecido nunca são movidos.", + "hardCover": "coberturas de garagem, portão e porta" + }, + "approval": { + "title": "Home Assistant: {{action}}", + "targets": "O que muda", + "now": "Agora: {{state}}", + "as": "Como", + "asOne": "A conexão {{name}}", + "asFirst": "A primeira conexão que lista este endereço", + "asUnknown": "Uma conexão do Home Assistant para este endereço", + "data": "Configurações enviadas", + "cut": "Mostrado cortado. O valor inteiro é enviado.", + "physical": "Isto tem efeito físico ou executa uma automação na sua casa. Acontece assim que você confirmar.", + "quiet": "Isto liga e desliga coisas na sua casa assim que você confirmar." + } +} diff --git a/addons/home-assistant/ui/src/i18n/home-assistant.zh-CN.json b/addons/home-assistant/ui/src/i18n/home-assistant.zh-CN.json new file mode 100644 index 0000000000..1244c332aa --- /dev/null +++ b/addons/home-assistant/ui/src/i18n/home-assistant.zh-CN.json @@ -0,0 +1,140 @@ +{ + "title": "Home Assistant", + "intro": "让你的聊天和智能体读取你的智能家居。如果你允许,它们还可以在你下面设定的范围内控制设备开关。", + "limits": "此连接不回答消息。只有当你在聊天中要求时,智能体才会操作你的家居。", + "new": "新建 Home Assistant 连接", + "edit": "编辑 Home Assistant 连接", + "name": "名称", + "token": "长期访问 token", + "tokenHelp": "在你的 Home Assistant 个人资料中创建。它会被加密保存,之后不再显示。", + "tokenSaved": "已保存。输入 token 可替换它。", + "tokenSpace": "token 不含空格。请重新复制,不要带前后的空格。", + "tokenShort": "这太短了,不像是长期访问 token。请复制完整的 token。", + "address": "你的 Home Assistant 地址", + "addressHelp": "你在浏览器中打开 Home Assistant 所用的地址,例如 https://homeassistant.local:8123。一个连接对应一个家。其他地址一律拒绝。", + "addressShape": "这不是地址。请写成 https://name:8123 的形式。", + "addressHttp": "只有你自己网络中的地址才接受普通 http。其他情况请使用 https。", + "agent": "智能体", + "agentNone": "选择智能体", + "agentCredentialsOnly": "没有智能体(仅凭据)", + "agentOptionalHint": "可选。没有智能体时,此连接只保存 token、地址和用于你的聊天的写入模式。它不回答任何消息,也不监听任何内容。", + "credentialsOnly": "仅凭据", + "agentNoModel": "{{name}}(需要模型)", + "test": "测试", + "testOk": "token 已保存。", + "checkFailed": "已保存。token 检查失败:{{reason}}", + "checkSeveral": "地址检查只在你有一个 Home Assistant 连接时运行,因为从此页面发起的检查无法判断你指的是哪一个。在聊天中,请通过“工具”菜单启用这些连接。", + "check": { + "title": "{{name}} 的检查结果", + "running": "正在检查你的 Home Assistant...", + "at": "检查时间:{{time}}", + "ok": "可访问。Home Assistant {{version}},{{count}} 个集成。", + "refused": "Home Assistant 拒绝了此 token。请检查它是否已被删除,以及它所属的用户是否能看到你想共享的内容。", + "certificate": "证书不受信任。如果是自签名证书,请在下面固定它的指纹。", + "failed": "检查未完成。请检查地址,稍后再试。" + }, + "save": "保存", + "cancel": "取消", + "editButton": "编辑", + "delete": "删除", + "empty": "还没有 Home Assistant 连接。", + "noAddress": "没有地址:此连接哪里也到不了。", + "addressOf": "地址:{{address}}", + "agentOf": "智能体:{{name}}", + "inactive": "未运行:{{reason}}。", + "confirmDelete": { + "title": "删除 {{name}}?", + "message": "你的聊天将无法再访问这个家,随之保存的 token 也会被删除。若要重新设置,你必须再次输入 token。", + "confirm": "删除" + }, + "setup": { + "title": "如何创建 token", + "step1": "在 Home Assistant 中,打开你的个人资料(侧边栏底部你的名字),然后进入“安全”标签页。", + "step2": "在“长期访问令牌”下选择“创建令牌”。为它命名,例如 LocalAI,然后复制。Home Assistant 只显示一次。", + "step3": "把它粘贴到这里,并填写你打开 Home Assistant 所用的地址。token 拥有创建它的用户的权限:如果可以,请用只能看到你想共享内容的用户来创建。", + "step4": "在读完下面的限制之前,请让写入模式保持“只读”。无论你如何选择,此附加组件都不会操作门锁、警报、车库门或警笛。", + "tls": "使用自签名证书的 Home Assistant:请在下方部分固定它的 SHA-256 指纹。之后此附加组件只会为该地址接受这一个证书,其他内容仍会照常检查。" + }, + "writeMode": { + "label": "智能体可以更改你家中的什么", + "hint": "只限于下方允许名单中的服务,且作用于未被排除的实体。", + "off": "只读", + "propose": "先问你", + "auto": "不经询问直接执行", + "line": { + "off": "它不能控制任何开关。", + "propose": "每项更改都等待你的确认。", + "auto": "你的聊天会立即控制灯光等设备。" + }, + "note": { + "off": "写入权限已关闭:此连接只读取。", + "propose": "每项更改都在你的审批中等待,直到你确认。", + "auto": "你的聊天会立即控制灯光、开关和媒体,不会询问。任何有物理影响的操作,例如供暖或窗帘、场景或脚本,每次仍然要等待你的确认。你不在场时的运行也要等待。" + }, + "row": { + "off": "只读", + "propose": "先问你", + "auto": "不经询问直接执行" + } + }, + "policy": { + "title": "聊天可以在你家中做什么", + "loading": "正在加载限制...", + "loadFailed": "无法加载限制。", + "saveFailed": "无法保存限制。", + "intro": "这些限制适用于此服务器上的每个 Home Assistant 连接。连接的写入模式决定聊天是否可以更改任何内容;这些限制决定可以更改什么。", + "physicalTitle": "物理影响。", + "physical": "你在这里允许的服务可能会移动窗帘、给房间加热、运行脚本或发送消息。只允许那些你愿意让陌生人在你家里做的事。凡是不属于灯、开关、input boolean 或媒体播放的服务,无论写入模式如何,每次都要等待你的确认。", + "readOnly": "只有管理员可以更改这些限制。", + "broken": "无法读取已保存的限制,因此工具拒绝工作。请重新保存限制以修复。", + "saved": "已保存。下一次调用将使用新的限制。", + "allowTitle": "聊天可以调用的服务", + "allow": "允许名单", + "allowHint": "每行一个,格式为 domain.service,或用 domain.* 表示某个域的所有服务(reload 和 restart 服务永远不包含在内)。未列出的一律拒绝。", + "allowShape": "“{{entry}}”的格式不是 domain.service 或 domain.*。", + "allowHard": "“{{entry}}”永远不能被允许。请查看永不可用的服务列表。", + "allowNone": "没有允许任何内容:写入工具会拒绝每一次调用。", + "allowSummary": "已允许的服务", + "class": { + "quiet": "写入模式允许时直接执行", + "confirm": "始终询问你" + }, + "confirmNote": "这些每次调用都需要你的确认:{{domains}}。", + "maxTargets": "单次调用的最大实体数", + "maxTargetsHint": "1 到 20 之间。调用需要逐个指明实体:不接受区域、设备和 all。", + "targetsShape": "请输入 1 到 20 的整数。", + "excludeTitle": "工具永远不会显示或触碰的内容", + "excludeIntro": "被排除的实体不会出现在任何搜索、状态和历史中,任何调用也无法控制它们。读取门锁默认被排除;把它从列表中移除后,聊天可以读取门锁报告的状态,但永远不能操作它。", + "excludeDomains": "实体域", + "excludeDomainsHint": "每行一个,例如 lock、alarm_control_panel、camera。", + "excludeAreas": "区域", + "excludeAreasHint": "每行一个,按名称填写。这些区域的实体会被隐藏。如果 Home Assistant 无法回答它们是哪些实体,工具会拒绝而不是猜测。", + "excludePatterns": "实体 ID 模式", + "excludePatternsHint": "每行一个正则表达式,与完整的实体 ID 匹配,例如 sensor\\..*_bedroom。", + "patternShape": "“{{entry}}”不是有效的模式。", + "excludeLabel": "敏感标签", + "excludeLabelHint": "带有此 Home Assistant 标签的实体会被隐藏。留空则关闭此检查。", + "pinsTitle": "证书", + "pins": "已固定的证书", + "pinsHint": "用于自签名证书:host:port=指纹,每行一个。指纹是证书的 SHA-256,共 64 位数字,可带冒号也可不带。可用 openssl x509 -noout -fingerprint -sha256 获取。", + "pinShape": "“{{entry}}”的格式不是带 64 位 SHA-256 的 host:port=fingerprint。", + "save": "保存限制", + "restore": "恢复推荐设置", + "hardTitle": "永不可用", + "hardIntro": "无论设置如何,此附加组件都会拒绝这些,任何模型都无法改变:门锁、警报、警笛、阀门、热水器、摄像头、重启和重新加载、shell 命令、系统脚本,以及系统和附加组件管理。车库门、大门、门以及类型未知的窗帘永远不会被移动。", + "hardCover": "车库门、大门和门类窗帘" + }, + "approval": { + "title": "Home Assistant:{{action}}", + "targets": "将更改的内容", + "now": "当前:{{state}}", + "as": "身份", + "asOne": "连接 {{name}}", + "asFirst": "第一个列出此地址的连接", + "asUnknown": "用于此地址的 Home Assistant 连接", + "data": "发送的设置", + "cut": "显示的内容已被截断。会发送完整的值。", + "physical": "这会产生物理影响,或在你家中运行自动化。你一确认就会执行。", + "quiet": "你一确认,家中的设备就会被切换。" + } +} diff --git a/addons/internal/connector-ui/src/AccessView.jsx b/addons/internal/connector-ui/src/AccessView.jsx index 0488e3eea7..7122d56e1d 100644 --- a/addons/internal/connector-ui/src/AccessView.jsx +++ b/addons/internal/connector-ui/src/AccessView.jsx @@ -4,20 +4,7 @@ import { useCallback, useEffect, useState } from 'react' import { Notice } from './controls' import { connectorsApi } from './api' - -export const ACCESS_TEXT = { - 'access.title': 'Latest decisions', - 'access.needSave': 'Save this bot first to see its latest decisions.', - 'access.refresh': 'Refresh', - 'access.recentTitle': 'Latest decisions', - 'access.recentHelp': 'The latest checks of this server, newest first. They hold IDs and names of permissions, never message text. They reset when the server restarts.', - 'access.recentNone': 'No decision yet.', - 'access.allowed': 'allowed', - 'access.denied': 'denied', - 'access.who': 'sender {{senders}}', - 'access.roles': 'roles {{roles}}', - 'access.via': 'via {{via}}', -} +import { pageLocale } from './connector' export default function AccessView({ binding, capabilities, s, version }) { const [view, setView] = useState(null) @@ -47,7 +34,7 @@ export default function AccessView({ binding, capabilities, s, version }) {
  • {s(e.allowed ? 'access.allowed' : 'access.denied')} {title(e.capability)} - {new Date(e.at).toLocaleString([], { dateStyle: 'short', timeStyle: 'medium' })} + {new Date(e.at).toLocaleString(pageLocale(), { dateStyle: 'short', timeStyle: 'medium' })} {(e.senders || []).length > 0 && {s('access.who', { senders: e.senders.join(', ') })}} {(e.roles || []).length > 0 && {s('access.roles', { roles: e.roles.join(', ') })}} {e.via && {s('access.via', { via: e.via })}} diff --git a/addons/internal/connector-ui/src/AnswerFields.jsx b/addons/internal/connector-ui/src/AnswerFields.jsx index 8ba64905cf..04776f5012 100644 --- a/addons/internal/connector-ui/src/AnswerFields.jsx +++ b/addons/internal/connector-ui/src/AnswerFields.jsx @@ -4,54 +4,15 @@ // files of a turn go out. The words are the same on every platform, so they // live here as defaults; an add-on can still change one in its namespace. import { Field, Hint, Select, Switch, TextInput } from './controls' -import { APPROVAL_TEXT } from './ApprovalFields' -import { FILES_TEXT } from './FilesFields' -import { PERMISSION_TEXT } from './PermissionFields' -import { PAIRING_TEXT } from './PairingFields' -import { ACCESS_TEXT } from './AccessView' -import { CAN_TEXT } from './CapabilitiesPanel' -import { READY_TEXT } from './ReadinessList' +import EN from './i18n/connector.en.json' import { CONVERSATIONS, MAX_WINDOW_MINUTES, REPLY_MODES } from './connector' -export const ANSWER_TEXT = { - 'answer.title': 'How it answers', - 'answer.replyLabel': 'While the agent works', - 'answer.reply.off': 'Typing, then the answer', - 'answer.reply.progress': 'Progress: the tools it uses', - 'answer.reply.stream': 'Live: the answer as it is written', - 'answer.replyHelp': 'Progress and Live show one working message, and the answer replaces it. On a platform with drafts, Live shows a private chat a draft instead, and the answer follows it. A long answer goes on in new messages.', - 'answer.conversationLabel': 'Conversations', - 'answer.conversationHelp': 'Send /new (or !new) in the app to start a new chat.', - 'answer.conversation.sender': 'One chat per person', - 'answer.conversation.chat': 'One chat per group or channel', - 'answer.conversation.window': 'Per person, new after a quiet time', - 'answer.conversation.thread': 'New chat per message; replies go on', - 'answer.window': 'Quiet time, in minutes', - 'answer.windowRange': 'Use a whole number from 1 to {{max}}.', - 'answer.files': 'Send the files the agent makes', - 'aside.title': 'How it works', - 'aside.step1': 'Give the connection a name and its token. The token is sealed and never shown again.', - 'aside.step2': 'Pick the agent that answers. It needs a model.', - 'aside.step3': 'Choose who may write to it, and how it answers.', - 'aside.testTitle': 'Test the connection', - 'aside.testText': 'When you save a new token, LocalAI checks it and names the account it belongs to. Use Test on a saved row to check it again.', - 'add': 'Add', - 'more': 'More', - 'less': 'Less', - 'answer.filesHelp': 'After the answer, up to 5 files. Programs and scripts are not sent. The size limit is under Files.', - 'answer.summary': 'Answers: {{reply}} · {{conversation}}', - 'answer.short.off': 'typing', - 'answer.short.progress': 'progress', - 'answer.short.stream': 'live answer', - 'answer.short.sender': 'a chat per person', - 'answer.short.chat': 'a chat per group', - 'answer.short.window': 'new chat after {{count}} min', - 'answer.short.thread': 'a chat per message', -} - -// text reads a string of the add-on's namespace, with the default above. +// text reads a string of the add-on's namespace, with the English default of +// the shared bundle (i18n/connector.en.json). The translations of that bundle +// are registered under the namespace "connector", which every page reads after +// its own, so an add-on can still change one word in its namespace. export function answerT(t) { - return (key, opts = {}) => t(key, { defaultValue: ANSWER_TEXT[key] ?? FILES_TEXT[key] ?? APPROVAL_TEXT[key] ?? PERMISSION_TEXT[key] ?? PAIRING_TEXT[key] ?? ACCESS_TEXT[key] ?? CAN_TEXT[key] ?? READY_TEXT[key], ...opts }) + return (key, opts = {}) => t(key, { defaultValue: EN[key] ?? EN[`${key}_other`], ...opts }) } // answerSummary says in a few words how a binding answers, for its row. diff --git a/addons/internal/connector-ui/src/ApprovalFields.jsx b/addons/internal/connector-ui/src/ApprovalFields.jsx index a131e630ef..103bc3e488 100644 --- a/addons/internal/connector-ui/src/ApprovalFields.jsx +++ b/addons/internal/connector-ui/src/ApprovalFields.jsx @@ -7,45 +7,6 @@ import { useId, useState } from 'react' import { Field, RadioCards, Switch, TextInput } from './controls' import { APPROVAL_ACTS, APPROVAL_LIMITS, APPROVAL_MODES, agentHoldsAct, approvalProblem } from './connector' -export const APPROVAL_TEXT = { - 'approval.title': 'Timers and triggers from the chat', - 'approval.help': 'When the agent sets up something that runs later ("in 10 minutes", "every day"), every run is one model run.', - 'approval.mode.ask': 'Ask me: the bot proposes it in the chat and the person answers yes or no', - 'approval.mode.auto': 'Set it up at once and tell the person how to cancel it', - 'approval.mode.auto_readonly': 'At once when the runs only read and report; otherwise ask', - 'approval.limitsHelp': 'These limits bound what is set up without asking. Leave a field empty for its default. Anything over a limit is asked instead.', - 'approval.max_pending': 'Most timers and triggers at once', - 'approval.min_gap_minutes': 'Fewest minutes between runs of a recurring trigger', - 'approval.max_runs_per_day': 'Most runs a day, all together', - 'approval.max_lifetime_days': 'Longest lifetime, in days', - 'approval.invalid': 'Use a whole number from 1 to {{max}}, or leave it empty.', - 'approval.default': 'Default: {{value}}.', - 'approval.summary': 'Triggers: {{mode}}', - 'approval.effect': 'Proposals: {{mode}}', - 'approval.limits': 'Limits', - 'approval.acts.title': 'Runs may act', - 'approval.acts.help': 'Off: the runs of a trigger made from this chat only read and report. On: they may also do what you tick below, and only what the agent has.', - 'approval.acts.modes': 'Ask me: you always decide. At once: only when you are paired as sender and every act is ticked here.', - 'approval.acts.act.files': 'Write files in the agent\'s folders', - 'approval.acts.act.files.effect': 'A run can create and change files the agent keeps.', - 'approval.acts.act.addons': 'Use the actions of the agent\'s add-ons', - 'approval.acts.act.addons.effect': 'A run can use what the add-ons of the agent do, including commands.', - 'approval.acts.act.send': 'Send messages beyond where it reports', - 'approval.acts.act.send.effect': 'A run can write to other chats and channels on its own.', - 'approval.acts.act.spend': 'Use paid or external tools and the network', - 'approval.acts.act.spend.effect': 'A run can call services outside LocalAI, which may cost money.', - 'approval.acts.lacks': 'The agent does not have this.', - 'approval.acts.sensitive': 'Sensitive', - 'approval.effectActs': ', runs may: {{acts}}', - 'approval.actShort.act.files': 'write files', - 'approval.actShort.act.addons': 'use add-ons', - 'approval.actShort.act.send': 'send messages', - 'approval.actShort.act.spend': 'use paid or external tools', - 'approval.short.ask': 'asks first', - 'approval.short.auto': 'set up at once', - 'approval.short.auto_readonly': 'at once if read-only', -} - /** The line under "Propose triggers": the mode, and the acts a run may do. */ export function approvalEffect(a, s) { const base = s('approval.effect', { mode: s(`approval.short.${a.mode || 'ask'}`) }) diff --git a/addons/internal/connector-ui/src/CapabilitiesPanel.jsx b/addons/internal/connector-ui/src/CapabilitiesPanel.jsx index 8d91c05eb5..c59b70ab8c 100644 --- a/addons/internal/connector-ui/src/CapabilitiesPanel.jsx +++ b/addons/internal/connector-ui/src/CapabilitiesPanel.jsx @@ -10,51 +10,6 @@ import { FixNotice, Select } from './controls' import { connectorsApi } from './api' import { fixAction, remainingChecks, sentenceCase, fixLabelKey, pickSubject, previewBody, previewId, previewKey, senderOptions } from './access' -export const CAN_TEXT = { - 'can.title': 'What this bot can do', - 'can.help': 'The check the host runs for each message, run on this form as it is now. It changes nothing. A blocked row shows the first problem; more may follow it.', - 'can.sender': 'If the sender is', - 'can.sender.listed': 'A person on the allowed list', - 'can.sender.channel_member': 'A member of an allowed channel', - 'can.sender.anyone': 'Anyone who writes to the bot', - 'can.sender.paired': 'A person you paired', - 'can.sender.role': 'Role: {{title}}', - 'can.fromForm': 'Computed from the form as it is now. Save to keep these settings.', - 'can.fromFormNew': 'Not saved yet. Computed from the form as it is now.', - 'can.loading': 'Checking what this bot can do', - 'can.stale': 'Updating', - 'can.errorTitle': 'Could not check what this bot can do', - 'can.retry': 'Try again', - 'can.allowed': 'Allowed', - 'can.blocked': 'Blocked', - 'can.summary': '{{allowed}} of {{total}} allowed for this sender.', - 'can.via.paired': 'because this person is paired', - 'can.via.allowlist': 'because this person is on the allowed list', - 'can.via.role': 'through the role {{role}}', - 'can.noPerson': 'No person is on the allowed list yet. This is what a listed person would get.', - 'can.then': 'Not checked yet: {{list}}.', - 'can.then.server': 'the server settings', - 'can.then.tools': 'the Tools switch of the agent', - 'can.then.agent': 'the permissions of the agent', - 'can.then.connection': 'the permissions of this connection', - 'can.then.sender': 'who is sending', - 'can.then.approval': 'how proposals are approved', - 'can.noMessage': 'Not allowed.', - 'fix.open_connection': 'Show the setting', - 'fix.open_agent': 'Open the agent', - 'fix.open_server_setting': 'Open Settings', - 'fix.pair_sender': 'Pair a sender', - 'fix.add_person': 'Add a person', - 'fix.save': 'Keep these changes', - 'fix.token': 'Paste the token', - 'fix.tokenNew': 'Paste a new token', - 'fix.agent': 'Choose an agent', - 'fix.usual': 'Choose permissions', - 'fix.turnOn': 'Turn on', - 'fix.newTab': '(opens in a new tab)', - 'fix.askAdmin': 'Ask an administrator', -} - const FLASH_MS = 1600 // Scrolls to the first of the selectors that exists on the page, focuses it diff --git a/addons/internal/connector-ui/src/ConnectorPage.jsx b/addons/internal/connector-ui/src/ConnectorPage.jsx index b0e0965cd4..0a34d32f86 100644 --- a/addons/internal/connector-ui/src/ConnectorPage.jsx +++ b/addons/internal/connector-ui/src/ConnectorPage.jsx @@ -15,13 +15,17 @@ import ReadinessList from './ReadinessList' import { useAuth } from '../../host-compat/src/context/AuthContext' import ApprovalOptions from './ApprovalFields' import FilesFields from './FilesFields' -import { Check, Field, Notice, RadioCards, Select, Switch, TextInput } from './controls' +import { Check, Field, HintLabels, Notice, RadioCards, Select, Switch, TextInput } from './controls' import NameList from './NameList' import PermissionFields, { NoCapsNotice } from './PermissionFields' import { agentsApi, connectorsApi } from './api' import { canSave, channelOnly, connectorForm, hasNoCaps, connectorPayload, emptyConnector, listEntries, replaceList, senderLine, usualCaps } from './connector' +import { registerConnectorLocales } from './i18n' import './connector.css' +// The shared words, in every language of the web UI, under the namespace "connector". +registerConnectorLocales() + function Senders({ id, onAllowed, t }) { const [list, setList] = useState(null) const [error, setError] = useState('') @@ -37,7 +41,7 @@ function Senders({ id, onAllowed, t }) { {list.map((s) => (
  • {s.allowed ? t('accepted') : t('ignored')} - {senderLine(s)} + {senderLine(s, answerT(t))} {!s.allowed && ( + )} {note && {note}} @@ -253,10 +258,10 @@ export default function ConnectorPage({ kind, ns, testId, TokenFields = SingleTo {(p) => setForm({ ...form, name: e.target.value })} />} - + {(p) => ( } export function Field({ label, hint, error, optional, className = '', children }) { const id = useId() + const labels = useContext(HintLabels) const hintId = hint ? `${id}-hint` : undefined const errorId = error ? `${id}-error` : undefined const describedBy = [hintId, errorId].filter(Boolean).join(' ') || undefined return (
    - + {children({ id, 'aria-describedby': describedBy, 'aria-invalid': error ? 'true' : undefined })} {hint && {hint}} {error && {error}} @@ -36,7 +37,14 @@ export function FieldError({ id, children, ...rest }) { // Help text of at most two lines. When the text is longer, a "More" control // opens the rest, so a long paragraph never pushes the next field down. -export function Hint({ id, children, more = 'More', less = 'Less' }) { +// The labels of the "More" control: a page gives its own words through this +// context, so a hint drawn by a shared field is in the language of the page. +export const HintLabels = createContext({ more: 'More', less: 'Less', optional: 'optional' }) + +export function Hint({ id, children, more, less }) { + const labels = useContext(HintLabels) + more = more ?? labels.more + less = less ?? labels.less const ref = useRef(null) const [open, setOpen] = useState(false) const [clipped, setClipped] = useState(false) diff --git a/addons/internal/connector-ui/src/i18n/connector.de.json b/addons/internal/connector-ui/src/i18n/connector.de.json new file mode 100644 index 0000000000..2f34549adb --- /dev/null +++ b/addons/internal/connector-ui/src/i18n/connector.de.json @@ -0,0 +1,281 @@ +{ + "answer.title": "Wie geantwortet wird", + "answer.replyLabel": "Während der Agent arbeitet", + "answer.reply.off": "Schreibanzeige, dann die Antwort", + "answer.reply.progress": "Fortschritt: die genutzten Werkzeuge", + "answer.reply.stream": "Live: die Antwort beim Schreiben", + "answer.replyHelp": "Fortschritt und Live zeigen eine Arbeitsnachricht, die die Antwort am Ende ersetzt. Auf einer Plattform mit Entwürfen zeigt Live in einem privaten Chat stattdessen einen Entwurf, danach folgt die Antwort. Eine lange Antwort geht in neuen Nachrichten weiter.", + "answer.conversationLabel": "Unterhaltungen", + "answer.conversationHelp": "Sende /new (oder !new) in der App, um einen neuen Chat zu starten.", + "answer.conversation.sender": "Ein Chat pro Person", + "answer.conversation.chat": "Ein Chat pro Gruppe oder Kanal", + "answer.conversation.window": "Pro Person, neu nach einer Pause", + "answer.conversation.thread": "Neuer Chat pro Nachricht; Antworten laufen weiter", + "answer.window": "Pause in Minuten", + "answer.windowRange": "Gib eine ganze Zahl von 1 bis {{max}} ein.", + "answer.files": "Dateien senden, die der Agent erstellt", + "aside.title": "So funktioniert es", + "aside.step1": "Gib der Verbindung einen Namen und ihr Token. Das Token wird versiegelt und nie wieder angezeigt.", + "aside.step2": "Wähle den Agenten, der antwortet. Er braucht ein Modell.", + "aside.step3": "Lege fest, wer schreiben darf und wie geantwortet wird.", + "aside.testTitle": "Verbindung testen", + "aside.testText": "Wenn du ein neues Token speicherst, prüft LocalAI es und nennt das zugehörige Konto. Mit Test in einer gespeicherten Zeile prüfst du es erneut.", + "add": "Hinzufügen", + "more": "Mehr", + "less": "Weniger", + "answer.filesHelp": "Nach der Antwort bis zu 5 Dateien. Programme und Skripte werden nicht gesendet. Das Größenlimit steht unter Dateien.", + "answer.summary": "Antworten: {{reply}} · {{conversation}}", + "answer.short.off": "Schreibanzeige", + "answer.short.progress": "Fortschritt", + "answer.short.stream": "Live-Antwort", + "answer.short.sender": "ein Chat pro Person", + "answer.short.chat": "ein Chat pro Gruppe", + "answer.short.window": "neuer Chat nach {{count}} Min.", + "answer.short.thread": "ein Chat pro Nachricht", + "approval.title": "Timer und Trigger aus dem Chat", + "approval.help": "Richtet der Agent etwas ein, das später läuft („in 10 Minuten“, „jeden Tag“), ist jeder Lauf ein Modelllauf.", + "approval.mode.ask": "Frag mich: Der Bot schlägt es im Chat vor und die Person antwortet mit Ja oder Nein", + "approval.mode.auto": "Sofort einrichten und der Person sagen, wie sie es abbricht", + "approval.mode.auto_readonly": "Sofort, wenn die Läufe nur lesen und berichten; sonst fragen", + "approval.limitsHelp": "Diese Limits begrenzen, was ohne Rückfrage eingerichtet wird. Lass ein Feld leer, um den Standardwert zu nutzen. Alles über einem Limit wird stattdessen erfragt.", + "approval.max_pending": "Höchstzahl gleichzeitiger Timer und Trigger", + "approval.min_gap_minutes": "Mindestabstand in Minuten zwischen Läufen eines wiederkehrenden Triggers", + "approval.max_runs_per_day": "Höchstzahl Läufe pro Tag, insgesamt", + "approval.max_lifetime_days": "Längste Lebensdauer in Tagen", + "approval.invalid": "Gib eine ganze Zahl von 1 bis {{max}} ein oder lass das Feld leer.", + "approval.default": "Standard: {{value}}.", + "approval.summary": "Trigger: {{mode}}", + "approval.effect": "Vorschläge: {{mode}}", + "approval.limits": "Limits", + "approval.acts.title": "Läufe dürfen handeln", + "approval.acts.help": "Aus: Die Läufe eines Triggers, der aus diesem Chat entsteht, lesen und berichten nur. An: Sie dürfen auch tun, was du unten ankreuzt, und nur das, was der Agent hat.", + "approval.acts.modes": "Frag mich: Du entscheidest immer. Sofort: nur wenn du als Absender gekoppelt bist und jede Handlung hier angekreuzt ist.", + "approval.acts.act.files": "Dateien in den Ordnern des Agenten schreiben", + "approval.acts.act.files.effect": "Ein Lauf kann Dateien des Agenten erstellen und ändern.", + "approval.acts.act.addons": "Die Aktionen der Add-ons des Agenten nutzen", + "approval.acts.act.addons.effect": "Ein Lauf kann nutzen, was die Add-ons des Agenten können, auch Befehle.", + "approval.acts.act.send": "Nachrichten über den Berichtsort hinaus senden", + "approval.acts.act.send.effect": "Ein Lauf kann von sich aus in andere Chats und Kanäle schreiben.", + "approval.acts.act.spend": "Kostenpflichtige oder externe Werkzeuge und das Netzwerk nutzen", + "approval.acts.act.spend.effect": "Ein Lauf kann Dienste außerhalb von LocalAI aufrufen, was Geld kosten kann.", + "approval.acts.lacks": "Der Agent hat das nicht.", + "approval.acts.sensitive": "Sensibel", + "approval.effectActs": ", Läufe dürfen: {{acts}}", + "approval.actShort.act.files": "Dateien schreiben", + "approval.actShort.act.addons": "Add-ons nutzen", + "approval.actShort.act.send": "Nachrichten senden", + "approval.actShort.act.spend": "kostenpflichtige oder externe Werkzeuge nutzen", + "approval.short.ask": "fragt zuerst", + "approval.short.auto": "sofort eingerichtet", + "approval.short.auto_readonly": "sofort, wenn nur lesend", + "files.title": "Dateien", + "files.help": "Lass ein Feld leer, um das Limit des Add-ons zu nutzen. Dein Limit kann nur niedriger sein.", + "files.receiveLabel": "Größte Datei, die gelesen wird, in MiB", + "files.sendLabel": "Größte Datei, die gesendet wird, in MiB", + "files.invalid": "Gib eine Zahl größer als 0 ein.", + "files.tooHigh": "Das Add-on erlaubt hier höchstens {{max}}.", + "files.now": "Jetzt: {{limit}} ({{layer}}).", + "files.receiveHelp": "Dateien, die Personen an den Bot senden. Eine größere Datei wird abgelehnt, und der Bot sagt, welches Limit er erreicht hat.", + "files.sendHelp": "Dateien, die der Agent erstellt und der Bot zurückschickt. Eine größere Datei wird nicht gesendet, und der Bot sagt, welches Limit er erreicht hat.", + "files.addon.off": "Dieses Add-on bewegt in dieser Richtung keine Dateien, daher gibt es nichts einzustellen.", + "files.addon.declared": "Das Add-on gibt bis zu {{max}} an.", + "files.addon.open": "Das Add-on gibt kein Limit an, daher gilt das Limit der Plattform.", + "files.host": "Kein Bot kann eine Datei über {{max}} bewegen. Ein Administrator legt das in den Einstellungen fest.", + "files.layer.owner": "für diesen Bot gesetztes Limit", + "files.layer.addon": "Limit des Add-ons", + "files.layer.host": "Limit der Plattform", + "perm.title": "Was Absender tun dürfen", + "perm.help": "Wähle, was die Personen, die diesem Bot schreiben, den Agenten tun lassen dürfen. Was ausgeschaltet ist, bleibt für alle aus, egal was eine Rolle oder eine Kopplung sagt.", + "perm.noCapsTitle": "Keine Berechtigung ist an, daher sind diese Funktionen aus", + "perm.noCapsText": "Dieser Bot wurde gespeichert, bevor es Berechtigungen gab, oder es wurde noch keine gewählt. Solange du keine einschaltest, können Absender dem Agenten keine Dateien senden, keine Trigger einrichten, nichts im Gedächtnis speichern und den Agenten nichts in einem Kanal posten lassen. Nachrichten werden weiterhin beantwortet.", + "perm.usual": "Das Übliche erlauben", + "perm.usualHelp": "Schaltet die alltäglichen Berechtigungen ein, die innerhalb von LocalAI bleiben: {{names}}. Sensible sind nie enthalten.", + "perm.usualDone": "Die üblichen Berechtigungen sind an. Speichere, um sie zu behalten.", + "perm.usualSaved": "Die üblichen Berechtigungen sind für {{name}} gespeichert.", + "perm.usualSet": "Das übliche Set erlauben", + "perm.usualSetHelp": "Kreuzt die alltäglichen Berechtigungen an, die innerhalb von LocalAI bleiben, soweit der Agent sie hat. Sensible sind nie enthalten.", + "perm.sameAs": "Wie der Agent", + "perm.sameAsHelp": "Kopiert die Berechtigungen, die der Agent jetzt hat, auch die sensiblen. Es ist eine Kopie von heute und folgt späteren Änderungen am Agenten nicht.", + "perm.block.noAgent": "Wähle zuerst einen Agenten.", + "perm.block.unreadable": "Die Berechtigungen dieses Agenten konnten nicht gelesen werden.", + "perm.block.nothing": "Dieser Agent hat keine Berechtigung, die eine Verbindung nutzen kann.", + "perm.usualTicked": "{{count}} angekreuzt: {{names}}. Speichere, um sie zu behalten.", + "perm.usualNone": "Nichts Neues anzukreuzen: Die üblichen Berechtigungen, die der Agent hat, sind schon an.", + "perm.usualSkipped": "Nicht angekreuzt, weil der Agent sie nicht hat: {{names}}.", + "perm.sameTicked": "{{count}} Berechtigungen von {{agent}} kopiert: {{names}}. Das ist eine Kopie von heute und folgt späteren Änderungen am Agenten nicht. Speichere, um sie zu behalten.", + "perm.agentLacks": "Der Agent hat das nicht.", + "perm.confirmTitle": "Auch die sensiblen Berechtigungen kopieren?", + "perm.confirmText": "Der Agent hat diese sensiblen Berechtigungen. Sind sie an, kann ein gekoppelter Absender sie über diesen Bot nutzen:", + "perm.confirmYes": "Auch kopieren", + "perm.confirmNo": "Abbrechen", + "perm.rule": "Personen auf der Liste der Erlaubten können die angekreuzten Berechtigungen nutzen. Sensible brauchen zusätzlich, dass die Person gekoppelt ist.", + "perm.stuckTitle": "Sensible Berechtigungen sind angekreuzt, aber niemand ist gekoppelt", + "perm.stuckText": "Eine sensible Berechtigung funktioniert nur für eine Person, die du mit diesem Bot gekoppelt hast. Solange niemand gekoppelt ist, kann kein Absender nutzen: {{names}}.", + "perm.channelOnlyTitle": "Die Liste der Erlaubten enthält nur Kanäle", + "perm.channelOnlyText": "Mitgliedern eines Kanals wird nicht einzeln vertraut, daher können sie die angekreuzten Berechtigungen nicht nutzen. Füge ihr Konto zu den erlaubten Personen hinzu.", + "perm.stuckPair": "Dein Konto koppeln", + "perm.sensitive": "Nur gekoppelte Absender", + "perm.sensitiveHelp": "Sensibel. Wird nie an eine Rolle vergeben. Nur ein von dir gekoppelter Absender kann sie nutzen, und jede Nutzung wird aufgezeichnet.", + "perm.capsLegend": "Berechtigungen dieses Bots", + "perm.capsHelp": "Die Obergrenze für alle. Eine Rolle kann weniger bekommen, nie mehr.", + "perm.rolesTitle": "Rollen", + "perm.rolesHelp": "Die Plattform teilt dem Host mit, welche Rollen ein Absender hat. Wähle, was jede Rolle tun darf. Sensible Berechtigungen gehen nie an eine Rolle, und eine Rolle kann nur eine Berechtigung bekommen, die oben eingeschaltet ist.", + "perm.rolesNone": "Schalte oben eine Berechtigung ein, die dieses Add-on an eine Rolle vergeben kann, dann erscheint sie hier.", + "perm.rolesGrid": "Was jede Rolle tun darf", + "perm.roleCap": "{{role}}: {{cap}}", + "perm.channelsTitle": "Freigegebene Kanäle", + "perm.channelsHelp": "Chats, in die der Agent auf Wunsch eines Absenders posten darf, zusätzlich zum Chat, in dem er antwortet. Für einen nicht freigegebenen Kanal braucht jede Nachricht einen gekoppelten Absender. Höchstens {{max}}.", + "perm.channelsOff": "Schalte oben „In freigegebene Kanäle posten“ ein, um Kanäle zu wählen.", + "perm.channelId": "Kanal-ID", + "perm.channelIdHelp": "Die ID, die die Plattform dem Chat gibt. Ohne Leerzeichen.", + "perm.channelLabel": "Name", + "perm.channelLabelHelp": "Ein Name für dich und den Agenten. Optional.", + "perm.channelApproved": "Freigegeben", + "perm.channelApprovedHelp": "An: Der Agent darf hier auf Wunsch jedes erlaubten Absenders posten.", + "perm.channelAdd": "Kanal hinzufügen", + "perm.channelRemove": "Kanal {{id}} entfernen", + "perm.remove": "Entfernen", + "perm.channelFull": "Die Liste ist voll.", + "perm.channelProblem.id": "Jeder Kanal braucht eine ID von 1 bis {{max}} Bytes ohne Leerzeichen.", + "perm.channelProblem.label": "Ein Name hat höchstens {{max}} Zeichen.", + "perm.channelProblem.repeat": "Ein Kanal steht doppelt in der Liste.", + "perm.channelProblem.count": "Höchstens {{max}} Kanäle.", + "perm.triggersNeed": "Schalte „Trigger vorschlagen“ ein, um das zu nutzen.", + "pair.title": "Gekoppelte Absender", + "pair.help": "Ein gekoppelter Absender hat bewiesen, dass er ein erlaubtes Konto kontrolliert. Nur ein gekoppelter Absender kann die sensiblen Berechtigungen nutzen. Höchstens {{max}}; eine Kopplung endet nach {{days}} Tagen.", + "pair.start": "Absender koppeln", + "pair.startHelp": "Erzeugt einen Code. Sende ihn innerhalb von zehn Minuten als Direktnachricht von dem zu koppelnden Konto an den Bot. Der Code gilt einmal.", + "pair.needSave": "Speichere zuerst diesen Bot, dann koppele einen Absender.", + "pair.codeTitle": "Dein Kopplungscode", + "pair.codeHelp": "Sende diesen Code als Direktnachricht von dem zu koppelnden Konto an den Bot. Er wird nur jetzt angezeigt, gilt einmal und läuft um {{time}} ab.", + "pair.codeGone": "Der Code ist abgelaufen. Erzeuge einen neuen, falls der Absender noch nicht aufgeführt ist.", + "pair.done": "Gekoppelt: {{sender}}.", + "pair.dismiss": "Code ausblenden", + "pair.none": "Niemand ist gekoppelt.", + "pair.expires": "endet {{time}}", + "pair.remove": "Entfernen", + "pair.removeSender": "Kopplung von {{sender}} entfernen", + "pair.full": "Drei Absender sind gekoppelt. Entferne einen, um einen weiteren zu koppeln.", + "access.title": "Letzte Entscheidungen", + "access.needSave": "Speichere zuerst diesen Bot, um seine letzten Entscheidungen zu sehen.", + "access.refresh": "Aktualisieren", + "access.recentTitle": "Letzte Entscheidungen", + "access.recentHelp": "Die letzten Prüfungen dieses Servers, die neuesten zuerst. Sie enthalten IDs und Namen von Berechtigungen, nie den Text von Nachrichten. Sie werden beim Neustart des Servers zurückgesetzt.", + "access.recentNone": "Noch keine Entscheidung.", + "access.allowed": "erlaubt", + "access.denied": "abgelehnt", + "access.who": "Absender {{senders}}", + "access.roles": "Rollen {{roles}}", + "access.via": "über {{via}}", + "can.title": "Was dieser Bot kann", + "can.help": "Die Prüfung, die der Host bei jeder Nachricht ausführt, hier für das Formular in seinem jetzigen Stand. Sie ändert nichts. Eine blockierte Zeile zeigt das erste Problem; weitere können folgen.", + "can.sender": "Wenn der Absender ist", + "can.sender.listed": "Eine Person auf der Liste der Erlaubten", + "can.sender.channel_member": "Ein Mitglied eines erlaubten Kanals", + "can.sender.anyone": "Jede Person, die dem Bot schreibt", + "can.sender.paired": "Eine Person, die du gekoppelt hast", + "can.sender.role": "Rolle: {{title}}", + "can.fromForm": "Berechnet aus dem Formular in seinem jetzigen Stand. Speichere, um diese Einstellungen zu behalten.", + "can.fromFormNew": "Noch nicht gespeichert. Berechnet aus dem Formular in seinem jetzigen Stand.", + "can.loading": "Es wird geprüft, was dieser Bot kann", + "can.stale": "Wird aktualisiert", + "can.errorTitle": "Es konnte nicht geprüft werden, was dieser Bot kann", + "can.retry": "Erneut versuchen", + "can.allowed": "Erlaubt", + "can.blocked": "Blockiert", + "can.summary": "{{allowed}} von {{total}} für diesen Absender erlaubt.", + "can.via.paired": "weil diese Person gekoppelt ist", + "can.via.allowlist": "weil diese Person auf der Liste der Erlaubten steht", + "can.via.role": "über die Rolle {{role}}", + "can.noPerson": "Noch keine Person steht auf der Liste der Erlaubten. Das bekäme eine aufgeführte Person.", + "can.then": "Noch nicht geprüft: {{list}}.", + "can.then.server": "die Servereinstellungen", + "can.then.tools": "der Werkzeug-Schalter des Agenten", + "can.then.agent": "die Berechtigungen des Agenten", + "can.then.connection": "die Berechtigungen dieser Verbindung", + "can.then.sender": "wer sendet", + "can.then.approval": "wie Vorschläge freigegeben werden", + "can.noMessage": "Nicht erlaubt.", + "fix.open_connection": "Einstellung anzeigen", + "fix.open_agent": "Agent öffnen", + "fix.open_server_setting": "Einstellungen öffnen", + "fix.pair_sender": "Absender koppeln", + "fix.add_person": "Person hinzufügen", + "fix.save": "Änderungen behalten", + "fix.token": "Token einfügen", + "fix.tokenNew": "Neues Token einfügen", + "fix.agent": "Agent wählen", + "fix.usual": "Berechtigungen wählen", + "fix.turnOn": "Einschalten", + "fix.newTab": "(öffnet in einem neuen Tab)", + "fix.askAdmin": "Administrator fragen", + "ready.title": "Bereitschaft", + "ready.help": "Was vorhanden sein muss, damit dieser Bot funktioniert, für das Formular in seinem jetzigen Stand.", + "ready.checkingSummary": "Es wird geprüft, was das Formular bewirkt.", + "ready.allGood": "Alles Nötige ist vorhanden.", + "ready.problems": "{{count}} zu prüfen.", + "ready.showAll": "Alle {{count}} anzeigen", + "ready.showProblems": "Nur anzeigen, was Aufmerksamkeit braucht", + "ready.checking": "Wird geprüft.", + "ready.unchecked": "Das konnte nicht geprüft werden. Siehe unten „Was dieser Bot kann“.", + "ready.row.token": "Token", + "ready.row.agent": "Agent", + "ready.row.who": "Wer ihn nutzen darf", + "ready.row.permissions": "Berechtigungen", + "ready.row.triggers": "Trigger", + "ready.row.files": "Dateien", + "ready.row.notify": "Benachrichtigungen", + "ready.state.ok": "OK", + "ready.state.attention": "Braucht Aufmerksamkeit", + "ready.state.off": "Aus", + "ready.state.after_save": "Nach dem Speichern", + "ready.state.broken": "Funktioniert nicht", + "ready.state.checking": "Wird geprüft", + "ready.state.unknown": "Unbekannt", + "ready.token.missing": "Füge das Token des Bots ein. Ohne es wird nichts empfangen.", + "ready.token.newTyped": "Das Token wird beim Speichern bei der Plattform geprüft.", + "ready.token.typed": "Das neue Token wird beim Speichern bei der Plattform geprüft.", + "ready.token.failed": "Die Plattform hat das Token abgelehnt: {{reason}}", + "ready.token.checked": "Die Plattform hat das Token akzeptiert. {{bot}}", + "ready.token.saved": "Ein Token ist gespeichert. Die Plattform hat es beim Speichern akzeptiert.", + "ready.agent.none": "Wähle den Agenten, der antwortet.", + "ready.agent.credentialsOnly": "Kein Agent. Diese Verbindung hält nur die Zugangsdaten und beantwortet keine Nachricht.", + "ready.agent.gone": "Der gewählte Agent steht nicht mehr in deiner Liste. Wähle einen anderen.", + "ready.agent.noModel": "{{name}} hat kein Modell und kann daher nicht antworten. Gib ihm eines.", + "ready.agent.ok": "{{name}} antwortet mit dem Modell {{model}}.", + "ready.agent.okUnknown": "Ein Agent ist gewählt.", + "ready.who.anyone": "Jede Person, die dem Bot schreiben kann, darf ihn nutzen.", + "ready.who.nobody": "Noch ist niemand erlaubt, daher ignoriert der Bot jede Nachricht. Füge eine Person hinzu.", + "ready.who.channelsOnly": "Auf der Liste stehen nur Kanäle. Personen in einem Kanal können chatten, aber keine Berechtigung nutzen, bis du sie als Personen hinzufügst.", + "ready.who.listed_one": "{{count}} Person oder Chat ist erlaubt.", + "ready.who.listed_other": "{{count}} Personen oder Chats sind erlaubt.", + "ready.who.mixed": "{{count}} Einträge sind erlaubt, Personen und Kanäle.", + "ready.perm.none": "Keine Berechtigung ist an. Absender können chatten, sonst nichts.", + "ready.perm.noneSaved": "Keine Berechtigung ist an, daher sind Dateien, Gedächtnis, Trigger und Posts aus.", + "ready.perm.onlyOwnRows": "Die anderen Berechtigungen sind aus. Trigger und Dateien stehen unten.", + "ready.perm.ok_one": "{{count}} Berechtigung ist an und ein Absender kann sie nutzen.", + "ready.perm.ok_other": "{{count}} Berechtigungen sind an und ein Absender kann sie nutzen.", + "ready.perm.dead_one": "Niemand kann {{names}} nutzen. {{message}}", + "ready.perm.dead_other": "Niemand kann {{count}} der eingeschalteten Berechtigungen nutzen: {{names}}. Zuerst: {{message}}", + "ready.triggers.off": "Der Agent kann hier keine Erinnerungen oder geplanten Aufgaben einrichten.", + "ready.triggers.blocked": "Trigger sind an, lassen sich aber nicht nutzen. {{message}}", + "ready.triggers.ok.ask": "Der Agent kann Trigger vorschlagen. Du bestätigst jeden, bevor er startet.", + "ready.triggers.ok.auto": "Der Agent kann Trigger vorschlagen. Sie starten sofort, ohne dich zu fragen.", + "ready.triggers.ok.auto_readonly": "Der Agent kann Trigger vorschlagen. Ein Trigger, der nur liest, startet sofort; jeden anderen bestätigst du.", + "ready.files.offReceive": "Absender können dem Agenten keine Dateien senden.", + "ready.files.offBoth": "Absender können dem Agenten keine Dateien senden, und der Bot schickt keine zurück.", + "ready.files.blocked": "Das Empfangen von Dateien ist an, lässt sich aber nicht nutzen. {{message}}", + "ready.files.ok": "Absender können dem Agenten Dateien senden, und der Bot kann Dateien zurückschicken.", + "ready.files.okReceive": "Absender können dem Agenten Dateien senden. Der Bot schickt keine zurück.", + "ready.notify.off": "Kein Chat erhält Hinweise von diesem Bot.", + "ready.notify.ok": "Hinweise gehen an den Chat {{chat}}.", + "dismiss": "Verwerfen", + "credentialsOnly": "Nur Zugangsdaten", + "agentCredentialsOnly": "Kein Agent (nur Zugangsdaten)", + "agentOptionalHint": "Optional. Ohne Agent hält diese Verbindung nur das Token, die Repository-Liste und den Schreibmodus für deine Chats. Sie beantwortet keine Nachrichten. Wähle einen Agenten, damit sie zuhört.", + "sender.line_one": "{{name}} · {{count}} Nachricht · {{id}}", + "sender.line_other": "{{name}} · {{count}} Nachrichten · {{id}}", + "ready.blocker.nobodyLetIn": "Noch ist niemand zugelassen.", + "ready.blocker.nobodyPaired": "Dafür braucht es eine mit dem Bot gekoppelte Person, und noch ist niemand gekoppelt.", + "optional": "optional" +} diff --git a/addons/internal/connector-ui/src/i18n/connector.en.json b/addons/internal/connector-ui/src/i18n/connector.en.json new file mode 100644 index 0000000000..2b5e81111d --- /dev/null +++ b/addons/internal/connector-ui/src/i18n/connector.en.json @@ -0,0 +1,281 @@ +{ + "answer.title": "How it answers", + "answer.replyLabel": "While the agent works", + "answer.reply.off": "Typing, then the answer", + "answer.reply.progress": "Progress: the tools it uses", + "answer.reply.stream": "Live: the answer as it is written", + "answer.replyHelp": "Progress and Live show one working message, and the answer replaces it. On a platform with drafts, Live shows a private chat a draft instead, and the answer follows it. A long answer goes on in new messages.", + "answer.conversationLabel": "Conversations", + "answer.conversationHelp": "Send /new (or !new) in the app to start a new chat.", + "answer.conversation.sender": "One chat per person", + "answer.conversation.chat": "One chat per group or channel", + "answer.conversation.window": "Per person, new after a quiet time", + "answer.conversation.thread": "New chat per message; replies go on", + "answer.window": "Quiet time, in minutes", + "answer.windowRange": "Use a whole number from 1 to {{max}}.", + "answer.files": "Send the files the agent makes", + "aside.title": "How it works", + "aside.step1": "Give the connection a name and its token. The token is sealed and never shown again.", + "aside.step2": "Pick the agent that answers. It needs a model.", + "aside.step3": "Choose who may write to it, and how it answers.", + "aside.testTitle": "Test the connection", + "aside.testText": "When you save a new token, LocalAI checks it and names the account it belongs to. Use Test on a saved row to check it again.", + "add": "Add", + "more": "More", + "less": "Less", + "answer.filesHelp": "After the answer, up to 5 files. Programs and scripts are not sent. The size limit is under Files.", + "answer.summary": "Answers: {{reply}} · {{conversation}}", + "answer.short.off": "typing", + "answer.short.progress": "progress", + "answer.short.stream": "live answer", + "answer.short.sender": "a chat per person", + "answer.short.chat": "a chat per group", + "answer.short.window": "new chat after {{count}} min", + "answer.short.thread": "a chat per message", + "approval.title": "Timers and triggers from the chat", + "approval.help": "When the agent sets up something that runs later (\"in 10 minutes\", \"every day\"), every run is one model run.", + "approval.mode.ask": "Ask me: the bot proposes it in the chat and the person answers yes or no", + "approval.mode.auto": "Set it up at once and tell the person how to cancel it", + "approval.mode.auto_readonly": "At once when the runs only read and report; otherwise ask", + "approval.limitsHelp": "These limits bound what is set up without asking. Leave a field empty for its default. Anything over a limit is asked instead.", + "approval.max_pending": "Most timers and triggers at once", + "approval.min_gap_minutes": "Fewest minutes between runs of a recurring trigger", + "approval.max_runs_per_day": "Most runs a day, all together", + "approval.max_lifetime_days": "Longest lifetime, in days", + "approval.invalid": "Use a whole number from 1 to {{max}}, or leave it empty.", + "approval.default": "Default: {{value}}.", + "approval.summary": "Triggers: {{mode}}", + "approval.effect": "Proposals: {{mode}}", + "approval.limits": "Limits", + "approval.acts.title": "Runs may act", + "approval.acts.help": "Off: the runs of a trigger made from this chat only read and report. On: they may also do what you tick below, and only what the agent has.", + "approval.acts.modes": "Ask me: you always decide. At once: only when you are paired as sender and every act is ticked here.", + "approval.acts.act.files": "Write files in the agent's folders", + "approval.acts.act.files.effect": "A run can create and change files the agent keeps.", + "approval.acts.act.addons": "Use the actions of the agent's add-ons", + "approval.acts.act.addons.effect": "A run can use what the add-ons of the agent do, including commands.", + "approval.acts.act.send": "Send messages beyond where it reports", + "approval.acts.act.send.effect": "A run can write to other chats and channels on its own.", + "approval.acts.act.spend": "Use paid or external tools and the network", + "approval.acts.act.spend.effect": "A run can call services outside LocalAI, which may cost money.", + "approval.acts.lacks": "The agent does not have this.", + "approval.acts.sensitive": "Sensitive", + "approval.effectActs": ", runs may: {{acts}}", + "approval.actShort.act.files": "write files", + "approval.actShort.act.addons": "use add-ons", + "approval.actShort.act.send": "send messages", + "approval.actShort.act.spend": "use paid or external tools", + "approval.short.ask": "asks first", + "approval.short.auto": "set up at once", + "approval.short.auto_readonly": "at once if read-only", + "files.title": "Files", + "files.help": "Leave a field empty to use the limit of the add-on. Your limit can only be lower than that.", + "files.receiveLabel": "Largest file it reads, in MiB", + "files.sendLabel": "Largest file it sends, in MiB", + "files.invalid": "Use a number greater than 0.", + "files.tooHigh": "The add-on allows at most {{max}} here.", + "files.now": "Now: {{limit}} ({{layer}}).", + "files.receiveHelp": "Files that people send to the bot. A bigger file is refused, and the bot says which limit it hit.", + "files.sendHelp": "Files that the agent makes and the bot sends back. A bigger file is not sent, and the bot says which limit it hit.", + "files.addon.off": "This add-on does not move files in this direction, so there is nothing to set.", + "files.addon.declared": "The add-on declares up to {{max}}.", + "files.addon.open": "The add-on declares no limit, so the limit of the platform applies.", + "files.host": "No bot can move a file above {{max}}. An administrator sets this in the settings.", + "files.layer.owner": "limit set for this bot", + "files.layer.addon": "limit of the add-on", + "files.layer.host": "limit of the platform", + "perm.title": "What senders may do", + "perm.help": "Choose what the people who write to this bot may ask the agent to do. Anything switched off stays off for everyone, whatever a role or a pairing says.", + "perm.noCapsTitle": "No permission is on, so these features are off", + "perm.noCapsText": "This bot was saved before permissions existed, or no permission was chosen yet. Until you turn some on, senders cannot send files to the agent, set up triggers, save to memory or have the agent post to a channel. Messages are still answered.", + "perm.usual": "Allow the usual", + "perm.usualHelp": "Switches on the everyday permissions that stay inside LocalAI: {{names}}. Sensitive ones are never included.", + "perm.usualDone": "The usual permissions are on. Save to keep them.", + "perm.usualSaved": "The usual permissions are saved for {{name}}.", + "perm.usualSet": "Allow the usual set", + "perm.usualSetHelp": "Ticks the everyday permissions that stay inside LocalAI, for what the agent has. Sensitive ones are never included.", + "perm.sameAs": "Same as the agent", + "perm.sameAsHelp": "Copies the permissions the agent has now, sensitive ones too. It is a copy of today and does not follow later changes to the agent.", + "perm.block.noAgent": "Choose an agent first.", + "perm.block.unreadable": "The permissions of this agent could not be read.", + "perm.block.nothing": "This agent has no permission that a connection can use.", + "perm.usualTicked": "Ticked {{count}}: {{names}}. Save to keep them.", + "perm.usualNone": "Nothing new to tick: the usual permissions the agent has are already on.", + "perm.usualSkipped": "Not ticked, because the agent does not have them: {{names}}.", + "perm.sameTicked": "Copied {{count}} permissions of {{agent}}: {{names}}. This is a copy of today and does not follow later changes to the agent. Save to keep them.", + "perm.agentLacks": "The agent does not have this.", + "perm.confirmTitle": "Copy the sensitive permissions too?", + "perm.confirmText": "The agent has these sensitive permissions. Once they are on, a sender you paired could use them through this bot:", + "perm.confirmYes": "Copy them too", + "perm.confirmNo": "Cancel", + "perm.rule": "People on the allowlist can use the ticked permissions. Sensitive ones also need the person to be paired.", + "perm.stuckTitle": "Sensitive permissions are ticked, but nobody is paired", + "perm.stuckText": "A sensitive permission works only for a person you paired with this bot. Until you pair someone, no sender can use: {{names}}.", + "perm.channelOnlyTitle": "The allowlist has only channels", + "perm.channelOnlyText": "Members of a channel are not trusted one by one, so they cannot use the ticked permissions. Add their user to the allowed people.", + "perm.stuckPair": "Pair your account", + "perm.sensitive": "Paired senders only", + "perm.sensitiveHelp": "Sensitive. Never given to a role. Only a sender you paired can use it, and every use is recorded.", + "perm.capsLegend": "Permissions of this bot", + "perm.capsHelp": "The ceiling for everyone. A role can be given less, never more.", + "perm.rolesTitle": "Roles", + "perm.rolesHelp": "The platform tells the host which roles a sender has. Choose what each role may do. Sensitive permissions are never given to a role, and a role can only get a permission that is switched on above.", + "perm.rolesNone": "Switch on a permission above that this add-on can give to a role, and it shows here.", + "perm.rolesGrid": "What each role may do", + "perm.roleCap": "{{role}}: {{cap}}", + "perm.channelsTitle": "Approved channels", + "perm.channelsHelp": "Chats where the agent may post when a sender asks, besides the chat it answers in. A channel that is not approved needs a paired sender for each message. At most {{max}}.", + "perm.channelsOff": "Switch on \"Post to approved channels\" above to choose channels.", + "perm.channelId": "Channel ID", + "perm.channelIdHelp": "The ID the platform gives the chat. No spaces.", + "perm.channelLabel": "Name", + "perm.channelLabelHelp": "A name for you and the agent. Optional.", + "perm.channelApproved": "Approved", + "perm.channelApprovedHelp": "On: the agent may post here on any allowed sender's request.", + "perm.channelAdd": "Add a channel", + "perm.channelRemove": "Remove channel {{id}}", + "perm.remove": "Remove", + "perm.channelFull": "The list is full.", + "perm.channelProblem.id": "Each channel needs an ID of 1 to {{max}} bytes, with no space.", + "perm.channelProblem.label": "A name has at most {{max}} characters.", + "perm.channelProblem.repeat": "A channel is listed twice.", + "perm.channelProblem.count": "At most {{max}} channels.", + "perm.triggersNeed": "Turn on Propose triggers to use this.", + "pair.title": "Paired senders", + "pair.help": "A paired sender proved that they control an allowed account. Only a paired sender can use the sensitive permissions. At most {{max}}; a pairing ends after {{days}} days.", + "pair.start": "Pair a sender", + "pair.startHelp": "Makes a code. Send it to the bot as a direct message from the account to pair, within ten minutes. The code works once.", + "pair.needSave": "Save this bot first, then pair a sender.", + "pair.codeTitle": "Your pairing code", + "pair.codeHelp": "Send this code to the bot as a direct message from the account to pair. It is shown only now, works once and expires at {{time}}.", + "pair.codeGone": "The code expired. Make a new one if the sender is not listed yet.", + "pair.done": "Paired: {{sender}}.", + "pair.dismiss": "Hide the code", + "pair.none": "Nobody is paired.", + "pair.expires": "ends {{time}}", + "pair.remove": "Remove", + "pair.removeSender": "Remove the pairing of {{sender}}", + "pair.full": "Three senders are paired. Remove one to pair another.", + "access.title": "Latest decisions", + "access.needSave": "Save this bot first to see its latest decisions.", + "access.refresh": "Refresh", + "access.recentTitle": "Latest decisions", + "access.recentHelp": "The latest checks of this server, newest first. They hold IDs and names of permissions, never message text. They reset when the server restarts.", + "access.recentNone": "No decision yet.", + "access.allowed": "allowed", + "access.denied": "denied", + "access.who": "sender {{senders}}", + "access.roles": "roles {{roles}}", + "access.via": "via {{via}}", + "can.title": "What this bot can do", + "can.help": "The check the host runs for each message, run on this form as it is now. It changes nothing. A blocked row shows the first problem; more may follow it.", + "can.sender": "If the sender is", + "can.sender.listed": "A person on the allowed list", + "can.sender.channel_member": "A member of an allowed channel", + "can.sender.anyone": "Anyone who writes to the bot", + "can.sender.paired": "A person you paired", + "can.sender.role": "Role: {{title}}", + "can.fromForm": "Computed from the form as it is now. Save to keep these settings.", + "can.fromFormNew": "Not saved yet. Computed from the form as it is now.", + "can.loading": "Checking what this bot can do", + "can.stale": "Updating", + "can.errorTitle": "Could not check what this bot can do", + "can.retry": "Try again", + "can.allowed": "Allowed", + "can.blocked": "Blocked", + "can.summary": "{{allowed}} of {{total}} allowed for this sender.", + "can.via.paired": "because this person is paired", + "can.via.allowlist": "because this person is on the allowed list", + "can.via.role": "through the role {{role}}", + "can.noPerson": "No person is on the allowed list yet. This is what a listed person would get.", + "can.then": "Not checked yet: {{list}}.", + "can.then.server": "the server settings", + "can.then.tools": "the Tools switch of the agent", + "can.then.agent": "the permissions of the agent", + "can.then.connection": "the permissions of this connection", + "can.then.sender": "who is sending", + "can.then.approval": "how proposals are approved", + "can.noMessage": "Not allowed.", + "fix.open_connection": "Show the setting", + "fix.open_agent": "Open the agent", + "fix.open_server_setting": "Open Settings", + "fix.pair_sender": "Pair a sender", + "fix.add_person": "Add a person", + "fix.save": "Keep these changes", + "fix.token": "Paste the token", + "fix.tokenNew": "Paste a new token", + "fix.agent": "Choose an agent", + "fix.usual": "Choose permissions", + "fix.turnOn": "Turn on", + "fix.newTab": "(opens in a new tab)", + "fix.askAdmin": "Ask an administrator", + "ready.title": "Readiness", + "ready.help": "What has to be in place for this bot to work, for the form as it is now.", + "ready.checkingSummary": "Checking what the form will do.", + "ready.allGood": "Everything needed is in place.", + "ready.problems": "{{count}} to look at.", + "ready.showAll": "Show all {{count}}", + "ready.showProblems": "Show only what needs attention", + "ready.checking": "Checking.", + "ready.unchecked": "Could not check this. See \"What this bot can do\" below.", + "ready.row.token": "Token", + "ready.row.agent": "Agent", + "ready.row.who": "Who may use it", + "ready.row.permissions": "Permissions", + "ready.row.triggers": "Triggers", + "ready.row.files": "Files", + "ready.row.notify": "Notifications", + "ready.state.ok": "OK", + "ready.state.attention": "Needs attention", + "ready.state.off": "Off", + "ready.state.after_save": "After you save", + "ready.state.broken": "Not working", + "ready.state.checking": "Checking", + "ready.state.unknown": "Unknown", + "ready.token.missing": "Paste the token of the bot. Without it nothing is received.", + "ready.token.newTyped": "The token is checked with the platform when you save.", + "ready.token.typed": "The new token is checked with the platform when you save.", + "ready.token.failed": "The platform refused the token: {{reason}}", + "ready.token.checked": "The platform accepted the token. {{bot}}", + "ready.token.saved": "A token is saved. The platform accepted it when it was saved.", + "ready.agent.none": "Choose the agent that answers.", + "ready.agent.credentialsOnly": "No agent. This connection only holds the credentials and answers no message.", + "ready.agent.gone": "The chosen agent is not in your list any more. Choose another.", + "ready.agent.noModel": "{{name}} has no model, so it cannot answer. Give it one.", + "ready.agent.ok": "{{name}} answers, with the model {{model}}.", + "ready.agent.okUnknown": "An agent is chosen.", + "ready.who.anyone": "Anyone who can message the bot may use it.", + "ready.who.nobody": "Nobody is allowed yet, so the bot ignores every message. Add a person.", + "ready.who.channelsOnly": "Only channels are on the list. People in a channel can chat, but they cannot use any permission until you add them as people.", + "ready.who.listed_one": "{{count}} person or chat is allowed.", + "ready.who.listed_other": "{{count}} people or chats are allowed.", + "ready.who.mixed": "{{count}} entries are allowed, people and channels.", + "ready.perm.none": "No permission is on. Senders can chat, and nothing else.", + "ready.perm.noneSaved": "No permission is on, so files, memory, triggers and posts are off.", + "ready.perm.onlyOwnRows": "The other permissions are off. Triggers and files are below.", + "ready.perm.ok_one": "{{count}} permission is on and a sender can use it.", + "ready.perm.ok_other": "{{count}} permissions are on and a sender can use them.", + "ready.perm.dead_one": "Nobody can use {{names}}. {{message}}", + "ready.perm.dead_other": "Nobody can use {{count}} of the permissions that are on: {{names}}. First: {{message}}", + "ready.triggers.off": "The agent cannot set reminders or scheduled tasks here.", + "ready.triggers.blocked": "Triggers are on, and they cannot be used. {{message}}", + "ready.triggers.ok.ask": "The agent can propose triggers. You confirm each one before it starts.", + "ready.triggers.ok.auto": "The agent can propose triggers. They start at once, without asking you.", + "ready.triggers.ok.auto_readonly": "The agent can propose triggers. A trigger that only reads starts at once; you confirm any other.", + "ready.files.offReceive": "Senders cannot send files to the agent.", + "ready.files.offBoth": "Senders cannot send files to the agent, and the bot sends none back.", + "ready.files.blocked": "Receiving files is on, and it cannot be used. {{message}}", + "ready.files.ok": "Senders can send files to the agent, and the bot can send files back.", + "ready.files.okReceive": "Senders can send files to the agent. The bot sends none back.", + "ready.notify.off": "No chat gets notices from this bot.", + "ready.notify.ok": "Notices go to the chat {{chat}}.", + "dismiss": "Dismiss", + "credentialsOnly": "Credentials only", + "agentCredentialsOnly": "No agent (credentials only)", + "agentOptionalHint": "Optional. Without an agent this connection only holds the token, the repository list and the write mode for your chats. It answers no messages. Choose an agent to let it listen.", + "sender.line_one": "{{name}} · {{count}} message · {{id}}", + "sender.line_other": "{{name}} · {{count}} messages · {{id}}", + "ready.blocker.nobodyLetIn": "Nobody is let in yet.", + "ready.blocker.nobodyPaired": "This needs a person you paired with the bot, and nobody is paired yet.", + "optional": "optional" +} diff --git a/addons/internal/connector-ui/src/i18n/connector.es.json b/addons/internal/connector-ui/src/i18n/connector.es.json new file mode 100644 index 0000000000..1b6f81b2ac --- /dev/null +++ b/addons/internal/connector-ui/src/i18n/connector.es.json @@ -0,0 +1,281 @@ +{ + "answer.title": "Cómo responde", + "answer.replyLabel": "Mientras el agente trabaja", + "answer.reply.off": "Escribiendo, y luego la respuesta", + "answer.reply.progress": "Progreso: las herramientas que usa", + "answer.reply.stream": "En vivo: la respuesta mientras se escribe", + "answer.replyHelp": "Progreso y En vivo muestran un solo mensaje de trabajo, y la respuesta lo reemplaza. En una plataforma con borradores, En vivo muestra un borrador en un chat privado y la respuesta va después. Una respuesta larga sigue en mensajes nuevos.", + "answer.conversationLabel": "Conversaciones", + "answer.conversationHelp": "Envía /new (o !new) en la aplicación para empezar un chat nuevo.", + "answer.conversation.sender": "Un chat por persona", + "answer.conversation.chat": "Un chat por grupo o canal", + "answer.conversation.window": "Por persona, nuevo tras un rato de silencio", + "answer.conversation.thread": "Un chat nuevo por mensaje; las respuestas siguen en él", + "answer.window": "Tiempo de silencio, en minutos", + "answer.windowRange": "Usa un número entero del 1 al {{max}}.", + "answer.files": "Enviar los archivos que crea el agente", + "aside.title": "Cómo funciona", + "aside.step1": "Dale un nombre a la conexión y su token. El token se guarda cifrado y no se vuelve a mostrar.", + "aside.step2": "Elige el agente que responde. Necesita un modelo.", + "aside.step3": "Elige quién puede escribirle y cómo responde.", + "aside.testTitle": "Probar la conexión", + "aside.testText": "Cuando guardas un token nuevo, LocalAI lo comprueba y nombra la cuenta a la que pertenece. Usa Probar en una fila guardada para comprobarlo de nuevo.", + "add": "Añadir", + "more": "Más", + "less": "Menos", + "answer.filesHelp": "Tras la respuesta, hasta 5 archivos. No se envían programas ni scripts. El límite de tamaño está en Archivos.", + "answer.summary": "Respuestas: {{reply}} · {{conversation}}", + "answer.short.off": "escribiendo", + "answer.short.progress": "progreso", + "answer.short.stream": "respuesta en vivo", + "answer.short.sender": "un chat por persona", + "answer.short.chat": "un chat por grupo", + "answer.short.window": "chat nuevo tras {{count}} min", + "answer.short.thread": "un chat por mensaje", + "approval.title": "Temporizadores y disparadores desde el chat", + "approval.help": "Cuando el agente prepara algo que se ejecuta más tarde («en 10 minutos», «cada día»), cada ejecución usa el modelo una vez.", + "approval.mode.ask": "Pregúntame: el bot lo propone en el chat y la persona responde sí o no", + "approval.mode.auto": "Prepararlo al instante y decir a la persona cómo cancelarlo", + "approval.mode.auto_readonly": "Al instante si las ejecuciones solo leen e informan; si no, preguntar", + "approval.limitsHelp": "Estos límites acotan lo que se prepara sin preguntar. Deja un campo vacío para usar su valor por defecto. Lo que pase de un límite se pregunta.", + "approval.max_pending": "Máximo de temporizadores y disparadores a la vez", + "approval.min_gap_minutes": "Mínimo de minutos entre ejecuciones de un disparador recurrente", + "approval.max_runs_per_day": "Máximo de ejecuciones al día, en total", + "approval.max_lifetime_days": "Duración máxima, en días", + "approval.invalid": "Usa un número entero del 1 al {{max}}, o déjalo vacío.", + "approval.default": "Por defecto: {{value}}.", + "approval.summary": "Disparadores: {{mode}}", + "approval.effect": "Propuestas: {{mode}}", + "approval.limits": "Límites", + "approval.acts.title": "Las ejecuciones pueden actuar", + "approval.acts.help": "Desactivado: las ejecuciones de un disparador creado desde este chat solo leen e informan. Activado: también pueden hacer lo que marques abajo, y solo lo que el agente tiene.", + "approval.acts.modes": "Pregúntame: decides tú siempre. Al instante: solo cuando estás emparejado como remitente y cada acción está marcada aquí.", + "approval.acts.act.files": "Escribir archivos en las carpetas del agente", + "approval.acts.act.files.effect": "Una ejecución puede crear y cambiar archivos que guarda el agente.", + "approval.acts.act.addons": "Usar las acciones de los complementos del agente", + "approval.acts.act.addons.effect": "Una ejecución puede usar lo que hacen los complementos del agente, incluidos los comandos.", + "approval.acts.act.send": "Enviar mensajes más allá de donde informa", + "approval.acts.act.send.effect": "Una ejecución puede escribir por su cuenta en otros chats y canales.", + "approval.acts.act.spend": "Usar herramientas de pago o externas y la red", + "approval.acts.act.spend.effect": "Una ejecución puede llamar a servicios fuera de LocalAI, que pueden costar dinero.", + "approval.acts.lacks": "El agente no tiene esto.", + "approval.acts.sensitive": "Sensible", + "approval.effectActs": ", las ejecuciones pueden: {{acts}}", + "approval.actShort.act.files": "escribir archivos", + "approval.actShort.act.addons": "usar complementos", + "approval.actShort.act.send": "enviar mensajes", + "approval.actShort.act.spend": "usar herramientas de pago o externas", + "approval.short.ask": "pregunta antes", + "approval.short.auto": "al instante", + "approval.short.auto_readonly": "al instante si solo lee", + "files.title": "Archivos", + "files.help": "Deja un campo vacío para usar el límite del complemento. Tu límite solo puede ser menor que ese.", + "files.receiveLabel": "Archivo más grande que lee, en MiB", + "files.sendLabel": "Archivo más grande que envía, en MiB", + "files.invalid": "Usa un número mayor que 0.", + "files.tooHigh": "El complemento permite como máximo {{max}} aquí.", + "files.now": "Ahora: {{limit}} ({{layer}}).", + "files.receiveHelp": "Archivos que las personas envían al bot. Un archivo más grande se rechaza, y el bot dice qué límite alcanzó.", + "files.sendHelp": "Archivos que crea el agente y que el bot devuelve. Un archivo más grande no se envía, y el bot dice qué límite alcanzó.", + "files.addon.off": "Este complemento no mueve archivos en este sentido, así que no hay nada que ajustar.", + "files.addon.declared": "El complemento declara hasta {{max}}.", + "files.addon.open": "El complemento no declara ningún límite, así que se aplica el de la plataforma.", + "files.host": "Ningún bot puede mover un archivo de más de {{max}}. Un administrador lo fija en la configuración.", + "files.layer.owner": "límite fijado para este bot", + "files.layer.addon": "límite del complemento", + "files.layer.host": "límite de la plataforma", + "perm.title": "Qué pueden hacer los remitentes", + "perm.help": "Elige qué pueden pedir al agente las personas que escriben a este bot. Lo que esté desactivado queda desactivado para todos, diga lo que diga un rol o un emparejamiento.", + "perm.noCapsTitle": "No hay ningún permiso activado, así que estas funciones están desactivadas", + "perm.noCapsText": "Este bot se guardó antes de que existieran los permisos, o aún no se eligió ninguno. Hasta que actives alguno, los remitentes no pueden enviar archivos al agente, preparar disparadores, guardar en la memoria ni hacer que el agente publique en un canal. Los mensajes se siguen respondiendo.", + "perm.usual": "Permitir lo habitual", + "perm.usualHelp": "Activa los permisos de uso diario que se quedan dentro de LocalAI: {{names}}. Los sensibles nunca se incluyen.", + "perm.usualDone": "Los permisos habituales están activados. Guarda para conservarlos.", + "perm.usualSaved": "Los permisos habituales se guardaron para {{name}}.", + "perm.usualSet": "Permitir el conjunto habitual", + "perm.usualSetHelp": "Marca los permisos de uso diario que se quedan dentro de LocalAI, según lo que tiene el agente. Los sensibles nunca se incluyen.", + "perm.sameAs": "Igual que el agente", + "perm.sameAsHelp": "Copia los permisos que tiene ahora el agente, también los sensibles. Es una copia de hoy y no sigue los cambios posteriores del agente.", + "perm.block.noAgent": "Elige primero un agente.", + "perm.block.unreadable": "No se pudieron leer los permisos de este agente.", + "perm.block.nothing": "Este agente no tiene ningún permiso que una conexión pueda usar.", + "perm.usualTicked": "Marcados {{count}}: {{names}}. Guarda para conservarlos.", + "perm.usualNone": "No hay nada nuevo que marcar: los permisos habituales que tiene el agente ya están activados.", + "perm.usualSkipped": "No se marcaron, porque el agente no los tiene: {{names}}.", + "perm.sameTicked": "Se copiaron {{count}} permisos de {{agent}}: {{names}}. Es una copia de hoy y no sigue los cambios posteriores del agente. Guarda para conservarlos.", + "perm.agentLacks": "El agente no tiene esto.", + "perm.confirmTitle": "¿Copiar también los permisos sensibles?", + "perm.confirmText": "El agente tiene estos permisos sensibles. Una vez activados, un remitente que hayas emparejado podría usarlos a través de este bot:", + "perm.confirmYes": "Copiarlos también", + "perm.confirmNo": "Cancelar", + "perm.rule": "Las personas de la lista de permitidos pueden usar los permisos marcados. Los sensibles exigen además que la persona esté emparejada.", + "perm.stuckTitle": "Hay permisos sensibles marcados, pero nadie está emparejado", + "perm.stuckText": "Un permiso sensible solo funciona para una persona que hayas emparejado con este bot. Hasta que empares a alguien, ningún remitente puede usar: {{names}}.", + "perm.channelOnlyTitle": "La lista de permitidos solo tiene canales", + "perm.channelOnlyText": "Los miembros de un canal no se consideran de confianza uno a uno, así que no pueden usar los permisos marcados. Añade su usuario a las personas permitidas.", + "perm.stuckPair": "Empareja tu cuenta", + "perm.sensitive": "Solo remitentes emparejados", + "perm.sensitiveHelp": "Sensible. Nunca se da a un rol. Solo puede usarlo un remitente emparejado, y cada uso queda registrado.", + "perm.capsLegend": "Permisos de este bot", + "perm.capsHelp": "El techo para todos. A un rol se le puede dar menos, nunca más.", + "perm.rolesTitle": "Roles", + "perm.rolesHelp": "La plataforma indica al host qué roles tiene un remitente. Elige qué puede hacer cada rol. Los permisos sensibles nunca se dan a un rol, y un rol solo puede recibir un permiso que esté activado arriba.", + "perm.rolesNone": "Activa arriba un permiso que este complemento pueda dar a un rol y aparecerá aquí.", + "perm.rolesGrid": "Qué puede hacer cada rol", + "perm.roleCap": "{{role}}: {{cap}}", + "perm.channelsTitle": "Canales aprobados", + "perm.channelsHelp": "Chats donde el agente puede publicar cuando un remitente se lo pide, además del chat en el que responde. En un canal no aprobado, cada mensaje necesita un remitente emparejado. Máximo {{max}}.", + "perm.channelsOff": "Activa «Publicar en canales aprobados» arriba para elegir canales.", + "perm.channelId": "ID del canal", + "perm.channelIdHelp": "El ID que la plataforma da al chat. Sin espacios.", + "perm.channelLabel": "Nombre", + "perm.channelLabelHelp": "Un nombre para ti y para el agente. Opcional.", + "perm.channelApproved": "Aprobado", + "perm.channelApprovedHelp": "Activado: el agente puede publicar aquí a petición de cualquier remitente permitido.", + "perm.channelAdd": "Añadir un canal", + "perm.channelRemove": "Quitar el canal {{id}}", + "perm.remove": "Quitar", + "perm.channelFull": "La lista está llena.", + "perm.channelProblem.id": "Cada canal necesita un ID de 1 a {{max}} bytes, sin espacios.", + "perm.channelProblem.label": "Un nombre tiene como máximo {{max}} caracteres.", + "perm.channelProblem.repeat": "Un canal aparece dos veces.", + "perm.channelProblem.count": "Máximo {{max}} canales.", + "perm.triggersNeed": "Activa Proponer disparadores para usar esto.", + "pair.title": "Remitentes emparejados", + "pair.help": "Un remitente emparejado demostró que controla una cuenta permitida. Solo un remitente emparejado puede usar los permisos sensibles. Máximo {{max}}; un emparejamiento termina tras {{days}} días.", + "pair.start": "Emparejar un remitente", + "pair.startHelp": "Genera un código. Envíalo al bot como mensaje directo desde la cuenta que quieres emparejar, en un plazo de diez minutos. El código sirve una sola vez.", + "pair.needSave": "Guarda primero este bot y luego empareja un remitente.", + "pair.codeTitle": "Tu código de emparejamiento", + "pair.codeHelp": "Envía este código al bot como mensaje directo desde la cuenta que quieres emparejar. Solo se muestra ahora, sirve una vez y caduca a las {{time}}.", + "pair.codeGone": "El código caducó. Genera uno nuevo si el remitente aún no aparece en la lista.", + "pair.done": "Emparejado: {{sender}}.", + "pair.dismiss": "Ocultar el código", + "pair.none": "Nadie está emparejado.", + "pair.expires": "termina {{time}}", + "pair.remove": "Quitar", + "pair.removeSender": "Quitar el emparejamiento de {{sender}}", + "pair.full": "Hay tres remitentes emparejados. Quita uno para emparejar otro.", + "access.title": "Últimas decisiones", + "access.needSave": "Guarda primero este bot para ver sus últimas decisiones.", + "access.refresh": "Actualizar", + "access.recentTitle": "Últimas decisiones", + "access.recentHelp": "Las últimas comprobaciones de este servidor, las más recientes primero. Contienen IDs y nombres de permisos, nunca el texto de los mensajes. Se reinician cuando se reinicia el servidor.", + "access.recentNone": "Aún no hay ninguna decisión.", + "access.allowed": "permitido", + "access.denied": "denegado", + "access.who": "remitente {{senders}}", + "access.roles": "roles {{roles}}", + "access.via": "mediante {{via}}", + "can.title": "Qué puede hacer este bot", + "can.help": "La comprobación que el host hace con cada mensaje, ejecutada sobre este formulario tal como está ahora. No cambia nada. Una fila bloqueada muestra el primer problema; pueden venir más después.", + "can.sender": "Si el remitente es", + "can.sender.listed": "Una persona de la lista de permitidos", + "can.sender.channel_member": "Un miembro de un canal permitido", + "can.sender.anyone": "Cualquiera que escriba al bot", + "can.sender.paired": "Una persona que emparejaste", + "can.sender.role": "Rol: {{title}}", + "can.fromForm": "Calculado a partir del formulario tal como está ahora. Guarda para conservar estos ajustes.", + "can.fromFormNew": "Aún sin guardar. Calculado a partir del formulario tal como está ahora.", + "can.loading": "Comprobando qué puede hacer este bot", + "can.stale": "Actualizando", + "can.errorTitle": "No se pudo comprobar qué puede hacer este bot", + "can.retry": "Reintentar", + "can.allowed": "Permitido", + "can.blocked": "Bloqueado", + "can.summary": "{{allowed}} de {{total}} permitidos para este remitente.", + "can.via.paired": "porque esta persona está emparejada", + "can.via.allowlist": "porque esta persona está en la lista de permitidos", + "can.via.role": "mediante el rol {{role}}", + "can.noPerson": "Aún no hay ninguna persona en la lista de permitidos. Esto es lo que recibiría una persona de la lista.", + "can.then": "Aún sin comprobar: {{list}}.", + "can.then.server": "la configuración del servidor", + "can.then.tools": "el interruptor Herramientas del agente", + "can.then.agent": "los permisos del agente", + "can.then.connection": "los permisos de esta conexión", + "can.then.sender": "quién envía", + "can.then.approval": "cómo se aprueban las propuestas", + "can.noMessage": "No permitido.", + "fix.open_connection": "Mostrar el ajuste", + "fix.open_agent": "Abrir el agente", + "fix.open_server_setting": "Abrir Configuración", + "fix.pair_sender": "Emparejar un remitente", + "fix.add_person": "Añadir una persona", + "fix.save": "Conservar estos cambios", + "fix.token": "Pega el token", + "fix.tokenNew": "Pega un token nuevo", + "fix.agent": "Elige un agente", + "fix.usual": "Elige permisos", + "fix.turnOn": "Activar", + "fix.newTab": "(se abre en una pestaña nueva)", + "fix.askAdmin": "Pregunta a un administrador", + "ready.title": "Estado de preparación", + "ready.help": "Lo que debe estar en su sitio para que este bot funcione, según el formulario tal como está ahora.", + "ready.checkingSummary": "Comprobando lo que hará el formulario.", + "ready.allGood": "Todo lo necesario está en su sitio.", + "ready.problems": "{{count}} por revisar.", + "ready.showAll": "Mostrar los {{count}}", + "ready.showProblems": "Mostrar solo lo que necesita atención", + "ready.checking": "Comprobando.", + "ready.unchecked": "No se pudo comprobar esto. Mira «Qué puede hacer este bot» más abajo.", + "ready.row.token": "Token", + "ready.row.agent": "Agente", + "ready.row.who": "Quién puede usarlo", + "ready.row.permissions": "Permisos", + "ready.row.triggers": "Disparadores", + "ready.row.files": "Archivos", + "ready.row.notify": "Notificaciones", + "ready.state.ok": "OK", + "ready.state.attention": "Necesita atención", + "ready.state.off": "Desactivado", + "ready.state.after_save": "Tras guardar", + "ready.state.broken": "No funciona", + "ready.state.checking": "Comprobando", + "ready.state.unknown": "Desconocido", + "ready.token.missing": "Pega el token del bot. Sin él no se recibe nada.", + "ready.token.newTyped": "El token se comprueba con la plataforma al guardar.", + "ready.token.typed": "El token nuevo se comprueba con la plataforma al guardar.", + "ready.token.failed": "La plataforma rechazó el token: {{reason}}", + "ready.token.checked": "La plataforma aceptó el token. {{bot}}", + "ready.token.saved": "Hay un token guardado. La plataforma lo aceptó al guardarlo.", + "ready.agent.none": "Elige el agente que responde.", + "ready.agent.credentialsOnly": "Sin agente. Esta conexión solo guarda las credenciales y no responde a ningún mensaje.", + "ready.agent.gone": "El agente elegido ya no está en tu lista. Elige otro.", + "ready.agent.noModel": "{{name}} no tiene modelo, así que no puede responder. Dale uno.", + "ready.agent.ok": "{{name}} responde, con el modelo {{model}}.", + "ready.agent.okUnknown": "Hay un agente elegido.", + "ready.who.anyone": "Cualquiera que pueda escribir al bot puede usarlo.", + "ready.who.nobody": "Aún no se permite a nadie, así que el bot ignora todos los mensajes. Añade una persona.", + "ready.who.channelsOnly": "Solo hay canales en la lista. Las personas de un canal pueden chatear, pero no pueden usar ningún permiso hasta que las añadas como personas.", + "ready.who.listed_one": "Se permite {{count}} persona o chat.", + "ready.who.listed_other": "Se permiten {{count}} personas o chats.", + "ready.who.mixed": "Se permiten {{count}} entradas, personas y canales.", + "ready.perm.none": "No hay ningún permiso activado. Los remitentes pueden chatear, y nada más.", + "ready.perm.noneSaved": "No hay ningún permiso activado, así que archivos, memoria, disparadores y publicaciones están desactivados.", + "ready.perm.onlyOwnRows": "Los demás permisos están desactivados. Los disparadores y los archivos están más abajo.", + "ready.perm.ok_one": "{{count}} permiso está activado y un remitente puede usarlo.", + "ready.perm.ok_other": "{{count}} permisos están activados y un remitente puede usarlos.", + "ready.perm.dead_one": "Nadie puede usar {{names}}. {{message}}", + "ready.perm.dead_other": "Nadie puede usar {{count}} de los permisos activados: {{names}}. El primero: {{message}}", + "ready.triggers.off": "El agente no puede crear recordatorios ni tareas programadas aquí.", + "ready.triggers.blocked": "Los disparadores están activados y no se pueden usar. {{message}}", + "ready.triggers.ok.ask": "El agente puede proponer disparadores. Tú confirmas cada uno antes de que empiece.", + "ready.triggers.ok.auto": "El agente puede proponer disparadores. Empiezan al instante, sin preguntarte.", + "ready.triggers.ok.auto_readonly": "El agente puede proponer disparadores. Uno que solo lee empieza al instante; confirmas cualquier otro.", + "ready.files.offReceive": "Los remitentes no pueden enviar archivos al agente.", + "ready.files.offBoth": "Los remitentes no pueden enviar archivos al agente, y el bot no devuelve ninguno.", + "ready.files.blocked": "La recepción de archivos está activada y no se puede usar. {{message}}", + "ready.files.ok": "Los remitentes pueden enviar archivos al agente, y el bot puede devolver archivos.", + "ready.files.okReceive": "Los remitentes pueden enviar archivos al agente. El bot no devuelve ninguno.", + "ready.notify.off": "Ningún chat recibe avisos de este bot.", + "ready.notify.ok": "Los avisos van al chat {{chat}}.", + "dismiss": "Descartar", + "credentialsOnly": "Solo credenciales", + "agentCredentialsOnly": "Sin agente (solo credenciales)", + "agentOptionalHint": "Opcional. Sin agente, esta conexión solo guarda el token, la lista de repositorios y el modo de escritura para tus chats. No responde a ningún mensaje. Elige un agente para que escuche.", + "sender.line_one": "{{name}} · {{count}} mensaje · {{id}}", + "sender.line_other": "{{name}} · {{count}} mensajes · {{id}}", + "ready.blocker.nobodyLetIn": "Aún no se deja entrar a nadie.", + "ready.blocker.nobodyPaired": "Esto necesita una persona emparejada con el bot, y aún no hay nadie emparejado.", + "optional": "opcional" +} diff --git a/addons/internal/connector-ui/src/i18n/connector.fr.json b/addons/internal/connector-ui/src/i18n/connector.fr.json new file mode 100644 index 0000000000..27104910fe --- /dev/null +++ b/addons/internal/connector-ui/src/i18n/connector.fr.json @@ -0,0 +1,281 @@ +{ + "answer.title": "Comment il répond", + "answer.replyLabel": "Pendant que l’agent travaille", + "answer.reply.off": "Saisie en cours, puis la réponse", + "answer.reply.progress": "Progression : les outils utilisés", + "answer.reply.stream": "En direct : la réponse à mesure qu’elle s’écrit", + "answer.replyHelp": "Progression et En direct affichent un seul message de travail, que la réponse remplace. Sur une plateforme avec brouillons, En direct montre un brouillon dans un chat privé, puis la réponse le suit. Une longue réponse continue dans de nouveaux messages.", + "answer.conversationLabel": "Conversations", + "answer.conversationHelp": "Envoyez /new (ou !new) dans l’application pour démarrer un nouveau chat.", + "answer.conversation.sender": "Un chat par personne", + "answer.conversation.chat": "Un chat par groupe ou canal", + "answer.conversation.window": "Par personne, nouveau après un temps d’inactivité", + "answer.conversation.thread": "Nouveau chat à chaque message ; les réponses suivent", + "answer.window": "Temps d’inactivité, en minutes", + "answer.windowRange": "Saisissez un nombre entier de 1 à {{max}}.", + "answer.files": "Envoyer les fichiers créés par l’agent", + "aside.title": "Comment ça marche", + "aside.step1": "Donnez un nom à la connexion et saisissez son jeton. Le jeton est scellé et n’est plus jamais affiché.", + "aside.step2": "Choisissez l’agent qui répond. Il lui faut un modèle.", + "aside.step3": "Choisissez qui peut lui écrire, et comment il répond.", + "aside.testTitle": "Tester la connexion", + "aside.testText": "Quand vous enregistrez un nouveau jeton, LocalAI le vérifie et nomme le compte auquel il appartient. Utilisez Tester sur une ligne enregistrée pour le vérifier de nouveau.", + "add": "Ajouter", + "more": "Plus", + "less": "Moins", + "answer.filesHelp": "Après la réponse, jusqu’à 5 fichiers. Les programmes et les scripts ne sont pas envoyés. La limite de taille se règle sous Fichiers.", + "answer.summary": "Réponses : {{reply}} · {{conversation}}", + "answer.short.off": "saisie en cours", + "answer.short.progress": "progression", + "answer.short.stream": "réponse en direct", + "answer.short.sender": "un chat par personne", + "answer.short.chat": "un chat par groupe", + "answer.short.window": "nouveau chat après {{count}} min", + "answer.short.thread": "un chat par message", + "approval.title": "Minuteurs et déclencheurs depuis le chat", + "approval.help": "Quand l’agent met en place quelque chose qui s’exécute plus tard (« dans 10 minutes », « chaque jour »), chaque exécution est un appel au modèle.", + "approval.mode.ask": "Me demander : le bot le propose dans le chat et la personne répond oui ou non", + "approval.mode.auto": "Le mettre en place tout de suite et indiquer à la personne comment l’annuler", + "approval.mode.auto_readonly": "Tout de suite si les exécutions se contentent de lire et de rapporter ; sinon demander", + "approval.limitsHelp": "Ces limites bornent ce qui est mis en place sans demander. Laissez un champ vide pour sa valeur par défaut. Tout ce qui dépasse une limite est soumis à confirmation.", + "approval.max_pending": "Nombre maximal de minuteurs et de déclencheurs à la fois", + "approval.min_gap_minutes": "Nombre minimal de minutes entre deux exécutions d’un déclencheur récurrent", + "approval.max_runs_per_day": "Nombre maximal d’exécutions par jour, au total", + "approval.max_lifetime_days": "Durée de vie maximale, en jours", + "approval.invalid": "Saisissez un nombre entier de 1 à {{max}}, ou laissez vide.", + "approval.default": "Par défaut : {{value}}.", + "approval.summary": "Déclencheurs : {{mode}}", + "approval.effect": "Propositions : {{mode}}", + "approval.limits": "Limites", + "approval.acts.title": "Les exécutions peuvent agir", + "approval.acts.help": "Désactivé : les exécutions d’un déclencheur créé depuis ce chat se contentent de lire et de rapporter. Activé : elles peuvent aussi faire ce que vous cochez ci-dessous, et seulement ce dont l’agent dispose.", + "approval.acts.modes": "Me demander : vous décidez toujours. Tout de suite : seulement si vous êtes associé comme expéditeur et que chaque action est cochée ici.", + "approval.acts.act.files": "Écrire des fichiers dans les dossiers de l’agent", + "approval.acts.act.files.effect": "Une exécution peut créer et modifier les fichiers que garde l’agent.", + "approval.acts.act.addons": "Utiliser les actions des add-ons de l’agent", + "approval.acts.act.addons.effect": "Une exécution peut utiliser ce que font les add-ons de l’agent, y compris des commandes.", + "approval.acts.act.send": "Envoyer des messages au-delà de l’endroit où elle rapporte", + "approval.acts.act.send.effect": "Une exécution peut écrire seule dans d’autres chats et canaux.", + "approval.acts.act.spend": "Utiliser des outils payants ou externes et le réseau", + "approval.acts.act.spend.effect": "Une exécution peut appeler des services extérieurs à LocalAI, ce qui peut coûter de l’argent.", + "approval.acts.lacks": "L’agent ne dispose pas de ceci.", + "approval.acts.sensitive": "Sensible", + "approval.effectActs": ", les exécutions peuvent : {{acts}}", + "approval.actShort.act.files": "écrire des fichiers", + "approval.actShort.act.addons": "utiliser des add-ons", + "approval.actShort.act.send": "envoyer des messages", + "approval.actShort.act.spend": "utiliser des outils payants ou externes", + "approval.short.ask": "demande d’abord", + "approval.short.auto": "mis en place tout de suite", + "approval.short.auto_readonly": "tout de suite si lecture seule", + "files.title": "Fichiers", + "files.help": "Laissez un champ vide pour utiliser la limite de l’add-on. Votre limite ne peut être que plus basse.", + "files.receiveLabel": "Plus gros fichier qu’il lit, en Mio", + "files.sendLabel": "Plus gros fichier qu’il envoie, en Mio", + "files.invalid": "Saisissez un nombre supérieur à 0.", + "files.tooHigh": "L’add-on autorise au plus {{max}} ici.", + "files.now": "Actuellement : {{limit}} ({{layer}}).", + "files.receiveHelp": "Les fichiers que les gens envoient au bot. Un fichier plus gros est refusé, et le bot indique la limite atteinte.", + "files.sendHelp": "Les fichiers que l’agent crée et que le bot renvoie. Un fichier plus gros n’est pas envoyé, et le bot indique la limite atteinte.", + "files.addon.off": "Cet add-on ne transfère pas de fichiers dans ce sens, il n’y a donc rien à régler.", + "files.addon.declared": "L’add-on déclare jusqu’à {{max}}.", + "files.addon.open": "L’add-on ne déclare aucune limite, c’est donc celle de la plateforme qui s’applique.", + "files.host": "Aucun bot ne peut transférer un fichier de plus de {{max}}. Un administrateur règle cela dans les paramètres.", + "files.layer.owner": "limite définie pour ce bot", + "files.layer.addon": "limite de l’add-on", + "files.layer.host": "limite de la plateforme", + "perm.title": "Ce que les expéditeurs peuvent faire", + "perm.help": "Choisissez ce que les personnes qui écrivent à ce bot peuvent demander à l’agent. Ce qui est désactivé le reste pour tout le monde, quoi que dise un rôle ou une association.", + "perm.noCapsTitle": "Aucune autorisation n’est activée, ces fonctions sont donc désactivées", + "perm.noCapsText": "Ce bot a été enregistré avant l’existence des autorisations, ou aucune n’a encore été choisie. Tant que vous n’en activez pas, les expéditeurs ne peuvent pas envoyer de fichiers à l’agent, mettre en place des déclencheurs, enregistrer en mémoire ni faire publier l’agent dans un canal. Les messages reçoivent toujours une réponse.", + "perm.usual": "Autoriser l’usuel", + "perm.usualHelp": "Active les autorisations courantes qui restent dans LocalAI : {{names}}. Les autorisations sensibles n’en font jamais partie.", + "perm.usualDone": "Les autorisations usuelles sont activées. Enregistrez pour les conserver.", + "perm.usualSaved": "Les autorisations usuelles sont enregistrées pour {{name}}.", + "perm.usualSet": "Autoriser l’ensemble usuel", + "perm.usualSetHelp": "Coche les autorisations courantes qui restent dans LocalAI, pour ce dont l’agent dispose. Les autorisations sensibles n’en font jamais partie.", + "perm.sameAs": "Comme l’agent", + "perm.sameAsHelp": "Copie les autorisations de l’agent à cet instant, sensibles comprises. C’est une copie d’aujourd’hui qui ne suit pas les changements ultérieurs de l’agent.", + "perm.block.noAgent": "Choisissez d’abord un agent.", + "perm.block.unreadable": "Les autorisations de cet agent n’ont pas pu être lues.", + "perm.block.nothing": "Cet agent n’a aucune autorisation qu’une connexion puisse utiliser.", + "perm.usualTicked": "{{count}} cochée(s) : {{names}}. Enregistrez pour les conserver.", + "perm.usualNone": "Rien de nouveau à cocher : les autorisations usuelles de l’agent sont déjà activées.", + "perm.usualSkipped": "Non cochées, car l’agent n’en dispose pas : {{names}}.", + "perm.sameTicked": "{{count}} autorisations copiées depuis {{agent}} : {{names}}. C’est une copie d’aujourd’hui qui ne suit pas les changements ultérieurs de l’agent. Enregistrez pour les conserver.", + "perm.agentLacks": "L’agent ne dispose pas de ceci.", + "perm.confirmTitle": "Copier aussi les autorisations sensibles ?", + "perm.confirmText": "L’agent a ces autorisations sensibles. Une fois activées, un expéditeur que vous avez associé pourrait les utiliser via ce bot :", + "perm.confirmYes": "Les copier aussi", + "perm.confirmNo": "Annuler", + "perm.rule": "Les personnes de la liste d’autorisation peuvent utiliser les autorisations cochées. Les autorisations sensibles exigent en plus que la personne soit associée.", + "perm.stuckTitle": "Des autorisations sensibles sont cochées, mais personne n’est associé", + "perm.stuckText": "Une autorisation sensible ne fonctionne que pour une personne que vous avez associée à ce bot. Tant que vous n’associez personne, aucun expéditeur ne peut utiliser : {{names}}.", + "perm.channelOnlyTitle": "La liste d’autorisation ne contient que des canaux", + "perm.channelOnlyText": "Les membres d’un canal ne sont pas approuvés un par un, ils ne peuvent donc pas utiliser les autorisations cochées. Ajoutez leur utilisateur aux personnes autorisées.", + "perm.stuckPair": "Associer votre compte", + "perm.sensitive": "Expéditeurs associés uniquement", + "perm.sensitiveHelp": "Sensible. Jamais accordée à un rôle. Seul un expéditeur que vous avez associé peut l’utiliser, et chaque usage est consigné.", + "perm.capsLegend": "Autorisations de ce bot", + "perm.capsHelp": "Le plafond pour tout le monde. Un rôle peut recevoir moins, jamais plus.", + "perm.rolesTitle": "Rôles", + "perm.rolesHelp": "La plateforme indique à l’hôte les rôles d’un expéditeur. Choisissez ce que chaque rôle peut faire. Les autorisations sensibles ne sont jamais accordées à un rôle, et un rôle ne peut recevoir qu’une autorisation activée plus haut.", + "perm.rolesNone": "Activez plus haut une autorisation que cet add-on peut accorder à un rôle, et elle apparaîtra ici.", + "perm.rolesGrid": "Ce que chaque rôle peut faire", + "perm.roleCap": "{{role}} : {{cap}}", + "perm.channelsTitle": "Canaux approuvés", + "perm.channelsHelp": "Les chats où l’agent peut publier à la demande d’un expéditeur, en plus du chat où il répond. Un canal non approuvé exige un expéditeur associé pour chaque message. Au plus {{max}}.", + "perm.channelsOff": "Activez « Publier dans les canaux approuvés » plus haut pour choisir des canaux.", + "perm.channelId": "ID du canal", + "perm.channelIdHelp": "L’ID que la plateforme donne au chat. Sans espace.", + "perm.channelLabel": "Nom", + "perm.channelLabelHelp": "Un nom pour vous et pour l’agent. Facultatif.", + "perm.channelApproved": "Approuvé", + "perm.channelApprovedHelp": "Activé : l’agent peut publier ici à la demande de tout expéditeur autorisé.", + "perm.channelAdd": "Ajouter un canal", + "perm.channelRemove": "Retirer le canal {{id}}", + "perm.remove": "Retirer", + "perm.channelFull": "La liste est pleine.", + "perm.channelProblem.id": "Chaque canal exige un ID de 1 à {{max}} octets, sans espace.", + "perm.channelProblem.label": "Un nom compte au plus {{max}} caractères.", + "perm.channelProblem.repeat": "Un canal figure deux fois dans la liste.", + "perm.channelProblem.count": "Au plus {{max}} canaux.", + "perm.triggersNeed": "Activez Proposer des déclencheurs pour l’utiliser.", + "pair.title": "Expéditeurs associés", + "pair.help": "Un expéditeur associé a prouvé qu’il contrôle un compte autorisé. Seul un expéditeur associé peut utiliser les autorisations sensibles. Au plus {{max}} ; une association prend fin après {{days}} jours.", + "pair.start": "Associer un expéditeur", + "pair.startHelp": "Génère un code. Envoyez-le au bot en message privé depuis le compte à associer, dans les dix minutes. Le code ne sert qu’une fois.", + "pair.needSave": "Enregistrez d’abord ce bot, puis associez un expéditeur.", + "pair.codeTitle": "Votre code d’association", + "pair.codeHelp": "Envoyez ce code au bot en message privé depuis le compte à associer. Il n’est affiché que maintenant, ne sert qu’une fois et expire à {{time}}.", + "pair.codeGone": "Le code a expiré. Générez-en un nouveau si l’expéditeur n’est pas encore dans la liste.", + "pair.done": "Associé : {{sender}}.", + "pair.dismiss": "Masquer le code", + "pair.none": "Personne n’est associé.", + "pair.expires": "fin {{time}}", + "pair.remove": "Retirer", + "pair.removeSender": "Retirer l’association de {{sender}}", + "pair.full": "Trois expéditeurs sont associés. Retirez-en un pour en associer un autre.", + "access.title": "Dernières décisions", + "access.needSave": "Enregistrez d’abord ce bot pour voir ses dernières décisions.", + "access.refresh": "Actualiser", + "access.recentTitle": "Dernières décisions", + "access.recentHelp": "Les dernières vérifications de ce serveur, les plus récentes d’abord. Elles contiennent des ID et des noms d’autorisations, jamais le texte des messages. Elles sont effacées au redémarrage du serveur.", + "access.recentNone": "Aucune décision pour le moment.", + "access.allowed": "autorisé", + "access.denied": "refusé", + "access.who": "expéditeur {{senders}}", + "access.roles": "rôles {{roles}}", + "access.via": "via {{via}}", + "can.title": "Ce que ce bot peut faire", + "can.help": "La vérification que l’hôte effectue pour chaque message, appliquée à ce formulaire tel qu’il est maintenant. Elle ne change rien. Une ligne bloquée montre le premier problème ; d’autres peuvent suivre.", + "can.sender": "Si l’expéditeur est", + "can.sender.listed": "Une personne de la liste autorisée", + "can.sender.channel_member": "Un membre d’un canal autorisé", + "can.sender.anyone": "N’importe qui écrivant au bot", + "can.sender.paired": "Une personne que vous avez associée", + "can.sender.role": "Rôle : {{title}}", + "can.fromForm": "Calculé d’après le formulaire tel qu’il est maintenant. Enregistrez pour conserver ces réglages.", + "can.fromFormNew": "Pas encore enregistré. Calculé d’après le formulaire tel qu’il est maintenant.", + "can.loading": "Vérification de ce que ce bot peut faire", + "can.stale": "Mise à jour", + "can.errorTitle": "Impossible de vérifier ce que ce bot peut faire", + "can.retry": "Réessayer", + "can.allowed": "Autorisé", + "can.blocked": "Bloqué", + "can.summary": "{{allowed}} sur {{total}} autorisés pour cet expéditeur.", + "can.via.paired": "parce que cette personne est associée", + "can.via.allowlist": "parce que cette personne figure dans la liste autorisée", + "can.via.role": "via le rôle {{role}}", + "can.noPerson": "Aucune personne n’est encore dans la liste autorisée. Voici ce qu’obtiendrait une personne de la liste.", + "can.then": "Pas encore vérifié : {{list}}.", + "can.then.server": "les paramètres du serveur", + "can.then.tools": "le commutateur Outils de l’agent", + "can.then.agent": "les autorisations de l’agent", + "can.then.connection": "les autorisations de cette connexion", + "can.then.sender": "qui envoie", + "can.then.approval": "la façon dont les propositions sont approuvées", + "can.noMessage": "Non autorisé.", + "fix.open_connection": "Afficher le réglage", + "fix.open_agent": "Ouvrir l’agent", + "fix.open_server_setting": "Ouvrir les paramètres", + "fix.pair_sender": "Associer un expéditeur", + "fix.add_person": "Ajouter une personne", + "fix.save": "Garder ces modifications", + "fix.token": "Coller le jeton", + "fix.tokenNew": "Coller un nouveau jeton", + "fix.agent": "Choisir un agent", + "fix.usual": "Choisir des autorisations", + "fix.turnOn": "Activer", + "fix.newTab": "(s’ouvre dans un nouvel onglet)", + "fix.askAdmin": "Demander à un administrateur", + "ready.title": "État de préparation", + "ready.help": "Ce qui doit être en place pour que ce bot fonctionne, d’après le formulaire tel qu’il est maintenant.", + "ready.checkingSummary": "Vérification de ce que fera le formulaire.", + "ready.allGood": "Tout le nécessaire est en place.", + "ready.problems": "{{count}} à examiner.", + "ready.showAll": "Tout afficher ({{count}})", + "ready.showProblems": "Afficher seulement ce qui demande attention", + "ready.checking": "Vérification.", + "ready.unchecked": "Impossible de le vérifier. Voir « Ce que ce bot peut faire » ci-dessous.", + "ready.row.token": "Jeton", + "ready.row.agent": "Agent", + "ready.row.who": "Qui peut l’utiliser", + "ready.row.permissions": "Autorisations", + "ready.row.triggers": "Déclencheurs", + "ready.row.files": "Fichiers", + "ready.row.notify": "Notifications", + "ready.state.ok": "OK", + "ready.state.attention": "À vérifier", + "ready.state.off": "Désactivé", + "ready.state.after_save": "Après l’enregistrement", + "ready.state.broken": "Ne fonctionne pas", + "ready.state.checking": "Vérification", + "ready.state.unknown": "Inconnu", + "ready.token.missing": "Collez le jeton du bot. Sans lui, rien n’est reçu.", + "ready.token.newTyped": "Le jeton est vérifié auprès de la plateforme à l’enregistrement.", + "ready.token.typed": "Le nouveau jeton est vérifié auprès de la plateforme à l’enregistrement.", + "ready.token.failed": "La plateforme a refusé le jeton : {{reason}}", + "ready.token.checked": "La plateforme a accepté le jeton. {{bot}}", + "ready.token.saved": "Un jeton est enregistré. La plateforme l’a accepté à l’enregistrement.", + "ready.agent.none": "Choisissez l’agent qui répond.", + "ready.agent.credentialsOnly": "Aucun agent. Cette connexion ne garde que les identifiants et ne répond à aucun message.", + "ready.agent.gone": "L’agent choisi n’est plus dans votre liste. Choisissez-en un autre.", + "ready.agent.noModel": "{{name}} n’a pas de modèle et ne peut donc pas répondre. Donnez-lui-en un.", + "ready.agent.ok": "{{name}} répond, avec le modèle {{model}}.", + "ready.agent.okUnknown": "Un agent est choisi.", + "ready.who.anyone": "Toute personne pouvant écrire au bot peut l’utiliser.", + "ready.who.nobody": "Personne n’est encore autorisé, le bot ignore donc tous les messages. Ajoutez une personne.", + "ready.who.channelsOnly": "Seuls des canaux figurent dans la liste. Les personnes d’un canal peuvent discuter, mais n’ont aucune autorisation tant que vous ne les ajoutez pas comme personnes.", + "ready.who.listed_one": "{{count}} personne ou chat autorisé.", + "ready.who.listed_other": "{{count}} personnes ou chats autorisés.", + "ready.who.mixed": "{{count}} entrées autorisées, personnes et canaux.", + "ready.perm.none": "Aucune autorisation n’est activée. Les expéditeurs peuvent discuter, rien de plus.", + "ready.perm.noneSaved": "Aucune autorisation n’est activée : fichiers, mémoire, déclencheurs et publications sont donc désactivés.", + "ready.perm.onlyOwnRows": "Les autres autorisations sont désactivées. Les déclencheurs et les fichiers se trouvent plus bas.", + "ready.perm.ok_one": "{{count}} autorisation est activée et un expéditeur peut l’utiliser.", + "ready.perm.ok_other": "{{count}} autorisations sont activées et un expéditeur peut les utiliser.", + "ready.perm.dead_one": "Personne ne peut utiliser {{names}}. {{message}}", + "ready.perm.dead_other": "Personne ne peut utiliser {{count}} des autorisations activées : {{names}}. La première : {{message}}", + "ready.triggers.off": "L’agent ne peut pas créer de rappels ni de tâches planifiées ici.", + "ready.triggers.blocked": "Les déclencheurs sont activés, mais ils ne peuvent pas être utilisés. {{message}}", + "ready.triggers.ok.ask": "L’agent peut proposer des déclencheurs. Vous confirmez chacun avant son démarrage.", + "ready.triggers.ok.auto": "L’agent peut proposer des déclencheurs. Ils démarrent tout de suite, sans vous demander.", + "ready.triggers.ok.auto_readonly": "L’agent peut proposer des déclencheurs. Un déclencheur qui ne fait que lire démarre tout de suite ; vous confirmez tous les autres.", + "ready.files.offReceive": "Les expéditeurs ne peuvent pas envoyer de fichiers à l’agent.", + "ready.files.offBoth": "Les expéditeurs ne peuvent pas envoyer de fichiers à l’agent, et le bot n’en renvoie aucun.", + "ready.files.blocked": "La réception de fichiers est activée, mais elle ne peut pas être utilisée. {{message}}", + "ready.files.ok": "Les expéditeurs peuvent envoyer des fichiers à l’agent, et le bot peut en renvoyer.", + "ready.files.okReceive": "Les expéditeurs peuvent envoyer des fichiers à l’agent. Le bot n’en renvoie aucun.", + "ready.notify.off": "Aucun chat ne reçoit d’avis de ce bot.", + "ready.notify.ok": "Les avis vont au chat {{chat}}.", + "dismiss": "Ignorer", + "credentialsOnly": "Identifiants uniquement", + "agentCredentialsOnly": "Aucun agent (identifiants uniquement)", + "agentOptionalHint": "Facultatif. Sans agent, cette connexion ne garde que le jeton, la liste des dépôts et le mode d’écriture pour vos chats. Elle ne répond à aucun message. Choisissez un agent pour qu’elle écoute.", + "sender.line_one": "{{name}} · {{count}} message · {{id}}", + "sender.line_other": "{{name}} · {{count}} messages · {{id}}", + "ready.blocker.nobodyLetIn": "Personne n’est encore admis.", + "ready.blocker.nobodyPaired": "Il faut une personne associée au bot, et personne n’est encore associé.", + "optional": "facultatif" +} diff --git a/addons/internal/connector-ui/src/i18n/connector.id.json b/addons/internal/connector-ui/src/i18n/connector.id.json new file mode 100644 index 0000000000..b369032dd9 --- /dev/null +++ b/addons/internal/connector-ui/src/i18n/connector.id.json @@ -0,0 +1,281 @@ +{ + "answer.title": "Cara menjawab", + "answer.replyLabel": "Saat agen bekerja", + "answer.reply.off": "Mengetik, lalu jawaban", + "answer.reply.progress": "Progres: alat yang dipakai", + "answer.reply.stream": "Langsung: jawaban saat sedang ditulis", + "answer.replyHelp": "Progres dan Langsung menampilkan satu pesan proses, lalu jawaban menggantikannya. Di platform yang punya draf, Langsung menampilkan draf ke chat pribadi, lalu jawaban menyusul. Jawaban yang panjang dilanjutkan di pesan baru.", + "answer.conversationLabel": "Percakapan", + "answer.conversationHelp": "Kirim /new (atau !new) di aplikasi untuk memulai chat baru.", + "answer.conversation.sender": "Satu chat per orang", + "answer.conversation.chat": "Satu chat per grup atau kanal", + "answer.conversation.window": "Per orang, baru setelah jeda", + "answer.conversation.thread": "Chat baru per pesan; balasan dilanjutkan", + "answer.window": "Jeda tanpa pesan, dalam menit", + "answer.windowRange": "Gunakan bilangan bulat dari 1 sampai {{max}}.", + "answer.files": "Kirim file yang dibuat agen", + "aside.title": "Cara kerja", + "aside.step1": "Beri nama koneksi dan masukkan token-nya. Token disimpan terenkripsi dan tidak pernah ditampilkan lagi.", + "aside.step2": "Pilih agen yang menjawab. Agen itu perlu model.", + "aside.step3": "Tentukan siapa yang boleh menulis kepadanya dan cara menjawabnya.", + "aside.testTitle": "Uji koneksi", + "aside.testText": "Saat Anda menyimpan token baru, LocalAI memeriksanya dan menyebut akun pemiliknya. Gunakan Uji pada baris yang tersimpan untuk memeriksanya lagi.", + "add": "Tambah", + "more": "Lainnya", + "less": "Lebih sedikit", + "answer.filesHelp": "Setelah jawaban, hingga 5 file. Program dan skrip tidak dikirim. Batas ukuran ada di File.", + "answer.summary": "Jawaban: {{reply}} · {{conversation}}", + "answer.short.off": "mengetik", + "answer.short.progress": "progres", + "answer.short.stream": "jawaban langsung", + "answer.short.sender": "satu chat per orang", + "answer.short.chat": "satu chat per grup", + "answer.short.window": "chat baru setelah {{count}} mnt", + "answer.short.thread": "satu chat per pesan", + "approval.title": "Timer dan pemicu dari chat", + "approval.help": "Saat agen menyiapkan sesuatu yang berjalan nanti (“dalam 10 menit”, “setiap hari”), setiap jalan adalah satu eksekusi model.", + "approval.mode.ask": "Tanya saya: bot mengusulkannya di chat dan orangnya menjawab ya atau tidak", + "approval.mode.auto": "Siapkan langsung dan beri tahu orangnya cara membatalkannya", + "approval.mode.auto_readonly": "Langsung jika jalannya hanya membaca dan melapor; selain itu tanya", + "approval.limitsHelp": "Batas ini membatasi apa yang disiapkan tanpa bertanya. Kosongkan kolom untuk memakai nilai bawaan. Apa pun yang melebihi batas akan ditanyakan.", + "approval.max_pending": "Jumlah timer dan pemicu terbanyak sekaligus", + "approval.min_gap_minutes": "Menit paling sedikit antar jalan pemicu berulang", + "approval.max_runs_per_day": "Jalan terbanyak per hari, secara total", + "approval.max_lifetime_days": "Masa aktif terlama, dalam hari", + "approval.invalid": "Gunakan bilangan bulat dari 1 sampai {{max}}, atau kosongkan.", + "approval.default": "Bawaan: {{value}}.", + "approval.summary": "Pemicu: {{mode}}", + "approval.effect": "Usulan: {{mode}}", + "approval.limits": "Batas", + "approval.acts.title": "Jalan boleh bertindak", + "approval.acts.help": "Nonaktif: jalan dari pemicu yang dibuat di chat ini hanya membaca dan melapor. Aktif: jalan juga boleh melakukan apa yang Anda centang di bawah, dan hanya yang dimiliki agen.", + "approval.acts.modes": "Tanya saya: Anda selalu memutuskan. Langsung: hanya saat Anda dipasangkan sebagai pengirim dan setiap tindakan dicentang di sini.", + "approval.acts.act.files": "Menulis file di folder agen", + "approval.acts.act.files.effect": "Sebuah jalan dapat membuat dan mengubah file yang disimpan agen.", + "approval.acts.act.addons": "Memakai aksi add-on milik agen", + "approval.acts.act.addons.effect": "Sebuah jalan dapat memakai apa yang dilakukan add-on milik agen, termasuk perintah.", + "approval.acts.act.send": "Mengirim pesan selain ke tempat ia melapor", + "approval.acts.act.send.effect": "Sebuah jalan dapat menulis sendiri ke chat dan kanal lain.", + "approval.acts.act.spend": "Memakai alat berbayar atau eksternal dan jaringan", + "approval.acts.act.spend.effect": "Sebuah jalan dapat memanggil layanan di luar LocalAI, yang mungkin berbiaya.", + "approval.acts.lacks": "Agen tidak memiliki ini.", + "approval.acts.sensitive": "Sensitif", + "approval.effectActs": ", jalan boleh: {{acts}}", + "approval.actShort.act.files": "menulis file", + "approval.actShort.act.addons": "memakai add-on", + "approval.actShort.act.send": "mengirim pesan", + "approval.actShort.act.spend": "memakai alat berbayar atau eksternal", + "approval.short.ask": "tanya dulu", + "approval.short.auto": "siapkan langsung", + "approval.short.auto_readonly": "langsung jika hanya baca", + "files.title": "File", + "files.help": "Kosongkan kolom untuk memakai batas add-on. Batas Anda hanya boleh lebih rendah dari itu.", + "files.receiveLabel": "File terbesar yang dibaca, dalam MiB", + "files.sendLabel": "File terbesar yang dikirim, dalam MiB", + "files.invalid": "Gunakan angka lebih besar dari 0.", + "files.tooHigh": "Add-on mengizinkan paling banyak {{max}} di sini.", + "files.now": "Sekarang: {{limit}} ({{layer}}).", + "files.receiveHelp": "File yang dikirim orang ke bot. File yang lebih besar ditolak, dan bot menyebut batas mana yang tercapai.", + "files.sendHelp": "File yang dibuat agen dan dikirim kembali oleh bot. File yang lebih besar tidak dikirim, dan bot menyebut batas mana yang tercapai.", + "files.addon.off": "Add-on ini tidak memindahkan file ke arah ini, jadi tidak ada yang perlu diatur.", + "files.addon.declared": "Add-on menyatakan hingga {{max}}.", + "files.addon.open": "Add-on tidak menyatakan batas, jadi batas platform berlaku.", + "files.host": "Tidak ada bot yang dapat memindahkan file di atas {{max}}. Administrator mengaturnya di pengaturan.", + "files.layer.owner": "batas yang ditetapkan untuk bot ini", + "files.layer.addon": "batas add-on", + "files.layer.host": "batas platform", + "perm.title": "Apa yang boleh dilakukan pengirim", + "perm.help": "Pilih apa yang boleh diminta orang yang menulis ke bot ini kepada agen. Apa pun yang dimatikan tetap mati untuk semua orang, apa pun kata peran atau pairing.", + "perm.noCapsTitle": "Tidak ada izin yang aktif, jadi fitur ini nonaktif", + "perm.noCapsText": "Bot ini disimpan sebelum izin ada, atau belum ada izin yang dipilih. Sampai Anda mengaktifkan beberapa izin, pengirim tidak dapat mengirim file ke agen, menyiapkan pemicu, menyimpan ke memori, atau meminta agen memposting ke kanal. Pesan tetap dijawab.", + "perm.usual": "Izinkan yang umum", + "perm.usualHelp": "Mengaktifkan izin sehari-hari yang tetap berada di dalam LocalAI: {{names}}. Izin sensitif tidak pernah disertakan.", + "perm.usualDone": "Izin yang umum sudah aktif. Simpan untuk mempertahankannya.", + "perm.usualSaved": "Izin yang umum telah disimpan untuk {{name}}.", + "perm.usualSet": "Izinkan set yang umum", + "perm.usualSetHelp": "Mencentang izin sehari-hari yang tetap berada di dalam LocalAI, sesuai yang dimiliki agen. Izin sensitif tidak pernah disertakan.", + "perm.sameAs": "Sama seperti agen", + "perm.sameAsHelp": "Menyalin izin yang dimiliki agen saat ini, termasuk yang sensitif. Ini salinan hari ini dan tidak mengikuti perubahan agen berikutnya.", + "perm.block.noAgent": "Pilih agen terlebih dahulu.", + "perm.block.unreadable": "Izin agen ini tidak dapat dibaca.", + "perm.block.nothing": "Agen ini tidak memiliki izin yang dapat dipakai koneksi.", + "perm.usualTicked": "{{count}} dicentang: {{names}}. Simpan untuk mempertahankannya.", + "perm.usualNone": "Tidak ada yang perlu dicentang: izin umum yang dimiliki agen sudah aktif.", + "perm.usualSkipped": "Tidak dicentang karena agen tidak memilikinya: {{names}}.", + "perm.sameTicked": "{{count}} izin dari {{agent}} disalin: {{names}}. Ini salinan hari ini dan tidak mengikuti perubahan agen berikutnya. Simpan untuk mempertahankannya.", + "perm.agentLacks": "Agen tidak memiliki ini.", + "perm.confirmTitle": "Salin juga izin yang sensitif?", + "perm.confirmText": "Agen memiliki izin sensitif ini. Setelah aktif, pengirim yang Anda pasangkan dapat memakainya lewat bot ini:", + "perm.confirmYes": "Salin juga", + "perm.confirmNo": "Batal", + "perm.rule": "Orang di daftar yang diizinkan dapat memakai izin yang dicentang. Izin sensitif juga mengharuskan orangnya dipasangkan.", + "perm.stuckTitle": "Izin sensitif dicentang, tetapi belum ada yang dipasangkan", + "perm.stuckText": "Izin sensitif hanya berlaku untuk orang yang Anda pasangkan dengan bot ini. Sampai Anda memasangkan seseorang, tidak ada pengirim yang dapat memakai: {{names}}.", + "perm.channelOnlyTitle": "Daftar yang diizinkan hanya berisi kanal", + "perm.channelOnlyText": "Anggota kanal tidak dipercaya satu per satu, jadi mereka tidak dapat memakai izin yang dicentang. Tambahkan pengguna mereka ke orang yang diizinkan.", + "perm.stuckPair": "Pasangkan akun Anda", + "perm.sensitive": "Hanya pengirim yang dipasangkan", + "perm.sensitiveHelp": "Sensitif. Tidak pernah diberikan ke peran. Hanya pengirim yang Anda pasangkan yang dapat memakainya, dan setiap pemakaian dicatat.", + "perm.capsLegend": "Izin bot ini", + "perm.capsHelp": "Batas atas untuk semua orang. Sebuah peran bisa diberi lebih sedikit, tidak pernah lebih banyak.", + "perm.rolesTitle": "Peran", + "perm.rolesHelp": "Platform memberi tahu host peran apa yang dimiliki pengirim. Pilih apa yang boleh dilakukan tiap peran. Izin sensitif tidak pernah diberikan ke peran, dan peran hanya bisa mendapat izin yang aktif di atas.", + "perm.rolesNone": "Aktifkan izin di atas yang dapat diberikan add-on ini ke peran, dan izin itu muncul di sini.", + "perm.rolesGrid": "Apa yang boleh dilakukan tiap peran", + "perm.roleCap": "{{role}}: {{cap}}", + "perm.channelsTitle": "Kanal yang disetujui", + "perm.channelsHelp": "Chat tempat agen boleh memposting saat pengirim memintanya, selain chat tempat ia menjawab. Kanal yang belum disetujui memerlukan pengirim yang dipasangkan untuk setiap pesan. Paling banyak {{max}}.", + "perm.channelsOff": "Aktifkan “Posting ke kanal yang disetujui” di atas untuk memilih kanal.", + "perm.channelId": "ID kanal", + "perm.channelIdHelp": "ID yang diberikan platform untuk chat. Tanpa spasi.", + "perm.channelLabel": "Nama", + "perm.channelLabelHelp": "Nama untuk Anda dan agen. Opsional.", + "perm.channelApproved": "Disetujui", + "perm.channelApprovedHelp": "Aktif: agen boleh memposting di sini atas permintaan pengirim yang diizinkan.", + "perm.channelAdd": "Tambah kanal", + "perm.channelRemove": "Hapus kanal {{id}}", + "perm.remove": "Hapus", + "perm.channelFull": "Daftar sudah penuh.", + "perm.channelProblem.id": "Setiap kanal memerlukan ID sepanjang 1 sampai {{max}} byte, tanpa spasi.", + "perm.channelProblem.label": "Nama paling banyak {{max}} karakter.", + "perm.channelProblem.repeat": "Sebuah kanal terdaftar dua kali.", + "perm.channelProblem.count": "Paling banyak {{max}} kanal.", + "perm.triggersNeed": "Aktifkan Usulkan pemicu untuk memakai ini.", + "pair.title": "Pengirim yang dipasangkan", + "pair.help": "Pengirim yang dipasangkan sudah membuktikan bahwa ia mengendalikan akun yang diizinkan. Hanya pengirim yang dipasangkan yang dapat memakai izin sensitif. Paling banyak {{max}}; pairing berakhir setelah {{days}} hari.", + "pair.start": "Pasangkan pengirim", + "pair.startHelp": "Membuat kode. Kirim kode itu ke bot sebagai pesan langsung dari akun yang akan dipasangkan, dalam sepuluh menit. Kode hanya berlaku sekali.", + "pair.needSave": "Simpan bot ini terlebih dahulu, lalu pasangkan pengirim.", + "pair.codeTitle": "Kode pairing Anda", + "pair.codeHelp": "Kirim kode ini ke bot sebagai pesan langsung dari akun yang akan dipasangkan. Kode ini hanya ditampilkan sekarang, berlaku sekali, dan kedaluwarsa pada {{time}}.", + "pair.codeGone": "Kode sudah kedaluwarsa. Buat yang baru jika pengirim belum terdaftar.", + "pair.done": "Dipasangkan: {{sender}}.", + "pair.dismiss": "Sembunyikan kode", + "pair.none": "Belum ada yang dipasangkan.", + "pair.expires": "berakhir {{time}}", + "pair.remove": "Hapus", + "pair.removeSender": "Hapus pairing {{sender}}", + "pair.full": "Tiga pengirim sudah dipasangkan. Hapus satu untuk memasangkan yang lain.", + "access.title": "Keputusan terbaru", + "access.needSave": "Simpan bot ini terlebih dahulu untuk melihat keputusan terbarunya.", + "access.refresh": "Segarkan", + "access.recentTitle": "Keputusan terbaru", + "access.recentHelp": "Pemeriksaan terbaru di server ini, yang terbaru di atas. Isinya ID dan nama izin, tidak pernah teks pesan. Daftar ini direset saat server dimulai ulang.", + "access.recentNone": "Belum ada keputusan.", + "access.allowed": "diizinkan", + "access.denied": "ditolak", + "access.who": "pengirim {{senders}}", + "access.roles": "peran {{roles}}", + "access.via": "lewat {{via}}", + "can.title": "Apa yang bisa dilakukan bot ini", + "can.help": "Pemeriksaan yang dijalankan host untuk setiap pesan, dijalankan pada formulir ini sebagaimana adanya sekarang. Tidak mengubah apa pun. Baris yang diblokir menampilkan masalah pertama; masalah lain mungkin menyusul.", + "can.sender": "Jika pengirimnya adalah", + "can.sender.listed": "Orang di daftar yang diizinkan", + "can.sender.channel_member": "Anggota kanal yang diizinkan", + "can.sender.anyone": "Siapa saja yang menulis ke bot", + "can.sender.paired": "Orang yang Anda pasangkan", + "can.sender.role": "Peran: {{title}}", + "can.fromForm": "Dihitung dari formulir sebagaimana adanya sekarang. Simpan untuk mempertahankan pengaturan ini.", + "can.fromFormNew": "Belum disimpan. Dihitung dari formulir sebagaimana adanya sekarang.", + "can.loading": "Memeriksa apa yang bisa dilakukan bot ini", + "can.stale": "Memperbarui", + "can.errorTitle": "Tidak dapat memeriksa apa yang bisa dilakukan bot ini", + "can.retry": "Coba lagi", + "can.allowed": "Diizinkan", + "can.blocked": "Diblokir", + "can.summary": "{{allowed}} dari {{total}} diizinkan untuk pengirim ini.", + "can.via.paired": "karena orang ini dipasangkan", + "can.via.allowlist": "karena orang ini ada di daftar yang diizinkan", + "can.via.role": "lewat peran {{role}}", + "can.noPerson": "Belum ada orang di daftar yang diizinkan. Ini yang akan didapat orang yang terdaftar.", + "can.then": "Belum diperiksa: {{list}}.", + "can.then.server": "pengaturan server", + "can.then.tools": "sakelar Alat milik agen", + "can.then.agent": "izin agen", + "can.then.connection": "izin koneksi ini", + "can.then.sender": "siapa yang mengirim", + "can.then.approval": "cara usulan disetujui", + "can.noMessage": "Tidak diizinkan.", + "fix.open_connection": "Tampilkan pengaturan", + "fix.open_agent": "Buka agen", + "fix.open_server_setting": "Buka Pengaturan", + "fix.pair_sender": "Pasangkan pengirim", + "fix.add_person": "Tambah orang", + "fix.save": "Simpan perubahan ini", + "fix.token": "Tempel token", + "fix.tokenNew": "Tempel token baru", + "fix.agent": "Pilih agen", + "fix.usual": "Pilih izin", + "fix.turnOn": "Aktifkan", + "fix.newTab": "(dibuka di tab baru)", + "fix.askAdmin": "Tanya administrator", + "ready.title": "Kesiapan", + "ready.help": "Apa yang harus ada agar bot ini berfungsi, untuk formulir sebagaimana adanya sekarang.", + "ready.checkingSummary": "Memeriksa apa yang akan dilakukan formulir.", + "ready.allGood": "Semua yang diperlukan sudah ada.", + "ready.problems": "{{count}} perlu diperiksa.", + "ready.showAll": "Tampilkan semua {{count}}", + "ready.showProblems": "Tampilkan hanya yang perlu perhatian", + "ready.checking": "Memeriksa.", + "ready.unchecked": "Tidak dapat memeriksa ini. Lihat “Apa yang bisa dilakukan bot ini” di bawah.", + "ready.row.token": "Token", + "ready.row.agent": "Agen", + "ready.row.who": "Siapa yang boleh memakai", + "ready.row.permissions": "Izin", + "ready.row.triggers": "Pemicu", + "ready.row.files": "File", + "ready.row.notify": "Notifikasi", + "ready.state.ok": "OK", + "ready.state.attention": "Perlu perhatian", + "ready.state.off": "Nonaktif", + "ready.state.after_save": "Setelah disimpan", + "ready.state.broken": "Tidak berfungsi", + "ready.state.checking": "Memeriksa", + "ready.state.unknown": "Tidak diketahui", + "ready.token.missing": "Tempel token bot. Tanpa token, tidak ada yang diterima.", + "ready.token.newTyped": "Token diperiksa ke platform saat Anda menyimpan.", + "ready.token.typed": "Token baru diperiksa ke platform saat Anda menyimpan.", + "ready.token.failed": "Platform menolak token: {{reason}}", + "ready.token.checked": "Platform menerima token. {{bot}}", + "ready.token.saved": "Token sudah tersimpan. Platform menerimanya saat disimpan.", + "ready.agent.none": "Pilih agen yang menjawab.", + "ready.agent.credentialsOnly": "Tanpa agen. Koneksi ini hanya menyimpan kredensial dan tidak menjawab pesan.", + "ready.agent.gone": "Agen yang dipilih tidak ada lagi di daftar Anda. Pilih yang lain.", + "ready.agent.noModel": "{{name}} tidak punya model, jadi tidak dapat menjawab. Berikan satu model.", + "ready.agent.ok": "{{name}} menjawab, dengan model {{model}}.", + "ready.agent.okUnknown": "Agen sudah dipilih.", + "ready.who.anyone": "Siapa saja yang dapat mengirim pesan ke bot boleh memakainya.", + "ready.who.nobody": "Belum ada yang diizinkan, jadi bot mengabaikan semua pesan. Tambahkan orang.", + "ready.who.channelsOnly": "Hanya kanal di daftar. Orang di kanal dapat chat, tetapi tidak dapat memakai izin apa pun sampai Anda menambahkan mereka sebagai orang.", + "ready.who.listed_one": "{{count}} orang atau chat diizinkan.", + "ready.who.listed_other": "{{count}} orang atau chat diizinkan.", + "ready.who.mixed": "{{count}} entri diizinkan, orang dan kanal.", + "ready.perm.none": "Tidak ada izin yang aktif. Pengirim dapat chat, dan tidak lebih.", + "ready.perm.noneSaved": "Tidak ada izin yang aktif, jadi file, memori, pemicu, dan posting nonaktif.", + "ready.perm.onlyOwnRows": "Izin lainnya nonaktif. Pemicu dan file ada di bawah.", + "ready.perm.ok_one": "{{count}} izin aktif dan pengirim dapat memakainya.", + "ready.perm.ok_other": "{{count}} izin aktif dan pengirim dapat memakainya.", + "ready.perm.dead_one": "Tidak ada yang dapat memakai {{names}}. {{message}}", + "ready.perm.dead_other": "Tidak ada yang dapat memakai {{count}} izin yang aktif: {{names}}. Pertama: {{message}}", + "ready.triggers.off": "Agen tidak dapat menyetel pengingat atau tugas terjadwal di sini.", + "ready.triggers.blocked": "Pemicu aktif, tetapi tidak dapat dipakai. {{message}}", + "ready.triggers.ok.ask": "Agen dapat mengusulkan pemicu. Anda mengonfirmasi tiap pemicu sebelum dimulai.", + "ready.triggers.ok.auto": "Agen dapat mengusulkan pemicu. Pemicu langsung dimulai, tanpa bertanya kepada Anda.", + "ready.triggers.ok.auto_readonly": "Agen dapat mengusulkan pemicu. Pemicu yang hanya membaca langsung dimulai; pemicu lain Anda konfirmasi.", + "ready.files.offReceive": "Pengirim tidak dapat mengirim file ke agen.", + "ready.files.offBoth": "Pengirim tidak dapat mengirim file ke agen, dan bot tidak mengirim file kembali.", + "ready.files.blocked": "Menerima file aktif, tetapi tidak dapat dipakai. {{message}}", + "ready.files.ok": "Pengirim dapat mengirim file ke agen, dan bot dapat mengirim file kembali.", + "ready.files.okReceive": "Pengirim dapat mengirim file ke agen. Bot tidak mengirim file kembali.", + "ready.notify.off": "Tidak ada chat yang menerima notifikasi dari bot ini.", + "ready.notify.ok": "Notifikasi dikirim ke chat {{chat}}.", + "dismiss": "Abaikan", + "credentialsOnly": "Hanya kredensial", + "agentCredentialsOnly": "Tanpa agen (hanya kredensial)", + "agentOptionalHint": "Opsional. Tanpa agen, koneksi ini hanya menyimpan token, daftar repositori, dan mode tulis untuk chat Anda. Koneksi tidak menjawab pesan. Pilih agen agar koneksi mulai mendengarkan.", + "sender.line_one": "{{name}} · {{count}} pesan · {{id}}", + "sender.line_other": "{{name}} · {{count}} pesan · {{id}}", + "ready.blocker.nobodyLetIn": "Belum ada yang diizinkan masuk.", + "ready.blocker.nobodyPaired": "Ini memerlukan orang yang Anda pasangkan dengan bot, dan belum ada yang dipasangkan.", + "optional": "opsional" +} diff --git a/addons/internal/connector-ui/src/i18n/connector.it.json b/addons/internal/connector-ui/src/i18n/connector.it.json new file mode 100644 index 0000000000..9c37640904 --- /dev/null +++ b/addons/internal/connector-ui/src/i18n/connector.it.json @@ -0,0 +1,281 @@ +{ + "answer.title": "Come risponde", + "answer.replyLabel": "Mentre l'agente lavora", + "answer.reply.off": "Sta scrivendo, poi la risposta", + "answer.reply.progress": "Avanzamento: gli strumenti che usa", + "answer.reply.stream": "In diretta: la risposta mentre viene scritta", + "answer.replyHelp": "Avanzamento e In diretta mostrano un solo messaggio di lavoro, che la risposta poi sostituisce. Su una piattaforma con le bozze, In diretta mostra a una chat privata una bozza, seguita dalla risposta. Una risposta lunga continua in nuovi messaggi.", + "answer.conversationLabel": "Conversazioni", + "answer.conversationHelp": "Invia /new (o !new) nell'app per iniziare una nuova chat.", + "answer.conversation.sender": "Una chat per persona", + "answer.conversation.chat": "Una chat per gruppo o canale", + "answer.conversation.window": "Per persona, nuova dopo una pausa", + "answer.conversation.thread": "Nuova chat per messaggio; le risposte proseguono", + "answer.window": "Pausa, in minuti", + "answer.windowRange": "Usa un numero intero da 1 a {{max}}.", + "answer.files": "Invia i file creati dall'agente", + "aside.title": "Come funziona", + "aside.step1": "Dai un nome alla connessione e inserisci il suo token. Il token viene cifrato e non viene più mostrato.", + "aside.step2": "Scegli l'agente che risponde. Ha bisogno di un modello.", + "aside.step3": "Scegli chi può scrivergli e come risponde.", + "aside.testTitle": "Prova la connessione", + "aside.testText": "Quando salvi un nuovo token, LocalAI lo verifica e indica l'account a cui appartiene. Usa Prova su una riga salvata per verificarlo di nuovo.", + "add": "Aggiungi", + "more": "Altro", + "less": "Meno", + "answer.filesHelp": "Dopo la risposta, fino a 5 file. Programmi e script non vengono inviati. Il limite di dimensione è in File.", + "answer.summary": "Risponde: {{reply}} · {{conversation}}", + "answer.short.off": "sta scrivendo", + "answer.short.progress": "avanzamento", + "answer.short.stream": "risposta in diretta", + "answer.short.sender": "una chat per persona", + "answer.short.chat": "una chat per gruppo", + "answer.short.window": "nuova chat dopo {{count}} min", + "answer.short.thread": "una chat per messaggio", + "approval.title": "Timer e trigger dalla chat", + "approval.help": "Quando l'agente imposta qualcosa che parte più tardi («tra 10 minuti», «ogni giorno»), ogni esecuzione è un'esecuzione del modello.", + "approval.mode.ask": "Chiedimelo: il bot lo propone nella chat e la persona risponde sì o no", + "approval.mode.auto": "Impostalo subito e spiega alla persona come annullarlo", + "approval.mode.auto_readonly": "Subito se le esecuzioni si limitano a leggere e riferire; altrimenti chiedi", + "approval.limitsHelp": "Questi limiti definiscono cosa viene impostato senza chiedere. Lascia vuoto un campo per usare il valore predefinito. Oltre un limite si chiede sempre.", + "approval.max_pending": "Numero massimo di timer e trigger contemporanei", + "approval.min_gap_minutes": "Minuti minimi tra le esecuzioni di un trigger ricorrente", + "approval.max_runs_per_day": "Numero massimo di esecuzioni al giorno, in totale", + "approval.max_lifetime_days": "Durata massima, in giorni", + "approval.invalid": "Usa un numero intero da 1 a {{max}}, oppure lascia vuoto.", + "approval.default": "Predefinito: {{value}}.", + "approval.summary": "Trigger: {{mode}}", + "approval.effect": "Proposte: {{mode}}", + "approval.limits": "Limiti", + "approval.acts.title": "Le esecuzioni possono agire", + "approval.acts.help": "Disattivato: le esecuzioni di un trigger creato da questa chat si limitano a leggere e riferire. Attivato: possono fare anche ciò che spunti qui sotto, e solo ciò che l'agente ha.", + "approval.acts.modes": "Chiedimelo: decidi sempre tu. Subito: solo se sei abbinato come mittente e ogni azione è spuntata qui.", + "approval.acts.act.files": "Scrivere file nelle cartelle dell'agente", + "approval.acts.act.files.effect": "Un'esecuzione può creare e modificare i file che l'agente conserva.", + "approval.acts.act.addons": "Usare le azioni dei componenti aggiuntivi dell'agente", + "approval.acts.act.addons.effect": "Un'esecuzione può usare ciò che fanno i componenti aggiuntivi dell'agente, comandi compresi.", + "approval.acts.act.send": "Inviare messaggi oltre al punto in cui riferisce", + "approval.acts.act.send.effect": "Un'esecuzione può scrivere da sola ad altre chat e canali.", + "approval.acts.act.spend": "Usare strumenti a pagamento o esterni e la rete", + "approval.acts.act.spend.effect": "Un'esecuzione può chiamare servizi esterni a LocalAI, che possono costare denaro.", + "approval.acts.lacks": "L'agente non ha questo permesso.", + "approval.acts.sensitive": "Sensibile", + "approval.effectActs": ", le esecuzioni possono: {{acts}}", + "approval.actShort.act.files": "scrivere file", + "approval.actShort.act.addons": "usare i componenti aggiuntivi", + "approval.actShort.act.send": "inviare messaggi", + "approval.actShort.act.spend": "usare strumenti a pagamento o esterni", + "approval.short.ask": "chiede prima", + "approval.short.auto": "impostati subito", + "approval.short.auto_readonly": "subito se in sola lettura", + "files.title": "File", + "files.help": "Lascia vuoto un campo per usare il limite del componente aggiuntivo. Il tuo limite può solo essere più basso.", + "files.receiveLabel": "File più grande che legge, in MiB", + "files.sendLabel": "File più grande che invia, in MiB", + "files.invalid": "Usa un numero maggiore di 0.", + "files.tooHigh": "Il componente aggiuntivo consente al massimo {{max}} qui.", + "files.now": "Ora: {{limit}} ({{layer}}).", + "files.receiveHelp": "I file che le persone inviano al bot. Un file più grande viene rifiutato e il bot dice quale limite ha raggiunto.", + "files.sendHelp": "I file che l'agente crea e il bot rimanda indietro. Un file più grande non viene inviato e il bot dice quale limite ha raggiunto.", + "files.addon.off": "Questo componente aggiuntivo non sposta file in questa direzione, quindi non c'è nulla da impostare.", + "files.addon.declared": "Il componente aggiuntivo dichiara fino a {{max}}.", + "files.addon.open": "Il componente aggiuntivo non dichiara alcun limite, quindi vale quello della piattaforma.", + "files.host": "Nessun bot può spostare un file più grande di {{max}}. Lo imposta un amministratore nelle impostazioni.", + "files.layer.owner": "limite impostato per questo bot", + "files.layer.addon": "limite del componente aggiuntivo", + "files.layer.host": "limite della piattaforma", + "perm.title": "Cosa possono fare i mittenti", + "perm.help": "Scegli cosa possono chiedere all'agente le persone che scrivono a questo bot. Ciò che è disattivato resta disattivato per tutti, qualunque cosa dica un ruolo o un abbinamento.", + "perm.noCapsTitle": "Nessun permesso è attivo, quindi queste funzioni sono disattivate", + "perm.noCapsText": "Questo bot è stato salvato prima che esistessero i permessi, oppure non ne è stato scelto ancora nessuno. Finché non ne attivi qualcuno, i mittenti non possono inviare file all'agente, impostare trigger, salvare in memoria né far pubblicare l'agente in un canale. I messaggi ricevono comunque risposta.", + "perm.usual": "Consenti i permessi usuali", + "perm.usualHelp": "Attiva i permessi di ogni giorno che restano dentro LocalAI: {{names}}. Quelli sensibili non sono mai inclusi.", + "perm.usualDone": "I permessi usuali sono attivi. Salva per mantenerli.", + "perm.usualSaved": "I permessi usuali sono salvati per {{name}}.", + "perm.usualSet": "Consenti l'insieme usuale", + "perm.usualSetHelp": "Spunta i permessi di ogni giorno che restano dentro LocalAI, per ciò che l'agente ha. Quelli sensibili non sono mai inclusi.", + "perm.sameAs": "Come l'agente", + "perm.sameAsHelp": "Copia i permessi che l'agente ha ora, anche quelli sensibili. È una copia di oggi e non segue le modifiche successive dell'agente.", + "perm.block.noAgent": "Scegli prima un agente.", + "perm.block.unreadable": "Non è stato possibile leggere i permessi di questo agente.", + "perm.block.nothing": "Questo agente non ha alcun permesso che una connessione possa usare.", + "perm.usualTicked": "Spuntati {{count}}: {{names}}. Salva per mantenerli.", + "perm.usualNone": "Niente di nuovo da spuntare: i permessi usuali che l'agente ha sono già attivi.", + "perm.usualSkipped": "Non spuntati, perché l'agente non li ha: {{names}}.", + "perm.sameTicked": "Copiati {{count}} permessi di {{agent}}: {{names}}. È una copia di oggi e non segue le modifiche successive dell'agente. Salva per mantenerli.", + "perm.agentLacks": "L'agente non ha questo permesso.", + "perm.confirmTitle": "Copiare anche i permessi sensibili?", + "perm.confirmText": "L'agente ha questi permessi sensibili. Una volta attivi, un mittente abbinato potrebbe usarli tramite questo bot:", + "perm.confirmYes": "Copiali anche", + "perm.confirmNo": "Annulla", + "perm.rule": "Le persone nell'elenco dei consentiti possono usare i permessi spuntati. Quelli sensibili richiedono anche che la persona sia abbinata.", + "perm.stuckTitle": "Ci sono permessi sensibili spuntati, ma nessuno è abbinato", + "perm.stuckText": "Un permesso sensibile funziona solo per una persona abbinata a questo bot. Finché non abbini qualcuno, nessun mittente può usare: {{names}}.", + "perm.channelOnlyTitle": "L'elenco dei consentiti contiene solo canali", + "perm.channelOnlyText": "I membri di un canale non sono considerati attendibili uno per uno, quindi non possono usare i permessi spuntati. Aggiungi il loro utente alle persone consentite.", + "perm.stuckPair": "Abbina il tuo account", + "perm.sensitive": "Solo mittenti abbinati", + "perm.sensitiveHelp": "Sensibile. Non viene mai dato a un ruolo. Può usarlo solo un mittente abbinato, e ogni uso viene registrato.", + "perm.capsLegend": "Permessi di questo bot", + "perm.capsHelp": "Il tetto per tutti. A un ruolo si può dare meno, mai di più.", + "perm.rolesTitle": "Ruoli", + "perm.rolesHelp": "La piattaforma comunica all'host quali ruoli ha un mittente. Scegli cosa può fare ogni ruolo. I permessi sensibili non vengono mai dati a un ruolo, e un ruolo può ottenere solo un permesso attivo qui sopra.", + "perm.rolesNone": "Attiva qui sopra un permesso che questo componente aggiuntivo può dare a un ruolo, e comparirà qui.", + "perm.rolesGrid": "Cosa può fare ogni ruolo", + "perm.roleCap": "{{role}}: {{cap}}", + "perm.channelsTitle": "Canali approvati", + "perm.channelsHelp": "Chat in cui l'agente può pubblicare quando un mittente lo chiede, oltre alla chat in cui risponde. Un canale non approvato richiede un mittente abbinato per ogni messaggio. Al massimo {{max}}.", + "perm.channelsOff": "Attiva «Pubblica nei canali approvati» qui sopra per scegliere i canali.", + "perm.channelId": "ID del canale", + "perm.channelIdHelp": "L'ID che la piattaforma dà alla chat. Senza spazi.", + "perm.channelLabel": "Nome", + "perm.channelLabelHelp": "Un nome per te e per l'agente. Facoltativo.", + "perm.channelApproved": "Approvato", + "perm.channelApprovedHelp": "Attivo: l'agente può pubblicare qui su richiesta di qualsiasi mittente consentito.", + "perm.channelAdd": "Aggiungi un canale", + "perm.channelRemove": "Rimuovi il canale {{id}}", + "perm.remove": "Rimuovi", + "perm.channelFull": "L'elenco è pieno.", + "perm.channelProblem.id": "Ogni canale richiede un ID da 1 a {{max}} byte, senza spazi.", + "perm.channelProblem.label": "Un nome ha al massimo {{max}} caratteri.", + "perm.channelProblem.repeat": "Un canale è presente due volte.", + "perm.channelProblem.count": "Al massimo {{max}} canali.", + "perm.triggersNeed": "Attiva Proponi trigger per usarlo.", + "pair.title": "Mittenti abbinati", + "pair.help": "Un mittente abbinato ha dimostrato di controllare un account consentito. Solo un mittente abbinato può usare i permessi sensibili. Al massimo {{max}}; un abbinamento termina dopo {{days}} giorni.", + "pair.start": "Abbina un mittente", + "pair.startHelp": "Crea un codice. Invialo al bot come messaggio diretto dall'account da abbinare, entro dieci minuti. Il codice vale una sola volta.", + "pair.needSave": "Salva prima questo bot, poi abbina un mittente.", + "pair.codeTitle": "Il tuo codice di abbinamento", + "pair.codeHelp": "Invia questo codice al bot come messaggio diretto dall'account da abbinare. Viene mostrato solo ora, vale una sola volta e scade alle {{time}}.", + "pair.codeGone": "Il codice è scaduto. Creane uno nuovo se il mittente non è ancora in elenco.", + "pair.done": "Abbinato: {{sender}}.", + "pair.dismiss": "Nascondi il codice", + "pair.none": "Nessuno è abbinato.", + "pair.expires": "termina {{time}}", + "pair.remove": "Rimuovi", + "pair.removeSender": "Rimuovi l'abbinamento di {{sender}}", + "pair.full": "Tre mittenti sono abbinati. Rimuovine uno per abbinarne un altro.", + "access.title": "Ultime decisioni", + "access.needSave": "Salva prima questo bot per vedere le sue ultime decisioni.", + "access.refresh": "Aggiorna", + "access.recentTitle": "Ultime decisioni", + "access.recentHelp": "Gli ultimi controlli di questo server, dal più recente. Contengono ID e nomi dei permessi, mai il testo dei messaggi. Si azzerano al riavvio del server.", + "access.recentNone": "Ancora nessuna decisione.", + "access.allowed": "consentito", + "access.denied": "negato", + "access.who": "mittente {{senders}}", + "access.roles": "ruoli {{roles}}", + "access.via": "tramite {{via}}", + "can.title": "Cosa può fare questo bot", + "can.help": "Il controllo che l'host esegue per ogni messaggio, applicato a questo modulo così com'è ora. Non cambia nulla. Una riga bloccata mostra il primo problema; potrebbero seguirne altri.", + "can.sender": "Se il mittente è", + "can.sender.listed": "Una persona consentita", + "can.sender.channel_member": "Membro di un canale consentito", + "can.sender.anyone": "Chiunque scriva al bot", + "can.sender.paired": "Una persona che hai abbinato", + "can.sender.role": "Ruolo: {{title}}", + "can.fromForm": "Calcolato dal modulo così com'è ora. Salva per mantenere queste impostazioni.", + "can.fromFormNew": "Non ancora salvato. Calcolato dal modulo così com'è ora.", + "can.loading": "Controllo di cosa può fare questo bot", + "can.stale": "Aggiornamento", + "can.errorTitle": "Non è stato possibile controllare cosa può fare questo bot", + "can.retry": "Riprova", + "can.allowed": "Consentito", + "can.blocked": "Bloccato", + "can.summary": "{{allowed}} su {{total}} consentiti per questo mittente.", + "can.via.paired": "perché questa persona è abbinata", + "can.via.allowlist": "perché questa persona è nell'elenco dei consentiti", + "can.via.role": "tramite il ruolo {{role}}", + "can.noPerson": "Nessuna persona è ancora nell'elenco dei consentiti. Questo è ciò che otterrebbe una persona in elenco.", + "can.then": "Non ancora controllati: {{list}}.", + "can.then.server": "le impostazioni del server", + "can.then.tools": "l'interruttore Strumenti dell'agente", + "can.then.agent": "i permessi dell'agente", + "can.then.connection": "i permessi di questa connessione", + "can.then.sender": "chi sta scrivendo", + "can.then.approval": "come vengono approvate le proposte", + "can.noMessage": "Non consentito.", + "fix.open_connection": "Mostra l'impostazione", + "fix.open_agent": "Apri l'agente", + "fix.open_server_setting": "Apri le impostazioni", + "fix.pair_sender": "Abbina un mittente", + "fix.add_person": "Aggiungi una persona", + "fix.save": "Mantieni queste modifiche", + "fix.token": "Incolla il token", + "fix.tokenNew": "Incolla un nuovo token", + "fix.agent": "Scegli un agente", + "fix.usual": "Scegli i permessi", + "fix.turnOn": "Attiva", + "fix.newTab": "(si apre in una nuova scheda)", + "fix.askAdmin": "Chiedi a un amministratore", + "ready.title": "Stato di preparazione", + "ready.help": "Cosa deve essere a posto perché questo bot funzioni, per il modulo così com'è ora.", + "ready.checkingSummary": "Controllo di cosa farà il modulo.", + "ready.allGood": "Tutto il necessario è a posto.", + "ready.problems": "{{count}} da controllare.", + "ready.showAll": "Mostra tutti e {{count}}", + "ready.showProblems": "Mostra solo ciò che richiede attenzione", + "ready.checking": "Controllo in corso.", + "ready.unchecked": "Non è stato possibile controllarlo. Vedi «Cosa può fare questo bot» qui sotto.", + "ready.row.token": "Token", + "ready.row.agent": "Agente", + "ready.row.who": "Chi può usarlo", + "ready.row.permissions": "Permessi", + "ready.row.triggers": "Trigger", + "ready.row.files": "File", + "ready.row.notify": "Notifiche", + "ready.state.ok": "OK", + "ready.state.attention": "Richiede attenzione", + "ready.state.off": "Disattivato", + "ready.state.after_save": "Dopo il salvataggio", + "ready.state.broken": "Non funziona", + "ready.state.checking": "Controllo", + "ready.state.unknown": "Sconosciuto", + "ready.token.missing": "Incolla il token del bot. Senza, non si riceve nulla.", + "ready.token.newTyped": "Il token viene verificato con la piattaforma al salvataggio.", + "ready.token.typed": "Il nuovo token viene verificato con la piattaforma al salvataggio.", + "ready.token.failed": "La piattaforma ha rifiutato il token: {{reason}}", + "ready.token.checked": "La piattaforma ha accettato il token. {{bot}}", + "ready.token.saved": "Un token è salvato. La piattaforma lo ha accettato al salvataggio.", + "ready.agent.none": "Scegli l'agente che risponde.", + "ready.agent.credentialsOnly": "Nessun agente. Questa connessione conserva solo le credenziali e non risponde ad alcun messaggio.", + "ready.agent.gone": "L'agente scelto non è più nel tuo elenco. Scegline un altro.", + "ready.agent.noModel": "{{name}} non ha un modello, quindi non può rispondere. Assegnagliene uno.", + "ready.agent.ok": "{{name}} risponde, con il modello {{model}}.", + "ready.agent.okUnknown": "È stato scelto un agente.", + "ready.who.anyone": "Chiunque possa scrivere al bot può usarlo.", + "ready.who.nobody": "Nessuno è ancora consentito, quindi il bot ignora ogni messaggio. Aggiungi una persona.", + "ready.who.channelsOnly": "Nell'elenco ci sono solo canali. Le persone di un canale possono chattare, ma non possono usare alcun permesso finché non le aggiungi come persone.", + "ready.who.listed_one": "{{count}} persona o chat è consentita.", + "ready.who.listed_other": "{{count}} persone o chat sono consentite.", + "ready.who.mixed": "{{count}} voci sono consentite, persone e canali.", + "ready.perm.none": "Nessun permesso è attivo. I mittenti possono chattare, e nient'altro.", + "ready.perm.noneSaved": "Nessun permesso è attivo, quindi file, memoria, trigger e pubblicazioni sono disattivati.", + "ready.perm.onlyOwnRows": "Gli altri permessi sono disattivati. Trigger e file sono qui sotto.", + "ready.perm.ok_one": "{{count}} permesso è attivo e un mittente può usarlo.", + "ready.perm.ok_other": "{{count}} permessi sono attivi e un mittente può usarli.", + "ready.perm.dead_one": "Nessuno può usare {{names}}. {{message}}", + "ready.perm.dead_other": "Nessuno può usare {{count}} dei permessi attivi: {{names}}. Il primo: {{message}}", + "ready.triggers.off": "L'agente non può impostare promemoria o attività pianificate qui.", + "ready.triggers.blocked": "I trigger sono attivi, ma non si possono usare. {{message}}", + "ready.triggers.ok.ask": "L'agente può proporre trigger. Confermi tu ciascuno prima che parta.", + "ready.triggers.ok.auto": "L'agente può proporre trigger. Partono subito, senza chiedertelo.", + "ready.triggers.ok.auto_readonly": "L'agente può proporre trigger. Un trigger che si limita a leggere parte subito; gli altri li confermi tu.", + "ready.files.offReceive": "I mittenti non possono inviare file all'agente.", + "ready.files.offBoth": "I mittenti non possono inviare file all'agente, e il bot non ne rimanda indietro.", + "ready.files.blocked": "La ricezione dei file è attiva, ma non si può usare. {{message}}", + "ready.files.ok": "I mittenti possono inviare file all'agente, e il bot può rimandarne indietro.", + "ready.files.okReceive": "I mittenti possono inviare file all'agente. Il bot non ne rimanda indietro.", + "ready.notify.off": "Nessuna chat riceve avvisi da questo bot.", + "ready.notify.ok": "Gli avvisi vanno alla chat {{chat}}.", + "dismiss": "Chiudi", + "credentialsOnly": "Solo credenziali", + "agentCredentialsOnly": "Nessun agente (solo credenziali)", + "agentOptionalHint": "Facoltativo. Senza un agente questa connessione conserva solo il token, l'elenco dei repository e la modalità di scrittura per le tue chat. Non risponde ad alcun messaggio. Scegli un agente per farla restare in ascolto.", + "sender.line_one": "{{name}} · {{count}} messaggio · {{id}}", + "sender.line_other": "{{name}} · {{count}} messaggi · {{id}}", + "ready.blocker.nobodyLetIn": "Nessuno è ancora ammesso.", + "ready.blocker.nobodyPaired": "Serve una persona abbinata al bot, e nessuno è ancora abbinato.", + "optional": "facoltativo" +} diff --git a/addons/internal/connector-ui/src/i18n/connector.ko.json b/addons/internal/connector-ui/src/i18n/connector.ko.json new file mode 100644 index 0000000000..e0f20944e0 --- /dev/null +++ b/addons/internal/connector-ui/src/i18n/connector.ko.json @@ -0,0 +1,281 @@ +{ + "answer.title": "답변 방식", + "answer.replyLabel": "에이전트가 작업하는 동안", + "answer.reply.off": "입력 중 표시 후 답변", + "answer.reply.progress": "진행 상황: 사용하는 도구", + "answer.reply.stream": "실시간: 작성되는 대로 답변 표시", + "answer.replyHelp": "진행 상황과 실시간은 작업 중 메시지 하나를 보여 주고, 답변이 나오면 그 메시지를 대체합니다. 초안을 지원하는 플랫폼에서 실시간은 비공개 채팅에 초안을 대신 보여 주고, 이어서 답변이 옵니다. 긴 답변은 새 메시지로 이어집니다.", + "answer.conversationLabel": "대화", + "answer.conversationHelp": "앱에서 /new (또는 !new)를 보내면 새 채팅이 시작됩니다.", + "answer.conversation.sender": "사람마다 채팅 하나", + "answer.conversation.chat": "그룹이나 채널마다 채팅 하나", + "answer.conversation.window": "사람마다, 조용한 시간이 지나면 새로 시작", + "answer.conversation.thread": "메시지마다 새 채팅, 답글은 이어짐", + "answer.window": "조용한 시간(분)", + "answer.windowRange": "1부터 {{max}}까지의 정수를 입력하세요.", + "answer.files": "에이전트가 만든 파일 보내기", + "aside.title": "작동 방식", + "aside.step1": "연결의 이름과 토큰을 입력하세요. 토큰은 봉인되어 다시 표시되지 않습니다.", + "aside.step2": "답변할 에이전트를 고르세요. 에이전트에는 모델이 필요합니다.", + "aside.step3": "메시지를 보낼 수 있는 사람과 답변 방식을 정하세요.", + "aside.testTitle": "연결 테스트", + "aside.testText": "새 토큰을 저장하면 LocalAI가 토큰을 확인하고 해당 계정의 이름을 알려 줍니다. 저장된 항목에서 테스트를 누르면 다시 확인합니다.", + "add": "추가", + "more": "더 보기", + "less": "간단히", + "answer.filesHelp": "답변 뒤에 파일을 최대 5개 보냅니다. 프로그램과 스크립트는 보내지 않습니다. 크기 제한은 파일에서 설정합니다.", + "answer.summary": "답변: {{reply}} · {{conversation}}", + "answer.short.off": "입력 중 표시", + "answer.short.progress": "진행 상황", + "answer.short.stream": "실시간 답변", + "answer.short.sender": "사람마다 채팅", + "answer.short.chat": "그룹마다 채팅", + "answer.short.window": "{{count}}분 후 새 채팅", + "answer.short.thread": "메시지마다 채팅", + "approval.title": "채팅의 타이머와 트리거", + "approval.help": "에이전트가 나중에 실행될 작업(\"10분 뒤\", \"매일\")을 설정하면, 실행할 때마다 모델이 한 번 실행됩니다.", + "approval.mode.ask": "먼저 묻기: 봇이 채팅에서 제안하고 상대가 예 또는 아니요로 답합니다", + "approval.mode.auto": "바로 설정하고 취소하는 방법을 상대에게 알림", + "approval.mode.auto_readonly": "실행이 읽고 보고하기만 하면 바로 설정, 그 외에는 묻기", + "approval.limitsHelp": "이 한도는 묻지 않고 설정할 수 있는 범위를 제한합니다. 비워 두면 기본값을 씁니다. 한도를 넘는 것은 대신 묻습니다.", + "approval.max_pending": "동시에 둘 수 있는 타이머와 트리거의 최대 개수", + "approval.min_gap_minutes": "반복 트리거 실행 사이의 최소 간격(분)", + "approval.max_runs_per_day": "하루 총 실행 횟수 상한", + "approval.max_lifetime_days": "최대 유지 기간(일)", + "approval.invalid": "1부터 {{max}}까지의 정수를 입력하거나 비워 두세요.", + "approval.default": "기본값: {{value}}.", + "approval.summary": "트리거: {{mode}}", + "approval.effect": "제안: {{mode}}", + "approval.limits": "한도", + "approval.acts.title": "실행이 할 수 있는 동작", + "approval.acts.help": "꺼짐: 이 채팅에서 만든 트리거의 실행은 읽고 보고하기만 합니다. 켜짐: 아래에서 선택한 동작도 할 수 있으며, 에이전트가 가진 권한 범위에서만 가능합니다.", + "approval.acts.modes": "먼저 묻기: 항상 직접 결정합니다. 바로 설정: 보낸 사람으로 페어링되어 있고 모든 동작이 여기서 선택된 경우에만 적용됩니다.", + "approval.acts.act.files": "에이전트의 폴더에 파일 쓰기", + "approval.acts.act.files.effect": "실행이 에이전트가 보관하는 파일을 만들고 바꿀 수 있습니다.", + "approval.acts.act.addons": "에이전트의 애드온 동작 사용", + "approval.acts.act.addons.effect": "실행이 에이전트의 애드온이 하는 일을 명령까지 포함해 사용할 수 있습니다.", + "approval.acts.act.send": "보고하는 곳 밖으로 메시지 보내기", + "approval.acts.act.send.effect": "실행이 스스로 다른 채팅과 채널에 쓸 수 있습니다.", + "approval.acts.act.spend": "유료 또는 외부 도구와 네트워크 사용", + "approval.acts.act.spend.effect": "실행이 LocalAI 밖의 서비스를 호출할 수 있으며 비용이 발생할 수 있습니다.", + "approval.acts.lacks": "에이전트에게 이 권한이 없습니다.", + "approval.acts.sensitive": "민감함", + "approval.effectActs": ", 실행이 할 수 있는 동작: {{acts}}", + "approval.actShort.act.files": "파일 쓰기", + "approval.actShort.act.addons": "애드온 사용", + "approval.actShort.act.send": "메시지 보내기", + "approval.actShort.act.spend": "유료 또는 외부 도구 사용", + "approval.short.ask": "먼저 묻기", + "approval.short.auto": "바로 설정", + "approval.short.auto_readonly": "읽기 전용이면 바로", + "files.title": "파일", + "files.help": "비워 두면 애드온의 제한을 따릅니다. 직접 정하는 제한은 애드온의 제한보다 낮게만 설정할 수 있습니다.", + "files.receiveLabel": "읽을 수 있는 최대 파일 크기(MiB)", + "files.sendLabel": "보낼 수 있는 최대 파일 크기(MiB)", + "files.invalid": "0보다 큰 숫자를 입력하세요.", + "files.tooHigh": "이 애드온은 여기에 최대 {{max}}까지 허용합니다.", + "files.now": "현재: {{limit}} ({{layer}}).", + "files.receiveHelp": "사람들이 봇에 보내는 파일입니다. 더 큰 파일은 거부되며, 봇이 어떤 제한에 걸렸는지 알려 줍니다.", + "files.sendHelp": "에이전트가 만들어 봇이 돌려보내는 파일입니다. 더 큰 파일은 보내지 않으며, 봇이 어떤 제한에 걸렸는지 알려 줍니다.", + "files.addon.off": "이 애드온은 이 방향으로 파일을 옮기지 않으므로 설정할 것이 없습니다.", + "files.addon.declared": "애드온이 최대 {{max}}까지 지원한다고 밝혔습니다.", + "files.addon.open": "애드온이 제한을 밝히지 않았으므로 플랫폼의 제한이 적용됩니다.", + "files.host": "어떤 봇도 {{max}}보다 큰 파일은 옮길 수 없습니다. 관리자가 설정에서 정합니다.", + "files.layer.owner": "이 봇에 설정한 제한", + "files.layer.addon": "애드온의 제한", + "files.layer.host": "플랫폼의 제한", + "perm.title": "보낸 사람이 할 수 있는 일", + "perm.help": "이 봇에 메시지를 보내는 사람이 에이전트에게 요청할 수 있는 일을 고르세요. 꺼 둔 항목은 역할이나 페어링과 관계없이 모두에게 꺼져 있습니다.", + "perm.noCapsTitle": "켜진 권한이 없어서 아래 기능은 꺼져 있습니다", + "perm.noCapsText": "이 봇은 권한 기능이 생기기 전에 저장되었거나 아직 권한을 고르지 않았습니다. 권한을 켜기 전에는 보낸 사람이 에이전트에게 파일을 보내거나, 트리거를 설정하거나, 메모리에 저장하거나, 채널에 게시하게 할 수 없습니다. 메시지에는 계속 답변합니다.", + "perm.usual": "일반 권한 허용", + "perm.usualHelp": "LocalAI 안에서만 동작하는 일상적인 권한을 켭니다: {{names}}. 민감한 권한은 포함되지 않습니다.", + "perm.usualDone": "일반 권한이 켜졌습니다. 유지하려면 저장하세요.", + "perm.usualSaved": "{{name}}의 일반 권한을 저장했습니다.", + "perm.usualSet": "일반 권한 세트 허용", + "perm.usualSetHelp": "에이전트가 가진 권한 중 LocalAI 안에서만 동작하는 일상적인 권한을 선택합니다. 민감한 권한은 포함되지 않습니다.", + "perm.sameAs": "에이전트와 동일하게", + "perm.sameAsHelp": "에이전트가 지금 가진 권한을 민감한 권한까지 복사합니다. 오늘 시점의 복사본이며 이후 에이전트의 변경을 따르지 않습니다.", + "perm.block.noAgent": "먼저 에이전트를 고르세요.", + "perm.block.unreadable": "이 에이전트의 권한을 읽을 수 없습니다.", + "perm.block.nothing": "이 에이전트에는 연결이 사용할 수 있는 권한이 없습니다.", + "perm.usualTicked": "{{count}}개 선택됨: {{names}}. 유지하려면 저장하세요.", + "perm.usualNone": "새로 선택할 것이 없습니다. 에이전트가 가진 일반 권한이 이미 켜져 있습니다.", + "perm.usualSkipped": "에이전트에게 없어서 선택하지 않음: {{names}}.", + "perm.sameTicked": "{{agent}}의 권한 {{count}}개를 복사했습니다: {{names}}. 오늘 시점의 복사본이며 이후 에이전트의 변경을 따르지 않습니다. 유지하려면 저장하세요.", + "perm.agentLacks": "에이전트에게 이 권한이 없습니다.", + "perm.confirmTitle": "민감한 권한도 복사할까요?", + "perm.confirmText": "에이전트에게 다음 민감한 권한이 있습니다. 켜면 페어링한 보낸 사람이 이 봇을 통해 사용할 수 있습니다:", + "perm.confirmYes": "함께 복사", + "perm.confirmNo": "취소", + "perm.rule": "허용 목록에 있는 사람은 선택된 권한을 사용할 수 있습니다. 민감한 권한은 페어링도 되어 있어야 합니다.", + "perm.stuckTitle": "민감한 권한이 선택되었지만 페어링된 사람이 없습니다", + "perm.stuckText": "민감한 권한은 이 봇과 페어링한 사람만 사용할 수 있습니다. 누군가를 페어링하기 전에는 어떤 보낸 사람도 {{names}}을(를) 사용할 수 없습니다.", + "perm.channelOnlyTitle": "허용 목록에 채널만 있습니다", + "perm.channelOnlyText": "채널 구성원은 개별적으로 신뢰하지 않으므로 선택된 권한을 사용할 수 없습니다. 해당 사용자를 허용된 사람에 추가하세요.", + "perm.stuckPair": "내 계정 페어링", + "perm.sensitive": "페어링된 보낸 사람만", + "perm.sensitiveHelp": "민감함. 역할에는 부여되지 않습니다. 페어링한 보낸 사람만 사용할 수 있으며 사용할 때마다 기록됩니다.", + "perm.capsLegend": "이 봇의 권한", + "perm.capsHelp": "모두에게 적용되는 상한입니다. 역할에는 이보다 적게는 줄 수 있지만 더 많이는 줄 수 없습니다.", + "perm.rolesTitle": "역할", + "perm.rolesHelp": "플랫폼이 보낸 사람의 역할을 호스트에 알려 줍니다. 역할마다 할 수 있는 일을 고르세요. 민감한 권한은 역할에 부여되지 않으며, 역할은 위에서 켜 둔 권한만 받을 수 있습니다.", + "perm.rolesNone": "이 애드온이 역할에 줄 수 있는 권한을 위에서 켜면 여기에 표시됩니다.", + "perm.rolesGrid": "역할별로 할 수 있는 일", + "perm.roleCap": "{{role}}: {{cap}}", + "perm.channelsTitle": "승인된 채널", + "perm.channelsHelp": "답변하는 채팅 외에, 보낸 사람이 요청하면 에이전트가 게시할 수 있는 채팅입니다. 승인되지 않은 채널은 메시지마다 페어링된 보낸 사람이 필요합니다. 최대 {{max}}개.", + "perm.channelsOff": "채널을 고르려면 위에서 \"승인된 채널에 게시\"를 켜세요.", + "perm.channelId": "채널 ID", + "perm.channelIdHelp": "플랫폼이 채팅에 부여한 ID입니다. 공백은 넣지 마세요.", + "perm.channelLabel": "이름", + "perm.channelLabelHelp": "나와 에이전트를 위한 이름입니다. 선택 사항입니다.", + "perm.channelApproved": "승인됨", + "perm.channelApprovedHelp": "켜짐: 허용된 보낸 사람이 요청하면 에이전트가 여기에 게시할 수 있습니다.", + "perm.channelAdd": "채널 추가", + "perm.channelRemove": "채널 {{id}} 제거", + "perm.remove": "제거", + "perm.channelFull": "목록이 가득 찼습니다.", + "perm.channelProblem.id": "채널마다 공백 없는 1~{{max}}바이트의 ID가 필요합니다.", + "perm.channelProblem.label": "이름은 최대 {{max}}자입니다.", + "perm.channelProblem.repeat": "같은 채널이 두 번 나열되어 있습니다.", + "perm.channelProblem.count": "채널은 최대 {{max}}개입니다.", + "perm.triggersNeed": "이 기능을 쓰려면 \"트리거 제안\"을 켜세요.", + "pair.title": "페어링된 보낸 사람", + "pair.help": "페어링된 보낸 사람은 허용된 계정을 직접 관리한다는 것을 증명한 사람입니다. 민감한 권한은 페어링된 보낸 사람만 사용할 수 있습니다. 최대 {{max}}명이며, 페어링은 {{days}}일 후 끝납니다.", + "pair.start": "보낸 사람 페어링", + "pair.startHelp": "코드를 만듭니다. 페어링할 계정에서 10분 안에 이 코드를 봇에 다이렉트 메시지로 보내세요. 코드는 한 번만 사용할 수 있습니다.", + "pair.needSave": "먼저 이 봇을 저장한 다음 보낸 사람을 페어링하세요.", + "pair.codeTitle": "페어링 코드", + "pair.codeHelp": "페어링할 계정에서 이 코드를 봇에 다이렉트 메시지로 보내세요. 코드는 지금만 표시되고, 한 번만 사용할 수 있으며, {{time}}에 만료됩니다.", + "pair.codeGone": "코드가 만료되었습니다. 보낸 사람이 아직 목록에 없다면 새로 만드세요.", + "pair.done": "페어링됨: {{sender}}.", + "pair.dismiss": "코드 숨기기", + "pair.none": "페어링된 사람이 없습니다.", + "pair.expires": "{{time}}에 종료", + "pair.remove": "제거", + "pair.removeSender": "{{sender}}의 페어링 제거", + "pair.full": "보낸 사람 3명이 페어링되어 있습니다. 다른 사람을 페어링하려면 하나를 제거하세요.", + "access.title": "최근 결정", + "access.needSave": "최근 결정을 보려면 먼저 이 봇을 저장하세요.", + "access.refresh": "새로 고침", + "access.recentTitle": "최근 결정", + "access.recentHelp": "이 서버의 최근 확인 내역이며 최신 항목이 위에 있습니다. 권한의 ID와 이름만 담고 메시지 내용은 담지 않습니다. 서버를 다시 시작하면 초기화됩니다.", + "access.recentNone": "아직 결정이 없습니다.", + "access.allowed": "허용됨", + "access.denied": "거부됨", + "access.who": "보낸 사람 {{senders}}", + "access.roles": "역할 {{roles}}", + "access.via": "경로: {{via}}", + "can.title": "이 봇이 할 수 있는 일", + "can.help": "호스트가 메시지마다 실행하는 검사를 이 양식의 현재 상태로 실행한 결과입니다. 아무것도 변경하지 않습니다. 차단된 행에는 첫 번째 문제만 표시되며, 다른 문제가 더 있을 수 있습니다.", + "can.sender": "보낸 사람이", + "can.sender.listed": "허용 목록에 있는 사람", + "can.sender.channel_member": "허용된 채널의 구성원", + "can.sender.anyone": "봇에 메시지를 보내는 모든 사람", + "can.sender.paired": "페어링한 사람", + "can.sender.role": "역할: {{title}}", + "can.fromForm": "양식의 현재 상태로 계산했습니다. 이 설정을 유지하려면 저장하세요.", + "can.fromFormNew": "아직 저장되지 않았습니다. 양식의 현재 상태로 계산했습니다.", + "can.loading": "이 봇이 할 수 있는 일을 확인하는 중", + "can.stale": "업데이트 중", + "can.errorTitle": "이 봇이 할 수 있는 일을 확인하지 못했습니다", + "can.retry": "다시 시도", + "can.allowed": "허용됨", + "can.blocked": "차단됨", + "can.summary": "이 보낸 사람에게 {{total}}개 중 {{allowed}}개 허용.", + "can.via.paired": "이 사람이 페어링되어 있어서", + "can.via.allowlist": "이 사람이 허용 목록에 있어서", + "can.via.role": "역할 {{role}}을(를) 통해", + "can.noPerson": "허용 목록에 아직 사람이 없습니다. 목록에 있는 사람이 받게 될 내용입니다.", + "can.then": "아직 확인하지 않음: {{list}}.", + "can.then.server": "서버 설정", + "can.then.tools": "에이전트의 도구 스위치", + "can.then.agent": "에이전트의 권한", + "can.then.connection": "이 연결의 권한", + "can.then.sender": "보내는 사람", + "can.then.approval": "제안을 승인하는 방식", + "can.noMessage": "허용되지 않습니다.", + "fix.open_connection": "설정 보기", + "fix.open_agent": "에이전트 열기", + "fix.open_server_setting": "설정 열기", + "fix.pair_sender": "보낸 사람 페어링", + "fix.add_person": "사람 추가", + "fix.save": "변경 사항 유지", + "fix.token": "토큰 붙여넣기", + "fix.tokenNew": "새 토큰 붙여넣기", + "fix.agent": "에이전트 고르기", + "fix.usual": "권한 고르기", + "fix.turnOn": "켜기", + "fix.newTab": "(새 탭에서 열림)", + "fix.askAdmin": "관리자에게 요청", + "ready.title": "준비 상태", + "ready.help": "이 봇이 작동하려면 갖춰져야 하는 것을 양식의 현재 상태 기준으로 보여 줍니다.", + "ready.checkingSummary": "양식이 어떻게 동작할지 확인하는 중입니다.", + "ready.allGood": "필요한 것이 모두 갖춰져 있습니다.", + "ready.problems": "확인할 항목 {{count}}개.", + "ready.showAll": "{{count}}개 모두 보기", + "ready.showProblems": "확인이 필요한 항목만 보기", + "ready.checking": "확인하는 중.", + "ready.unchecked": "확인할 수 없습니다. 아래의 \"이 봇이 할 수 있는 일\"을 보세요.", + "ready.row.token": "토큰", + "ready.row.agent": "에이전트", + "ready.row.who": "사용할 수 있는 사람", + "ready.row.permissions": "권한", + "ready.row.triggers": "트리거", + "ready.row.files": "파일", + "ready.row.notify": "알림", + "ready.state.ok": "정상", + "ready.state.attention": "확인 필요", + "ready.state.off": "꺼짐", + "ready.state.after_save": "저장 후", + "ready.state.broken": "작동 안 함", + "ready.state.checking": "확인 중", + "ready.state.unknown": "알 수 없음", + "ready.token.missing": "봇의 토큰을 붙여 넣으세요. 토큰이 없으면 아무것도 받지 못합니다.", + "ready.token.newTyped": "저장할 때 플랫폼에서 토큰을 확인합니다.", + "ready.token.typed": "저장할 때 플랫폼에서 새 토큰을 확인합니다.", + "ready.token.failed": "플랫폼이 토큰을 거부했습니다: {{reason}}", + "ready.token.checked": "플랫폼이 토큰을 받아들였습니다. {{bot}}", + "ready.token.saved": "토큰이 저장되어 있습니다. 저장할 때 플랫폼이 받아들였습니다.", + "ready.agent.none": "답변할 에이전트를 고르세요.", + "ready.agent.credentialsOnly": "에이전트 없음. 이 연결은 자격 증명만 보관하며 메시지에 답변하지 않습니다.", + "ready.agent.gone": "선택한 에이전트가 더 이상 목록에 없습니다. 다른 에이전트를 고르세요.", + "ready.agent.noModel": "{{name}}에는 모델이 없어서 답변할 수 없습니다. 모델을 지정하세요.", + "ready.agent.ok": "{{name}}이(가) 모델 {{model}}(으)로 답변합니다.", + "ready.agent.okUnknown": "에이전트가 선택되어 있습니다.", + "ready.who.anyone": "봇에 메시지를 보낼 수 있는 사람은 누구나 사용할 수 있습니다.", + "ready.who.nobody": "아직 허용된 사람이 없어서 봇이 모든 메시지를 무시합니다. 사람을 추가하세요.", + "ready.who.channelsOnly": "목록에 채널만 있습니다. 채널의 사람들은 채팅할 수 있지만, 사람으로 추가하기 전에는 어떤 권한도 사용할 수 없습니다.", + "ready.who.listed_one": "사람 또는 채팅 {{count}}개가 허용되어 있습니다.", + "ready.who.listed_other": "사람 또는 채팅 {{count}}개가 허용되어 있습니다.", + "ready.who.mixed": "사람과 채널을 합쳐 항목 {{count}}개가 허용되어 있습니다.", + "ready.perm.none": "켜진 권한이 없습니다. 보낸 사람은 채팅만 할 수 있습니다.", + "ready.perm.noneSaved": "켜진 권한이 없어서 파일, 메모리, 트리거, 게시가 꺼져 있습니다.", + "ready.perm.onlyOwnRows": "다른 권한은 꺼져 있습니다. 트리거와 파일은 아래에 있습니다.", + "ready.perm.ok_one": "권한 {{count}}개가 켜져 있으며 보낸 사람이 사용할 수 있습니다.", + "ready.perm.ok_other": "권한 {{count}}개가 켜져 있으며 보낸 사람이 사용할 수 있습니다.", + "ready.perm.dead_one": "아무도 {{names}}을(를) 사용할 수 없습니다. {{message}}", + "ready.perm.dead_other": "켜진 권한 중 {{count}}개를 아무도 사용할 수 없습니다: {{names}}. 첫 번째: {{message}}", + "ready.triggers.off": "에이전트가 여기서 알림이나 예약 작업을 설정할 수 없습니다.", + "ready.triggers.blocked": "트리거가 켜져 있지만 사용할 수 없습니다. {{message}}", + "ready.triggers.ok.ask": "에이전트가 트리거를 제안할 수 있습니다. 시작하기 전에 하나씩 확인합니다.", + "ready.triggers.ok.auto": "에이전트가 트리거를 제안할 수 있습니다. 묻지 않고 바로 시작합니다.", + "ready.triggers.ok.auto_readonly": "에이전트가 트리거를 제안할 수 있습니다. 읽기만 하는 트리거는 바로 시작하고, 그 외에는 직접 확인합니다.", + "ready.files.offReceive": "보낸 사람이 에이전트에게 파일을 보낼 수 없습니다.", + "ready.files.offBoth": "보낸 사람이 에이전트에게 파일을 보낼 수 없고, 봇도 파일을 돌려보내지 않습니다.", + "ready.files.blocked": "파일 받기가 켜져 있지만 사용할 수 없습니다. {{message}}", + "ready.files.ok": "보낸 사람이 에이전트에게 파일을 보낼 수 있고, 봇도 파일을 돌려보낼 수 있습니다.", + "ready.files.okReceive": "보낸 사람이 에이전트에게 파일을 보낼 수 있습니다. 봇은 파일을 돌려보내지 않습니다.", + "ready.notify.off": "이 봇의 알림을 받는 채팅이 없습니다.", + "ready.notify.ok": "알림은 채팅 {{chat}}(으)로 갑니다.", + "dismiss": "닫기", + "credentialsOnly": "자격 증명만", + "agentCredentialsOnly": "에이전트 없음(자격 증명만)", + "agentOptionalHint": "선택 사항입니다. 에이전트가 없으면 이 연결은 토큰, 저장소 목록, 내 채팅의 쓰기 모드만 보관합니다. 메시지에 답변하지 않습니다. 수신하려면 에이전트를 고르세요.", + "sender.line_one": "{{name}} · 메시지 {{count}}개 · {{id}}", + "sender.line_other": "{{name}} · 메시지 {{count}}개 · {{id}}", + "ready.blocker.nobodyLetIn": "아직 아무도 허용되지 않았습니다.", + "ready.blocker.nobodyPaired": "봇과 페어링한 사람이 필요한데, 아직 페어링된 사람이 없습니다.", + "optional": "선택" +} diff --git a/addons/internal/connector-ui/src/i18n/connector.pt-BR.json b/addons/internal/connector-ui/src/i18n/connector.pt-BR.json new file mode 100644 index 0000000000..d55b270a42 --- /dev/null +++ b/addons/internal/connector-ui/src/i18n/connector.pt-BR.json @@ -0,0 +1,281 @@ +{ + "answer.title": "Como responde", + "answer.replyLabel": "Enquanto o agente trabalha", + "answer.reply.off": "Digitando, depois a resposta", + "answer.reply.progress": "Progresso: as ferramentas que usa", + "answer.reply.stream": "Ao vivo: a resposta conforme é escrita", + "answer.replyHelp": "Progresso e Ao vivo mostram uma única mensagem de trabalho, que a resposta substitui. Em uma plataforma com rascunhos, Ao vivo mostra um rascunho a um chat privado, e a resposta vem depois. Uma resposta longa continua em novas mensagens.", + "answer.conversationLabel": "Conversas", + "answer.conversationHelp": "Envie /new (ou !new) no app para iniciar um novo chat.", + "answer.conversation.sender": "Um chat por pessoa", + "answer.conversation.chat": "Um chat por grupo ou canal", + "answer.conversation.window": "Por pessoa, novo após uma pausa", + "answer.conversation.thread": "Novo chat por mensagem; as respostas continuam", + "answer.window": "Pausa, em minutos", + "answer.windowRange": "Use um número inteiro de 1 a {{max}}.", + "answer.files": "Enviar os arquivos que o agente cria", + "aside.title": "Como funciona", + "aside.step1": "Dê um nome à conexão e informe o token. O token é guardado com segurança e nunca mais é mostrado.", + "aside.step2": "Escolha o agente que responde. Ele precisa de um modelo.", + "aside.step3": "Escolha quem pode escrever para ele e como ele responde.", + "aside.testTitle": "Testar a conexão", + "aside.testText": "Ao salvar um novo token, o LocalAI o verifica e indica a conta a que ele pertence. Use Testar em uma linha salva para verificar de novo.", + "add": "Adicionar", + "more": "Mais", + "less": "Menos", + "answer.filesHelp": "Depois da resposta, até 5 arquivos. Programas e scripts não são enviados. O limite de tamanho está em Arquivos.", + "answer.summary": "Responde: {{reply}} · {{conversation}}", + "answer.short.off": "digitando", + "answer.short.progress": "progresso", + "answer.short.stream": "resposta ao vivo", + "answer.short.sender": "um chat por pessoa", + "answer.short.chat": "um chat por grupo", + "answer.short.window": "novo chat após {{count}} min", + "answer.short.thread": "um chat por mensagem", + "approval.title": "Timers e gatilhos a partir do chat", + "approval.help": "Quando o agente configura algo que roda depois (\"em 10 minutos\", \"todo dia\"), cada execução é uma execução do modelo.", + "approval.mode.ask": "Perguntar a mim: o bot propõe no chat e a pessoa responde sim ou não", + "approval.mode.auto": "Configurar na hora e dizer à pessoa como cancelar", + "approval.mode.auto_readonly": "Na hora quando as execuções só leem e relatam; senão, perguntar", + "approval.limitsHelp": "Estes limites definem o que é configurado sem perguntar. Deixe um campo vazio para usar o padrão. O que passar de um limite é perguntado.", + "approval.max_pending": "Máximo de timers e gatilhos ao mesmo tempo", + "approval.min_gap_minutes": "Mínimo de minutos entre execuções de um gatilho recorrente", + "approval.max_runs_per_day": "Máximo de execuções por dia, no total", + "approval.max_lifetime_days": "Duração máxima, em dias", + "approval.invalid": "Use um número inteiro de 1 a {{max}}, ou deixe vazio.", + "approval.default": "Padrão: {{value}}.", + "approval.summary": "Gatilhos: {{mode}}", + "approval.effect": "Propostas: {{mode}}", + "approval.limits": "Limites", + "approval.acts.title": "Execuções podem agir", + "approval.acts.help": "Desligado: as execuções de um gatilho criado neste chat só leem e relatam. Ligado: elas também podem fazer o que você marcar abaixo, e só o que o agente tem.", + "approval.acts.modes": "Perguntar a mim: você sempre decide. Na hora: só quando você está pareado como remetente e todas as ações estão marcadas aqui.", + "approval.acts.act.files": "Escrever arquivos nas pastas do agente", + "approval.acts.act.files.effect": "Uma execução pode criar e alterar arquivos que o agente guarda.", + "approval.acts.act.addons": "Usar as ações dos complementos do agente", + "approval.acts.act.addons.effect": "Uma execução pode usar o que os complementos do agente fazem, inclusive comandos.", + "approval.acts.act.send": "Enviar mensagens além de onde relata", + "approval.acts.act.send.effect": "Uma execução pode escrever em outros chats e canais por conta própria.", + "approval.acts.act.spend": "Usar ferramentas pagas ou externas e a rede", + "approval.acts.act.spend.effect": "Uma execução pode chamar serviços fora do LocalAI, o que pode custar dinheiro.", + "approval.acts.lacks": "O agente não tem isto.", + "approval.acts.sensitive": "Sensível", + "approval.effectActs": ", as execuções podem: {{acts}}", + "approval.actShort.act.files": "escrever arquivos", + "approval.actShort.act.addons": "usar complementos", + "approval.actShort.act.send": "enviar mensagens", + "approval.actShort.act.spend": "usar ferramentas pagas ou externas", + "approval.short.ask": "pergunta antes", + "approval.short.auto": "configura na hora", + "approval.short.auto_readonly": "na hora se for só leitura", + "files.title": "Arquivos", + "files.help": "Deixe um campo vazio para usar o limite do complemento. O seu limite só pode ser menor que esse.", + "files.receiveLabel": "Maior arquivo que lê, em MiB", + "files.sendLabel": "Maior arquivo que envia, em MiB", + "files.invalid": "Use um número maior que 0.", + "files.tooHigh": "O complemento permite no máximo {{max}} aqui.", + "files.now": "Agora: {{limit}} ({{layer}}).", + "files.receiveHelp": "Arquivos que as pessoas enviam ao bot. Um arquivo maior é recusado, e o bot diz qual limite atingiu.", + "files.sendHelp": "Arquivos que o agente cria e o bot envia de volta. Um arquivo maior não é enviado, e o bot diz qual limite atingiu.", + "files.addon.off": "Este complemento não move arquivos nesta direção, então não há nada a configurar.", + "files.addon.declared": "O complemento declara até {{max}}.", + "files.addon.open": "O complemento não declara limite, então vale o limite da plataforma.", + "files.host": "Nenhum bot pode mover um arquivo acima de {{max}}. Um administrador define isso nas configurações.", + "files.layer.owner": "limite definido para este bot", + "files.layer.addon": "limite do complemento", + "files.layer.host": "limite da plataforma", + "perm.title": "O que os remetentes podem fazer", + "perm.help": "Escolha o que as pessoas que escrevem para este bot podem pedir ao agente. O que estiver desligado fica desligado para todos, seja qual for o papel ou o pareamento.", + "perm.noCapsTitle": "Nenhuma permissão está ligada, então estes recursos estão desligados", + "perm.noCapsText": "Este bot foi salvo antes de existirem permissões, ou nenhuma permissão foi escolhida ainda. Enquanto você não ligar alguma, os remetentes não podem enviar arquivos ao agente, configurar gatilhos, salvar na memória nem pedir que o agente publique em um canal. As mensagens continuam sendo respondidas.", + "perm.usual": "Permitir o básico", + "perm.usualHelp": "Liga as permissões do dia a dia que ficam dentro do LocalAI: {{names}}. As sensíveis nunca entram.", + "perm.usualDone": "As permissões básicas estão ligadas. Salve para mantê-las.", + "perm.usualSaved": "As permissões básicas foram salvas para {{name}}.", + "perm.usualSet": "Permitir o conjunto básico", + "perm.usualSetHelp": "Marca as permissões do dia a dia que ficam dentro do LocalAI, conforme o que o agente tem. As sensíveis nunca entram.", + "perm.sameAs": "Igual ao agente", + "perm.sameAsHelp": "Copia as permissões que o agente tem agora, inclusive as sensíveis. É uma cópia de hoje e não acompanha mudanças posteriores no agente.", + "perm.block.noAgent": "Escolha um agente primeiro.", + "perm.block.unreadable": "Não foi possível ler as permissões deste agente.", + "perm.block.nothing": "Este agente não tem nenhuma permissão que uma conexão possa usar.", + "perm.usualTicked": "{{count}} marcadas: {{names}}. Salve para mantê-las.", + "perm.usualNone": "Nada novo a marcar: as permissões básicas que o agente tem já estão ligadas.", + "perm.usualSkipped": "Não marcadas, porque o agente não as tem: {{names}}.", + "perm.sameTicked": "{{count}} permissões de {{agent}} copiadas: {{names}}. É uma cópia de hoje e não acompanha mudanças posteriores no agente. Salve para mantê-las.", + "perm.agentLacks": "O agente não tem isto.", + "perm.confirmTitle": "Copiar também as permissões sensíveis?", + "perm.confirmText": "O agente tem estas permissões sensíveis. Depois de ligadas, um remetente que você pareou poderá usá-las por este bot:", + "perm.confirmYes": "Copiar também", + "perm.confirmNo": "Cancelar", + "perm.rule": "As pessoas da lista de permitidos podem usar as permissões marcadas. As sensíveis também exigem que a pessoa esteja pareada.", + "perm.stuckTitle": "Há permissões sensíveis marcadas, mas ninguém está pareado", + "perm.stuckText": "Uma permissão sensível só funciona para uma pessoa que você pareou com este bot. Enquanto você não parear alguém, nenhum remetente pode usar: {{names}}.", + "perm.channelOnlyTitle": "A lista de permitidos só tem canais", + "perm.channelOnlyText": "Os membros de um canal não recebem confiança um a um, então não podem usar as permissões marcadas. Adicione o usuário deles às pessoas permitidas.", + "perm.stuckPair": "Parear sua conta", + "perm.sensitive": "Só remetentes pareados", + "perm.sensitiveHelp": "Sensível. Nunca é dada a um papel. Só um remetente que você pareou pode usá-la, e todo uso é registrado.", + "perm.capsLegend": "Permissões deste bot", + "perm.capsHelp": "O teto para todos. Um papel pode receber menos, nunca mais.", + "perm.rolesTitle": "Papéis", + "perm.rolesHelp": "A plataforma informa ao host quais papéis um remetente tem. Escolha o que cada papel pode fazer. Permissões sensíveis nunca são dadas a um papel, e um papel só recebe uma permissão que esteja ligada acima.", + "perm.rolesNone": "Ligue acima uma permissão que este complemento possa dar a um papel, e ela aparece aqui.", + "perm.rolesGrid": "O que cada papel pode fazer", + "perm.roleCap": "{{role}}: {{cap}}", + "perm.channelsTitle": "Canais aprovados", + "perm.channelsHelp": "Chats onde o agente pode publicar quando um remetente pede, além do chat em que responde. Um canal não aprovado exige um remetente pareado a cada mensagem. No máximo {{max}}.", + "perm.channelsOff": "Ligue \"Publicar nos canais aprovados\" acima para escolher canais.", + "perm.channelId": "ID do canal", + "perm.channelIdHelp": "O ID que a plataforma dá ao chat. Sem espaços.", + "perm.channelLabel": "Nome", + "perm.channelLabelHelp": "Um nome para você e o agente. Opcional.", + "perm.channelApproved": "Aprovado", + "perm.channelApprovedHelp": "Ligado: o agente pode publicar aqui a pedido de qualquer remetente permitido.", + "perm.channelAdd": "Adicionar um canal", + "perm.channelRemove": "Remover o canal {{id}}", + "perm.remove": "Remover", + "perm.channelFull": "A lista está cheia.", + "perm.channelProblem.id": "Cada canal precisa de um ID de 1 a {{max}} bytes, sem espaços.", + "perm.channelProblem.label": "Um nome tem no máximo {{max}} caracteres.", + "perm.channelProblem.repeat": "Um canal aparece duas vezes na lista.", + "perm.channelProblem.count": "No máximo {{max}} canais.", + "perm.triggersNeed": "Ligue Propor gatilhos para usar isto.", + "pair.title": "Remetentes pareados", + "pair.help": "Um remetente pareado provou que controla uma conta permitida. Só um remetente pareado pode usar as permissões sensíveis. No máximo {{max}}; um pareamento termina após {{days}} dias.", + "pair.start": "Parear um remetente", + "pair.startHelp": "Gera um código. Envie-o ao bot em mensagem direta, da conta a parear, em até dez minutos. O código vale uma vez.", + "pair.needSave": "Salve este bot primeiro, depois pareie um remetente.", + "pair.codeTitle": "Seu código de pareamento", + "pair.codeHelp": "Envie este código ao bot em mensagem direta, da conta a parear. Ele só aparece agora, vale uma vez e expira às {{time}}.", + "pair.codeGone": "O código expirou. Gere um novo se o remetente ainda não estiver na lista.", + "pair.done": "Pareado: {{sender}}.", + "pair.dismiss": "Ocultar o código", + "pair.none": "Ninguém está pareado.", + "pair.expires": "termina {{time}}", + "pair.remove": "Remover", + "pair.removeSender": "Remover o pareamento de {{sender}}", + "pair.full": "Três remetentes estão pareados. Remova um para parear outro.", + "access.title": "Últimas decisões", + "access.needSave": "Salve este bot primeiro para ver as últimas decisões dele.", + "access.refresh": "Atualizar", + "access.recentTitle": "Últimas decisões", + "access.recentHelp": "As últimas verificações deste servidor, da mais recente para a mais antiga. Elas contêm IDs e nomes de permissões, nunca o texto das mensagens. São zeradas quando o servidor reinicia.", + "access.recentNone": "Nenhuma decisão ainda.", + "access.allowed": "permitido", + "access.denied": "negado", + "access.who": "remetente {{senders}}", + "access.roles": "papéis {{roles}}", + "access.via": "via {{via}}", + "can.title": "O que este bot pode fazer", + "can.help": "A verificação que o host faz a cada mensagem, executada neste formulário como está agora. Ela não altera nada. Uma linha bloqueada mostra o primeiro problema; pode haver outros depois dele.", + "can.sender": "Se o remetente for", + "can.sender.listed": "Uma pessoa da lista de permitidos", + "can.sender.channel_member": "Um membro de um canal permitido", + "can.sender.anyone": "Qualquer pessoa que escreva para o bot", + "can.sender.paired": "Uma pessoa que você pareou", + "can.sender.role": "Papel: {{title}}", + "can.fromForm": "Calculado a partir do formulário como está agora. Salve para manter estas configurações.", + "can.fromFormNew": "Ainda não salvo. Calculado a partir do formulário como está agora.", + "can.loading": "Verificando o que este bot pode fazer", + "can.stale": "Atualizando", + "can.errorTitle": "Não foi possível verificar o que este bot pode fazer", + "can.retry": "Tentar de novo", + "can.allowed": "Permitido", + "can.blocked": "Bloqueado", + "can.summary": "{{allowed}} de {{total}} permitidas para este remetente.", + "can.via.paired": "porque esta pessoa está pareada", + "can.via.allowlist": "porque esta pessoa está na lista de permitidos", + "can.via.role": "pelo papel {{role}}", + "can.noPerson": "Nenhuma pessoa está na lista de permitidos ainda. Isto é o que uma pessoa da lista teria.", + "can.then": "Ainda não verificado: {{list}}.", + "can.then.server": "as configurações do servidor", + "can.then.tools": "a chave Ferramentas do agente", + "can.then.agent": "as permissões do agente", + "can.then.connection": "as permissões desta conexão", + "can.then.sender": "quem está enviando", + "can.then.approval": "como as propostas são aprovadas", + "can.noMessage": "Não permitido.", + "fix.open_connection": "Mostrar a configuração", + "fix.open_agent": "Abrir o agente", + "fix.open_server_setting": "Abrir Configurações", + "fix.pair_sender": "Parear um remetente", + "fix.add_person": "Adicionar uma pessoa", + "fix.save": "Manter estas alterações", + "fix.token": "Colar o token", + "fix.tokenNew": "Colar um novo token", + "fix.agent": "Escolher um agente", + "fix.usual": "Escolher permissões", + "fix.turnOn": "Ligar", + "fix.newTab": "(abre em uma nova aba)", + "fix.askAdmin": "Pedir a um administrador", + "ready.title": "Prontidão", + "ready.help": "O que precisa estar no lugar para este bot funcionar, com o formulário como está agora.", + "ready.checkingSummary": "Verificando o que o formulário fará.", + "ready.allGood": "Tudo o que é necessário está no lugar.", + "ready.problems": "{{count}} para ver.", + "ready.showAll": "Mostrar todos os {{count}}", + "ready.showProblems": "Mostrar só o que precisa de atenção", + "ready.checking": "Verificando.", + "ready.unchecked": "Não foi possível verificar isto. Veja \"O que este bot pode fazer\" abaixo.", + "ready.row.token": "Token", + "ready.row.agent": "Agente", + "ready.row.who": "Quem pode usar", + "ready.row.permissions": "Permissões", + "ready.row.triggers": "Gatilhos", + "ready.row.files": "Arquivos", + "ready.row.notify": "Notificações", + "ready.state.ok": "OK", + "ready.state.attention": "Precisa de atenção", + "ready.state.off": "Desligado", + "ready.state.after_save": "Depois de salvar", + "ready.state.broken": "Não funciona", + "ready.state.checking": "Verificando", + "ready.state.unknown": "Desconhecido", + "ready.token.missing": "Cole o token do bot. Sem ele nada é recebido.", + "ready.token.newTyped": "O token é verificado com a plataforma quando você salva.", + "ready.token.typed": "O novo token é verificado com a plataforma quando você salva.", + "ready.token.failed": "A plataforma recusou o token: {{reason}}", + "ready.token.checked": "A plataforma aceitou o token. {{bot}}", + "ready.token.saved": "Há um token salvo. A plataforma o aceitou quando foi salvo.", + "ready.agent.none": "Escolha o agente que responde.", + "ready.agent.credentialsOnly": "Sem agente. Esta conexão só guarda as credenciais e não responde a nenhuma mensagem.", + "ready.agent.gone": "O agente escolhido não está mais na sua lista. Escolha outro.", + "ready.agent.noModel": "{{name}} não tem modelo, então não pode responder. Dê um a ele.", + "ready.agent.ok": "{{name}} responde, com o modelo {{model}}.", + "ready.agent.okUnknown": "Um agente está escolhido.", + "ready.who.anyone": "Qualquer pessoa que possa enviar mensagem ao bot pode usá-lo.", + "ready.who.nobody": "Ninguém está permitido ainda, então o bot ignora todas as mensagens. Adicione uma pessoa.", + "ready.who.channelsOnly": "Só há canais na lista. As pessoas de um canal podem conversar, mas não podem usar nenhuma permissão até você adicioná-las como pessoas.", + "ready.who.listed_one": "{{count}} pessoa ou chat permitido.", + "ready.who.listed_other": "{{count}} pessoas ou chats permitidos.", + "ready.who.mixed": "{{count}} entradas permitidas, pessoas e canais.", + "ready.perm.none": "Nenhuma permissão está ligada. Os remetentes podem conversar, e nada mais.", + "ready.perm.noneSaved": "Nenhuma permissão está ligada, então arquivos, memória, gatilhos e publicações estão desligados.", + "ready.perm.onlyOwnRows": "As outras permissões estão desligadas. Gatilhos e arquivos estão abaixo.", + "ready.perm.ok_one": "{{count}} permissão está ligada e um remetente pode usá-la.", + "ready.perm.ok_other": "{{count}} permissões estão ligadas e um remetente pode usá-las.", + "ready.perm.dead_one": "Ninguém pode usar {{names}}. {{message}}", + "ready.perm.dead_other": "Ninguém pode usar {{count}} das permissões ligadas: {{names}}. Primeiro: {{message}}", + "ready.triggers.off": "O agente não pode configurar lembretes nem tarefas agendadas aqui.", + "ready.triggers.blocked": "Os gatilhos estão ligados, mas não podem ser usados. {{message}}", + "ready.triggers.ok.ask": "O agente pode propor gatilhos. Você confirma cada um antes de começar.", + "ready.triggers.ok.auto": "O agente pode propor gatilhos. Eles começam na hora, sem perguntar a você.", + "ready.triggers.ok.auto_readonly": "O agente pode propor gatilhos. Um gatilho que só lê começa na hora; você confirma os outros.", + "ready.files.offReceive": "Os remetentes não podem enviar arquivos ao agente.", + "ready.files.offBoth": "Os remetentes não podem enviar arquivos ao agente, e o bot não envia nenhum de volta.", + "ready.files.blocked": "O recebimento de arquivos está ligado, mas não pode ser usado. {{message}}", + "ready.files.ok": "Os remetentes podem enviar arquivos ao agente, e o bot pode enviar arquivos de volta.", + "ready.files.okReceive": "Os remetentes podem enviar arquivos ao agente. O bot não envia nenhum de volta.", + "ready.notify.off": "Nenhum chat recebe avisos deste bot.", + "ready.notify.ok": "Os avisos vão para o chat {{chat}}.", + "dismiss": "Descartar", + "credentialsOnly": "Só credenciais", + "agentCredentialsOnly": "Sem agente (só credenciais)", + "agentOptionalHint": "Opcional. Sem um agente, esta conexão só guarda o token, a lista de repositórios e o modo de escrita dos seus chats. Ela não responde a mensagens. Escolha um agente para ela passar a escutar.", + "sender.line_one": "{{name}} · {{count}} mensagem · {{id}}", + "sender.line_other": "{{name}} · {{count}} mensagens · {{id}}", + "ready.blocker.nobodyLetIn": "Ninguém foi liberado ainda.", + "ready.blocker.nobodyPaired": "Isto exige uma pessoa pareada com o bot, e ninguém está pareado ainda.", + "optional": "opcional" +} diff --git a/addons/internal/connector-ui/src/i18n/connector.zh-CN.json b/addons/internal/connector-ui/src/i18n/connector.zh-CN.json new file mode 100644 index 0000000000..c9723abccc --- /dev/null +++ b/addons/internal/connector-ui/src/i18n/connector.zh-CN.json @@ -0,0 +1,281 @@ +{ + "answer.title": "回复方式", + "answer.replyLabel": "智能体工作时", + "answer.reply.off": "显示正在输入,然后给出回答", + "answer.reply.progress": "进度:它用到的工具", + "answer.reply.stream": "实时:边写边显示回答", + "answer.replyHelp": "“进度”和“实时”会显示一条处理中的消息,回答出来后替换它。在支持草稿的平台上,“实时”会在私聊中显示草稿,回答随后跟上。较长的回答会继续发成新消息。", + "answer.conversationLabel": "对话", + "answer.conversationHelp": "在应用中发送 /new(或 !new)即可开始新的对话。", + "answer.conversation.sender": "每人一个对话", + "answer.conversation.chat": "每个群组或频道一个对话", + "answer.conversation.window": "每人一个,安静一段时间后开新对话", + "answer.conversation.thread": "每条消息一个新对话,回复接着聊", + "answer.window": "安静时间(分钟)", + "answer.windowRange": "请输入 1 到 {{max}} 的整数。", + "answer.files": "发送智能体生成的文件", + "aside.title": "工作原理", + "aside.step1": "给连接起个名字,并填写它的 token。token 会被加密保存,之后不再显示。", + "aside.step2": "选择负责回答的智能体。它需要有模型。", + "aside.step3": "选择谁可以向它发消息,以及它如何回答。", + "aside.testTitle": "测试连接", + "aside.testText": "保存新的 token 时,LocalAI 会检查它,并给出它所属的账户。对已保存的行点击“测试”可以再次检查。", + "add": "添加", + "more": "更多", + "less": "收起", + "answer.filesHelp": "回答之后最多发送 5 个文件。程序和脚本不会发送。大小上限在“文件”中设置。", + "answer.summary": "回复:{{reply}} · {{conversation}}", + "answer.short.off": "正在输入", + "answer.short.progress": "进度", + "answer.short.stream": "实时回答", + "answer.short.sender": "每人一个对话", + "answer.short.chat": "每个群组一个对话", + "answer.short.window": "{{count}} 分钟后开新对话", + "answer.short.thread": "每条消息一个对话", + "approval.title": "来自聊天的定时任务和触发器", + "approval.help": "当智能体设置了稍后运行的内容(“10 分钟后”“每天”)时,每次运行都会调用一次模型。", + "approval.mode.ask": "先问我:机器人在聊天中提出建议,由对方回答是或否", + "approval.mode.auto": "立即设置,并告诉对方如何取消", + "approval.mode.auto_readonly": "运行只读取和汇报时立即设置,否则先询问", + "approval.limitsHelp": "这些限制决定了无需询问即可设置的范围。字段留空则使用默认值。超出限制的一律改为询问。", + "approval.max_pending": "同时存在的定时任务和触发器上限", + "approval.min_gap_minutes": "循环触发器两次运行之间的最少分钟数", + "approval.max_runs_per_day": "每天的运行总次数上限", + "approval.max_lifetime_days": "最长存续天数", + "approval.invalid": "请输入 1 到 {{max}} 的整数,或留空。", + "approval.default": "默认值:{{value}}。", + "approval.summary": "触发器:{{mode}}", + "approval.effect": "建议:{{mode}}", + "approval.limits": "限制", + "approval.acts.title": "运行可执行的操作", + "approval.acts.help": "关闭:由此聊天创建的触发器,其运行只读取和汇报。开启:运行还可以执行你在下面勾选的操作,且仅限智能体具备的操作。", + "approval.acts.modes": "先问我:始终由你决定。立即设置:仅当你已配对为发送者,且这里勾选了所有操作时。", + "approval.acts.act.files": "在智能体的文件夹中写入文件", + "approval.acts.act.files.effect": "运行可以创建和修改智能体保存的文件。", + "approval.acts.act.addons": "使用智能体附加组件的操作", + "approval.acts.act.addons.effect": "运行可以使用智能体的附加组件所能做的事,包括命令。", + "approval.acts.act.send": "向汇报位置之外发送消息", + "approval.acts.act.send.effect": "运行可以自行向其他聊天和频道写入内容。", + "approval.acts.act.spend": "使用付费或外部工具以及网络", + "approval.acts.act.spend.effect": "运行可以调用 LocalAI 之外的服务,这可能产生费用。", + "approval.acts.lacks": "智能体不具备此项。", + "approval.acts.sensitive": "敏感", + "approval.effectActs": ",运行可以:{{acts}}", + "approval.actShort.act.files": "写入文件", + "approval.actShort.act.addons": "使用附加组件", + "approval.actShort.act.send": "发送消息", + "approval.actShort.act.spend": "使用付费或外部工具", + "approval.short.ask": "先询问", + "approval.short.auto": "立即设置", + "approval.short.auto_readonly": "只读时立即设置", + "files.title": "文件", + "files.help": "字段留空则使用附加组件的上限。你设置的上限只能比它更低。", + "files.receiveLabel": "可读取的最大文件(MiB)", + "files.sendLabel": "可发送的最大文件(MiB)", + "files.invalid": "请输入大于 0 的数字。", + "files.tooHigh": "附加组件在这里最多允许 {{max}}。", + "files.now": "当前:{{limit}}({{layer}})。", + "files.receiveHelp": "用户发给机器人的文件。过大的文件会被拒绝,机器人会说明触及了哪个上限。", + "files.sendHelp": "智能体生成并由机器人回传的文件。过大的文件不会发送,机器人会说明触及了哪个上限。", + "files.addon.off": "此附加组件不支持这个方向的文件传输,因此无需设置。", + "files.addon.declared": "附加组件声明的上限为 {{max}}。", + "files.addon.open": "附加组件未声明上限,因此适用平台的上限。", + "files.host": "任何机器人都不能传输超过 {{max}} 的文件。管理员可在设置中调整。", + "files.layer.owner": "为此机器人设置的上限", + "files.layer.addon": "附加组件的上限", + "files.layer.host": "平台的上限", + "perm.title": "发送者可以做什么", + "perm.help": "选择向此机器人发消息的人可以请求智能体做什么。关闭的权限对所有人都保持关闭,无论角色或配对如何设置。", + "perm.noCapsTitle": "没有开启任何权限,因此这些功能处于关闭状态", + "perm.noCapsText": "此机器人保存于权限功能出现之前,或尚未选择任何权限。在你开启一些权限之前,发送者无法向智能体发送文件、设置触发器、保存到记忆,也无法让智能体向频道发消息。消息仍会得到回复。", + "perm.usual": "允许常用权限", + "perm.usualHelp": "开启仅限 LocalAI 内部的日常权限:{{names}}。敏感权限永远不包含在内。", + "perm.usualDone": "常用权限已开启。保存后生效。", + "perm.usualSaved": "已为 {{name}} 保存常用权限。", + "perm.usualSet": "允许常用权限组", + "perm.usualSetHelp": "按智能体具备的权限,勾选仅限 LocalAI 内部的日常权限。敏感权限永远不包含在内。", + "perm.sameAs": "与智能体相同", + "perm.sameAsHelp": "复制智能体当前具备的权限,包括敏感权限。这只是当前状态的副本,不会跟随智能体之后的变更。", + "perm.block.noAgent": "请先选择智能体。", + "perm.block.unreadable": "无法读取此智能体的权限。", + "perm.block.nothing": "此智能体没有连接可以使用的权限。", + "perm.usualTicked": "已勾选 {{count}} 项:{{names}}。保存后生效。", + "perm.usualNone": "没有需要新勾选的内容:智能体具备的常用权限都已开启。", + "perm.usualSkipped": "未勾选,因为智能体不具备:{{names}}。", + "perm.sameTicked": "已复制 {{agent}} 的 {{count}} 项权限:{{names}}。这只是当前状态的副本,不会跟随智能体之后的变更。保存后生效。", + "perm.agentLacks": "智能体不具备此项。", + "perm.confirmTitle": "也复制敏感权限吗?", + "perm.confirmText": "智能体具备以下敏感权限。一旦开启,你配对过的发送者就可以通过此机器人使用它们:", + "perm.confirmYes": "也复制", + "perm.confirmNo": "取消", + "perm.rule": "允许名单中的人可以使用已勾选的权限。敏感权限还要求该用户已配对。", + "perm.stuckTitle": "已勾选敏感权限,但没有人配对", + "perm.stuckText": "敏感权限只对与此机器人配对过的人有效。在你配对某人之前,没有发送者可以使用:{{names}}。", + "perm.channelOnlyTitle": "允许名单中只有频道", + "perm.channelOnlyText": "频道成员不会被逐个信任,因此无法使用已勾选的权限。请把他们的用户添加到允许的人员中。", + "perm.stuckPair": "配对你的账户", + "perm.sensitive": "仅限已配对的发送者", + "perm.sensitiveHelp": "敏感。绝不授予角色。只有你配对过的发送者可以使用,每次使用都会被记录。", + "perm.capsLegend": "此机器人的权限", + "perm.capsHelp": "对所有人的上限。角色可以获得更少,但不能更多。", + "perm.rolesTitle": "角色", + "perm.rolesHelp": "平台会告诉主机发送者拥有哪些角色。请选择每个角色可以做什么。敏感权限绝不授予角色,角色也只能获得上方已开启的权限。", + "perm.rolesNone": "请在上方开启此附加组件可授予角色的权限,它就会显示在这里。", + "perm.rolesGrid": "每个角色可以做什么", + "perm.roleCap": "{{role}}:{{cap}}", + "perm.channelsTitle": "已批准的频道", + "perm.channelsHelp": "除了回答所在的聊天,智能体在发送者请求时还可以发消息的聊天。未批准的频道,每条消息都需要已配对的发送者。最多 {{max}} 个。", + "perm.channelsOff": "请先在上方开启“向已批准的频道发消息”,再选择频道。", + "perm.channelId": "频道 ID", + "perm.channelIdHelp": "平台给该聊天的 ID。不含空格。", + "perm.channelLabel": "名称", + "perm.channelLabelHelp": "给你和智能体看的名称。可选。", + "perm.channelApproved": "已批准", + "perm.channelApprovedHelp": "开启:任何被允许的发送者请求时,智能体都可以在此发消息。", + "perm.channelAdd": "添加频道", + "perm.channelRemove": "移除频道 {{id}}", + "perm.remove": "移除", + "perm.channelFull": "列表已满。", + "perm.channelProblem.id": "每个频道需要 1 到 {{max}} 字节的 ID,且不含空格。", + "perm.channelProblem.label": "名称最多 {{max}} 个字符。", + "perm.channelProblem.repeat": "有频道重复列出。", + "perm.channelProblem.count": "最多 {{max}} 个频道。", + "perm.triggersNeed": "请先开启“提议触发器”再使用。", + "pair.title": "已配对的发送者", + "pair.help": "已配对的发送者证明了自己控制着某个被允许的账户。只有已配对的发送者才能使用敏感权限。最多 {{max}} 个;配对在 {{days}} 天后结束。", + "pair.start": "配对发送者", + "pair.startHelp": "生成一个代码。请在十分钟内,用要配对的账户把它私信发给机器人。代码只能使用一次。", + "pair.needSave": "请先保存此机器人,再配对发送者。", + "pair.codeTitle": "你的配对码", + "pair.codeHelp": "请用要配对的账户把此代码私信发给机器人。它只显示这一次,只能使用一次,并在 {{time}} 过期。", + "pair.codeGone": "代码已过期。如果发送者尚未列出,请重新生成。", + "pair.done": "已配对:{{sender}}。", + "pair.dismiss": "隐藏代码", + "pair.none": "没有人配对。", + "pair.expires": "{{time}} 结束", + "pair.remove": "移除", + "pair.removeSender": "移除 {{sender}} 的配对", + "pair.full": "已配对三个发送者。请先移除一个,再配对其他人。", + "access.title": "最近的决定", + "access.needSave": "请先保存此机器人,才能查看它最近的决定。", + "access.refresh": "刷新", + "access.recentTitle": "最近的决定", + "access.recentHelp": "此服务器最近的检查,最新的在前。其中只有 ID 和权限名称,没有消息内容。服务器重启后会清空。", + "access.recentNone": "暂无决定。", + "access.allowed": "已允许", + "access.denied": "已拒绝", + "access.who": "发送者 {{senders}}", + "access.roles": "角色 {{roles}}", + "access.via": "经由 {{via}}", + "can.title": "此机器人可以做什么", + "can.help": "主机对每条消息执行的检查,按此表单当前的内容运行。不会更改任何内容。被阻止的行只显示第一个问题,后面可能还有其他问题。", + "can.sender": "如果发送者是", + "can.sender.listed": "允许名单中的人", + "can.sender.channel_member": "允许的频道的成员", + "can.sender.anyone": "任何向机器人发消息的人", + "can.sender.paired": "你配对过的人", + "can.sender.role": "角色:{{title}}", + "can.fromForm": "按此表单当前的内容计算。保存后这些设置才会生效。", + "can.fromFormNew": "尚未保存。按此表单当前的内容计算。", + "can.loading": "正在检查此机器人可以做什么", + "can.stale": "正在更新", + "can.errorTitle": "无法检查此机器人可以做什么", + "can.retry": "重试", + "can.allowed": "允许", + "can.blocked": "已阻止", + "can.summary": "对此发送者,{{total}} 项中允许 {{allowed}} 项。", + "can.via.paired": "因为此人已配对", + "can.via.allowlist": "因为此人在允许名单中", + "can.via.role": "通过角色 {{role}}", + "can.noPerson": "允许名单中还没有人。以下是名单中的人会得到的结果。", + "can.then": "尚未检查:{{list}}。", + "can.then.server": "服务器设置", + "can.then.tools": "智能体的“工具”开关", + "can.then.agent": "智能体的权限", + "can.then.connection": "此连接的权限", + "can.then.sender": "发送者是谁", + "can.then.approval": "建议如何被确认", + "can.noMessage": "不允许。", + "fix.open_connection": "显示该设置", + "fix.open_agent": "打开智能体", + "fix.open_server_setting": "打开设置", + "fix.pair_sender": "配对发送者", + "fix.add_person": "添加人员", + "fix.save": "保留这些更改", + "fix.token": "粘贴 token", + "fix.tokenNew": "粘贴新的 token", + "fix.agent": "选择智能体", + "fix.usual": "选择权限", + "fix.turnOn": "开启", + "fix.newTab": "(在新标签页中打开)", + "fix.askAdmin": "请询问管理员", + "ready.title": "就绪状态", + "ready.help": "按表单当前的内容,此机器人要正常工作需要具备的条件。", + "ready.checkingSummary": "正在检查表单将产生的效果。", + "ready.allGood": "所需条件均已具备。", + "ready.problems": "{{count}} 项需要处理。", + "ready.showAll": "显示全部 {{count}} 项", + "ready.showProblems": "只显示需要处理的", + "ready.checking": "正在检查。", + "ready.unchecked": "无法检查此项。请查看下方的“此机器人可以做什么”。", + "ready.row.token": "Token", + "ready.row.agent": "智能体", + "ready.row.who": "谁可以使用", + "ready.row.permissions": "权限", + "ready.row.triggers": "触发器", + "ready.row.files": "文件", + "ready.row.notify": "通知", + "ready.state.ok": "正常", + "ready.state.attention": "需要处理", + "ready.state.off": "关闭", + "ready.state.after_save": "保存后", + "ready.state.broken": "无法工作", + "ready.state.checking": "检查中", + "ready.state.unknown": "未知", + "ready.token.missing": "请粘贴机器人的 token。没有它就收不到任何消息。", + "ready.token.newTyped": "保存时会向平台校验该 token。", + "ready.token.typed": "保存时会向平台校验新的 token。", + "ready.token.failed": "平台拒绝了该 token:{{reason}}", + "ready.token.checked": "平台已接受该 token。{{bot}}", + "ready.token.saved": "已保存 token。保存时平台已接受它。", + "ready.agent.none": "请选择负责回答的智能体。", + "ready.agent.credentialsOnly": "没有智能体。此连接只保存凭据,不回答任何消息。", + "ready.agent.gone": "所选智能体已不在你的列表中。请另选一个。", + "ready.agent.noModel": "{{name}} 没有模型,无法回答。请为它指定一个。", + "ready.agent.ok": "{{name}} 负责回答,使用模型 {{model}}。", + "ready.agent.okUnknown": "已选择智能体。", + "ready.who.anyone": "任何能给机器人发消息的人都可以使用。", + "ready.who.nobody": "目前没有人被允许,因此机器人会忽略所有消息。请添加人员。", + "ready.who.channelsOnly": "名单中只有频道。频道中的人可以聊天,但在你把他们添加为人员之前,不能使用任何权限。", + "ready.who.listed_one": "已允许 {{count}} 个人或聊天。", + "ready.who.listed_other": "已允许 {{count}} 个人或聊天。", + "ready.who.mixed": "已允许 {{count}} 个条目,包括人员和频道。", + "ready.perm.none": "没有开启任何权限。发送者只能聊天,不能做其他事。", + "ready.perm.noneSaved": "没有开启任何权限,因此文件、记忆、触发器和发帖都处于关闭状态。", + "ready.perm.onlyOwnRows": "其他权限已关闭。触发器和文件见下方。", + "ready.perm.ok_one": "已开启 {{count}} 项权限,发送者可以使用。", + "ready.perm.ok_other": "已开启 {{count}} 项权限,发送者可以使用。", + "ready.perm.dead_one": "没有人可以使用 {{names}}。{{message}}", + "ready.perm.dead_other": "已开启的权限中,有 {{count}} 项没有人可以使用:{{names}}。首先:{{message}}", + "ready.triggers.off": "智能体无法在这里设置提醒或定时任务。", + "ready.triggers.blocked": "触发器已开启,但无法使用。{{message}}", + "ready.triggers.ok.ask": "智能体可以提议触发器。每个触发器启动前都由你确认。", + "ready.triggers.ok.auto": "智能体可以提议触发器。它们会立即启动,不会询问你。", + "ready.triggers.ok.auto_readonly": "智能体可以提议触发器。只读取的触发器立即启动,其他的需要你确认。", + "ready.files.offReceive": "发送者无法向智能体发送文件。", + "ready.files.offBoth": "发送者无法向智能体发送文件,机器人也不会回传文件。", + "ready.files.blocked": "接收文件已开启,但无法使用。{{message}}", + "ready.files.ok": "发送者可以向智能体发送文件,机器人也可以回传文件。", + "ready.files.okReceive": "发送者可以向智能体发送文件。机器人不回传文件。", + "ready.notify.off": "没有聊天会收到此机器人的通知。", + "ready.notify.ok": "通知会发送到聊天 {{chat}}。", + "dismiss": "忽略", + "credentialsOnly": "仅凭据", + "agentCredentialsOnly": "没有智能体(仅凭据)", + "agentOptionalHint": "可选。没有智能体时,此连接只保存 token、仓库列表和用于你的聊天的写入模式,不回答任何消息。选择智能体后,它才会开始监听。", + "sender.line_one": "{{name}} · {{count}} 条消息 · {{id}}", + "sender.line_other": "{{name}} · {{count}} 条消息 · {{id}}", + "ready.blocker.nobodyLetIn": "目前没有人被放行。", + "ready.blocker.nobodyPaired": "这需要一个与机器人配对过的人,而目前没有人配对。", + "optional": "可选" +} diff --git a/addons/internal/connector-ui/src/i18n/index.js b/addons/internal/connector-ui/src/i18n/index.js new file mode 100644 index 0000000000..f0e445d12a --- /dev/null +++ b/addons/internal/connector-ui/src/i18n/index.js @@ -0,0 +1,20 @@ +// The shared words of the connector pages, in every language of the web UI. +// English is the source and the fallback of every key; a key that a language +// lacks reads in English. addons/internal/i18ncheck checks that no +// bundle misses a key, adds one, or breaks a placeholder. +import { registerLocales } from '../../../host-compat/src/i18n.js' +import en from './connector.en.json' +import it from './connector.it.json' +import fr from './connector.fr.json' +import es from './connector.es.json' +import de from './connector.de.json' +import zh from './connector.zh-CN.json' +import id from './connector.id.json' +import ko from './connector.ko.json' +import pt from './connector.pt-BR.json' + +export const connectorLocales = { en, it, es, de, fr, 'zh-CN': zh, id, ko, 'pt-BR': pt } + +export function registerConnectorLocales() { + registerLocales({ connector: connectorLocales }) +} diff --git a/addons/internal/i18ncheck/check.mjs b/addons/internal/i18ncheck/check.mjs new file mode 100644 index 0000000000..6d70f75e2b --- /dev/null +++ b/addons/internal/i18ncheck/check.mjs @@ -0,0 +1,116 @@ +#!/usr/bin/env node +// Completeness gate of the add-on locale bundles. +// +// An add-on page reads its words from JSON bundles named ..json +// in a folder `i18n` (addons//ui/src/i18n, and the shared connector pages in +// addons/internal/connector-ui/src/i18n). English is the source. Every language +// that the web UI ships (the folders of core/http/react-ui/public/locales) must +// have a bundle for each English one, and each bundle must: +// +// - have every key of English, and no key that English lacks; +// - have the same placeholders ({{name}} and %s) as English for each key; +// - have no empty value. +// +// A key that is missing would show English text in a page of another language +// with no error anywhere. There is no baseline: a gap fails the check. +// +// node addons/internal/i18ncheck/check.mjs +import { existsSync, readFileSync, readdirSync } from 'node:fs' +import { dirname, join, relative } from 'node:path' +import { fileURLToPath } from 'node:url' + +const here = dirname(fileURLToPath(import.meta.url)) +const REPO = join(here, '..', '..', '..') +const SOURCE = 'en' +// Bundles that are copies of a locale file of the web UI. The gaps of those are +// the web UI's own, tracked by core/http/react-ui/i18n-baseline.json. +const MIRRORS = new Set(['studio']) + +// Every key path of a bundle, with its string value. Arrays count as leaves. +export function leaves(value, prefix = '', out = new Map()) { + if (value && typeof value === 'object' && !Array.isArray(value)) { + for (const [key, child] of Object.entries(value)) leaves(child, prefix ? `${prefix}.${key}` : key, out) + } else { + out.set(prefix, value) + } + return out +} + +// The placeholders of a string, sorted, with repeats: {{name}} (any spacing) and %s, %d, %v. +export function placeholders(text) { + if (typeof text !== 'string') return [] + const found = [] + for (const m of text.matchAll(/\{\{\s*-?\s*([A-Za-z0-9_.]+)\s*\}\}/g)) found.push(`{{${m[1]}}}`) + for (const m of text.matchAll(/%(?:\d+\$)?[sdvf]/g)) found.push(m[0]) + return found.sort() +} + +// The languages of the web UI. +export function languages(repo = REPO) { + const dir = join(repo, 'core/http/react-ui/public/locales') + return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort() +} + +// The i18n folders of the add-on pages and of the shared packages. +export function bundleDirs(repo = REPO) { + const addons = join(repo, 'addons') + const out = [] + for (const entry of readdirSync(addons, { withFileTypes: true })) { + if (!entry.isDirectory() || MIRRORS.has(entry.name)) continue + const candidates = entry.name === 'internal' + ? readdirSync(join(addons, 'internal'), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join(addons, 'internal', e.name, 'src', 'i18n')) + : [join(addons, entry.name, 'ui', 'src', 'i18n')] + for (const dir of candidates) if (existsSync(dir)) out.push(dir) + } + return out.sort() +} + +// Returns a list of problems, each a line of text. langs defaults to the web UI languages. +export function check(repo = REPO, langs = languages(repo)) { + const problems = [] + for (const dir of bundleDirs(repo)) { + const rel = relative(repo, dir) + const files = readdirSync(dir).filter((f) => f.endsWith('.json')) + const namespaces = [...new Set(files.map((f) => f.match(/^(.+)\.[A-Za-z-]+\.json$/)?.[1]).filter(Boolean))].sort() + for (const ns of namespaces) { + const enFile = join(dir, `${ns}.${SOURCE}.json`) + if (!existsSync(enFile)) { problems.push(`${rel}: ${ns}.${SOURCE}.json is missing`); continue } + const source = leaves(JSON.parse(readFileSync(enFile, 'utf8'))) + for (const [key, value] of source) { + if (typeof value !== 'string' || value.trim() === '') problems.push(`${rel}/${ns}.${SOURCE}.json: ${key} is not a non-empty string`) + } + for (const lang of langs.filter((l) => l !== SOURCE)) { + const name = `${ns}.${lang}.json` + const file = join(dir, name) + if (!existsSync(file)) { problems.push(`${rel}: ${name} is missing`); continue } + let bundle + try { bundle = leaves(JSON.parse(readFileSync(file, 'utf8'))) } catch (e) { problems.push(`${rel}/${name}: not valid JSON (${e.message})`); continue } + for (const [key, value] of source) { + if (!bundle.has(key)) { problems.push(`${rel}/${name}: missing key ${key}`); continue } + const got = bundle.get(key) + if (typeof got !== 'string' || got.trim() === '') { problems.push(`${rel}/${name}: empty value for ${key}`); continue } + const want = placeholders(value).join(' ') + const have = placeholders(got).join(' ') + if (want !== have) problems.push(`${rel}/${name}: placeholders of ${key} are [${have}], English has [${want}]`) + } + for (const key of bundle.keys()) if (!source.has(key)) problems.push(`${rel}/${name}: extra key ${key}`) + } + // A bundle for a language the web UI does not ship is a mistake in the name. + for (const f of files) { + const m = f.match(/^(.+)\.([A-Za-z-]+)\.json$/) + if (m && m[1] === ns && m[2] !== SOURCE && !langs.includes(m[2])) problems.push(`${rel}/${f}: ${m[2]} is not a language of the web UI`) + } + } + } + return problems +} + +if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { + const problems = check() + if (problems.length) { + console.error(`${problems.length} add-on locale problem(s):`) + for (const p of problems) console.error(` ${p}`) + process.exit(1) + } + console.log(`add-on locale bundles are complete for ${languages().join(', ')}`) +} diff --git a/addons/internal/i18ncheck/test/check.test.mjs b/addons/internal/i18ncheck/test/check.test.mjs new file mode 100644 index 0000000000..b59743ea7b --- /dev/null +++ b/addons/internal/i18ncheck/test/check.test.mjs @@ -0,0 +1,47 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { check, placeholders } from '../check.mjs' + +// A repo with two languages and one add-on bundle, written to a temporary folder. +function repoWith(bundles) { + const repo = mkdtempSync(join(tmpdir(), 'i18ncheck-')) + for (const l of ['en', 'it']) mkdirSync(join(repo, 'core/http/react-ui/public/locales', l), { recursive: true }) + const dir = join(repo, 'addons/demo/ui/src/i18n') + mkdirSync(dir, { recursive: true }) + for (const [name, data] of Object.entries(bundles)) writeFileSync(join(dir, name), JSON.stringify(data)) + return repo +} + +const EN = { title: 'Demo', hello: 'Hello {{name}}', rows_one: '{{count}} row', rows_other: '{{count}} rows', nested: { a: 'Save %s' } } +const IT = { title: 'Demo', hello: 'Ciao {{name}}', rows_one: '{{count}} riga', rows_other: '{{count}} righe', nested: { a: 'Salva %s' } } +const run = (it) => { const repo = repoWith({ 'demo.en.json': EN, ...(it ? { 'demo.it.json': it } : {}) }); try { return check(repo, ['en', 'it']) } finally { rmSync(repo, { recursive: true, force: true }) } } + +test('i18n gate: a complete bundle passes', () => { + assert.deepEqual(run(IT), []) +}) + +test('i18n gate: a missing language file, a missing key and an extra key fail', () => { + assert.match(run(null).join('\n'), /demo\.it\.json is missing/) + const { hello, ...gap } = IT + assert.match(run(gap).join('\n'), /missing key hello/) + assert.match(run({ ...IT, more: 'x' }).join('\n'), /extra key more/) +}) + +test('i18n gate: a changed or lost placeholder fails, in nested keys too', () => { + assert.match(run({ ...IT, hello: 'Ciao {{nome}}' }).join('\n'), /placeholders of hello/) + assert.match(run({ ...IT, rows_other: 'righe' }).join('\n'), /placeholders of rows_other/) + assert.match(run({ ...IT, nested: { a: 'Salva' } }).join('\n'), /placeholders of nested\.a/) +}) + +test('i18n gate: an empty value fails, and spacing inside braces does not count as a change', () => { + assert.match(run({ ...IT, title: ' ' }).join('\n'), /empty value for title/) + assert.deepEqual(run({ ...IT, hello: 'Ciao {{ name }}' }), []) + assert.deepEqual(placeholders('{{b}} %s {{a}} {{a}}'), ['%s', '{{a}}', '{{a}}', '{{b}}']) +}) + +test('i18n gate: the add-on bundles of this repository are complete', () => { + assert.deepEqual(check(), []) +}) diff --git a/addons/jellyfin/ui/src/addon.jsx b/addons/jellyfin/ui/src/addon.jsx index 5880248910..c2e5ef5090 100644 --- a/addons/jellyfin/ui/src/addon.jsx +++ b/addons/jellyfin/ui/src/addon.jsx @@ -5,15 +5,25 @@ import { getSdk } from '../../../internal/host-compat/src/runtime.js' import { registerLocales } from '../../../internal/host-compat/src/i18n.js' import Icon from '../../../internal/host-compat/src/components/Icon' import ConnectorPage from '../../../internal/connector-ui/src/ConnectorPage' +import { pageLocale } from '../../../internal/connector-ui/src/connector' import { connectorsApi } from '../../../internal/connector-ui/src/api' import { Field, Notice, RadioCards, TextInput } from '../../../internal/connector-ui/src/controls' import en from './i18n/jellyfin.en.json' +import it from './i18n/jellyfin.it.json' +import es from './i18n/jellyfin.es.json' +import de from './i18n/jellyfin.de.json' +import fr from './i18n/jellyfin.fr.json' +import zh from './i18n/jellyfin.zh-CN.json' +import id from './i18n/jellyfin.id.json' +import ko from './i18n/jellyfin.ko.json' +import pt from './i18n/jellyfin.pt-BR.json' import ProposalCard from './ProposalCard' import { checkFailure, checkItem, composeServerKey, extraBody, extraForm, extraValid, followName, isPlainHttp, jellyfinSecret, serverIssue, tokenProblem, WRITE_MODES, writeModeOf } from './server' import { finishTest, resultOf, startTest, subscribe } from './results' import './jellyfin.css' -registerLocales({ jellyfin: { en } }) +// The words of the page, in every language of the web UI; a key a language lacks reads in English. +registerLocales({ jellyfin: { en, it, es, de, fr, 'zh-CN': zh, id, ko, 'pt-BR': pt } }) // The API key. It is typed only to replace it. function JellyfinToken({ form, setForm, t }) { @@ -123,7 +133,7 @@ function Checks({ c, t }) {
  • ))} - {r.at > 0 &&

    {t('check.at', { time: new Date(r.at).toLocaleTimeString() })}

    } + {r.at > 0 &&

    {t('check.at', { time: new Date(r.at).toLocaleTimeString(pageLocale()) })}

    } ) } diff --git a/addons/jellyfin/ui/src/i18n/jellyfin.de.json b/addons/jellyfin/ui/src/i18n/jellyfin.de.json new file mode 100644 index 0000000000..edc682f90d --- /dev/null +++ b/addons/jellyfin/ui/src/i18n/jellyfin.de.json @@ -0,0 +1,128 @@ +{ + "title": "Jellyfin", + "intro": "Lass deine Agenten in deiner Jellyfin-Mediathek suchen und sehen, was gerade läuft. Wenn du es erlaubst, können sie auch Player fernsteuern, Titel als gesehen markieren, Favoriten setzen und zu Playlists hinzufügen.", + "limits": "Diese Verbindung beantwortet keine Nachrichten. Ein Agent nutzt Jellyfin nur, wenn du ihn in einem Chat darum bittest. Er kann die Mediathek weder löschen, scannen noch bearbeiten und keine Nutzer verwalten.", + "new": "Neue Jellyfin-Verbindung", + "edit": "Jellyfin-Verbindung bearbeiten", + "name": "Name", + "token": "API-Schlüssel", + "tokenHelp": "Wird im Jellyfin-Dashboard erstellt. Er wird versiegelt gespeichert und nie wieder angezeigt.", + "tokenSaved": "Gespeichert. Gib einen Schlüssel ein, um ihn zu ersetzen.", + "tokenSpace": "Der Schlüssel enthält keine Leerzeichen oder Anführungszeichen. Kopiere ihn erneut ohne die Zeichen davor und danach.", + "server": "Serveradresse", + "serverHelp": "Die Adresse, unter der du Jellyfin öffnest, zum Beispiel http://192.168.1.10:8096. Eine Verbindung gilt für einen Server; für einen zweiten Server legst du eine weitere Verbindung an. Der Agent erreicht diese Adresse und keine andere.", + "serverShape": "Das ist keine Webadresse. Schreibe sie wie http://192.168.1.10:8096 oder https://media.example.", + "serverScheme": "Die Adresse beginnt mit http:// oder https://.", + "serverUser": "Lass Benutzername und Passwort aus der Adresse weg.", + "serverQuery": "Lass alles nach ? und # weg. Für den Fingerabdruck eines Zertifikats nutze das Feld darunter.", + "serverLong": "Die Adresse ist zu lang.", + "httpNote": "Diese Adresse ist nicht verschlüsselt: Der API-Schlüssel wird im Klartext übertragen. Nutze sie nur in einem Netzwerk, dem du vertraust.", + "pin": "Zertifikat-Fingerabdruck (nur für ein selbstsigniertes Zertifikat)", + "pinHelp": "SHA-256-Fingerabdruck des Serverzertifikats, aus der Zertifikatsansicht deines Browsers. Damit akzeptiert LocalAI genau dieses Zertifikat und kein anderes. Lass das Feld leer, wenn das Zertifikat von einer öffentlichen Stelle stammt.", + "pinShape": "Ein SHA-256-Fingerabdruck besteht aus 64 Hexadezimalziffern. Doppelpunkte und Leerzeichen sind erlaubt.", + "pinHttp": "Ein Fingerabdruck gehört zu einer https://-Adresse.", + "pinnedMark": "(Zertifikat fixiert)", + "agent": "Agent", + "agentNone": "Agent wählen", + "agentCredentialsOnly": "Kein Agent (nur Zugangsdaten)", + "agentOptionalHint": "Optional. Ohne Agent hält diese Verbindung nur den Schlüssel, den Server und den Schreibmodus für deine Chats. Sie beantwortet keine Nachrichten und hört auf nichts.", + "credentialsOnly": "Nur Zugangsdaten", + "agentNoModel": "{{name}} (braucht ein Modell)", + "test": "Test", + "testOk": "Schlüssel akzeptiert.", + "checkFailed": "Gespeichert. Die Prüfung des Schlüssels ist fehlgeschlagen: {{reason}}", + "checkSeveral": "Die Serverprüfung läuft, wenn du genau eine Jellyfin-Verbindung hast, denn eine Prüfung von dieser Seite aus kann nicht erkennen, welche du meinst. Aktiviere die Verbindungen in einem Chat im Menü Werkzeuge.", + "check": { + "title": "Prüfung von {{name}}", + "running": "Der Server wird geprüft...", + "at": "Geprüft um {{time}}", + "ok": "{{name}} {{version}} antwortet, und der Schlüssel wird akzeptiert.", + "keyRefused": "{{name}} {{version}} antwortet, hat den Schlüssel aber abgelehnt. Prüfe, dass der Schlüssel nicht im Dashboard gelöscht wurde.", + "refused": "Der Server hat den Schlüssel abgelehnt. Prüfe, dass er nicht im Dashboard gelöscht wurde.", + "certificate": "Dem Zertifikat wird nicht vertraut. Füge bei einem selbstsignierten Zertifikat seinen SHA-256-Fingerabdruck hinzu; falls du das getan hast, zeigt der Server jetzt ein anderes Zertifikat.", + "failed": "Der Server hat nicht geantwortet. Prüfe die Adresse und ob der Server läuft." + }, + "save": "Speichern", + "cancel": "Abbrechen", + "editButton": "Bearbeiten", + "delete": "Löschen", + "empty": "Noch keine Jellyfin-Verbindungen.", + "noServer": "Kein Server aufgeführt: Der Agent erreicht keinen.", + "serverOf": "Server: {{server}} {{pinned}}", + "agentOf": "Agent: {{name}}", + "inactive": "Läuft nicht: {{reason}}.", + "confirmDelete": { + "title": "{{name}} löschen?", + "message": "Der Agent erreicht diesen Server nicht mehr, und der mit der Verbindung gespeicherte Schlüssel wird gelöscht. Um sie erneut einzurichten, musst du den Schlüssel noch einmal eingeben.", + "confirm": "Löschen" + }, + "setup": { + "title": "So erstellst du den Schlüssel", + "step1": "Öffne das Jellyfin-Dashboard als Administrator und gehe zu Erweitert, dann API-Schlüssel.", + "step2": "Wähle die Plus-Schaltfläche, gib dem Schlüssel einen Namen, zum Beispiel LocalAI, und bestätige.", + "step3": "Kopiere den Schlüssel, der in der Liste erscheint, und füge ihn hier in das Schlüsselfeld ein.", + "step4": "Gib die Adresse des Servers so ein, wie du ihn im Browser öffnest, mit dem Port.", + "admin": "Ein Jellyfin-API-Schlüssel gilt für den ganzen Server, nicht für einen Nutzer. Der Schreibmodus unten begrenzt den Agenten. Wenn du den Schlüssel im Dashboard löschst, funktioniert diese Verbindung sofort nicht mehr." + }, + "writeMode": { + "label": "Was der Agent auf Jellyfin ändern darf", + "hint": "Fernsteuerung von Playern, Gesehen-Markierungen, Favoriten und das Hinzufügen zu Playlists. Er löscht, scannt oder bearbeitet die Mediathek nie.", + "off": "Nur lesen", + "propose": "Fragt dich zuerst", + "auto": "Handelt ohne Rückfrage", + "line": { + "off": "Er kann nichts ändern.", + "propose": "Jede Änderung wartet auf dein OK.", + "auto": "Deine Chats handeln sofort." + }, + "note": { + "off": "Schreibzugriff ist aus: Diese Verbindung liest nur.", + "propose": "Jede Änderung wartet in deinen Freigaben, bis du sie bestätigst. Die Karte nennt den Player und den Nutzer, den sie betrifft.", + "auto": "Ein Chat von dir handelt sofort, ohne Rückfrage: Er kann pausieren oder stoppen, was andere gerade ansehen. Läufe ohne dich warten weiterhin auf deine Bestätigung." + }, + "row": { + "off": "Nur lesen", + "propose": "Fragt dich zuerst", + "auto": "Handelt ohne Rückfrage" + } + }, + "approval": { + "command": { + "play": "Einen Titel auf einem Player abspielen", + "pause": "Einen Player pausieren", + "resume": "Einen Player fortsetzen", + "stop": "Einen Player stoppen", + "seek": "Zu einer Position springen", + "set-volume": "Die Lautstärke eines Players einstellen" + }, + "action": { + "mark-played": "Die Gesehen-Markierung ändern", + "set-favorite": "Einen Favoriten ändern", + "add-to-playlist": "Zu einer Playlist hinzufügen" + }, + "player": "Player", + "ofUser": "Sitzung von {{user}}", + "nowPlaying": "Läuft gerade", + "at": "bei {{time}}", + "item": "Titel", + "seekTo": "Springen zu", + "startAt": "Start bei", + "volume": "Lautstärke", + "change": "Änderung", + "markPlayed": "Als gesehen markieren", + "markUnplayed": "Als nicht gesehen markieren", + "addFavorite": "Zu den Favoriten hinzufügen", + "removeFavorite": "Aus den Favoriten entfernen", + "playlist": "Playlist", + "items_one": "{{count}} Titel", + "items_other": "{{count}} Titel", + "more": "und {{count}} weitere", + "forUser": "Für Nutzer", + "as": "Als", + "asOne": "Die Verbindung {{name}}", + "asFirst": "Die erste Verbindung, die {{server}} aufführt", + "asUnknown": "Eine Jellyfin-Verbindung für {{server}}", + "others": "Möglicherweise sehen andere diesen Player gerade an. Das unterbricht sie.", + "names": "Die Namen werden von deinem Jellyfin-Server gelesen." + } +} diff --git a/addons/jellyfin/ui/src/i18n/jellyfin.es.json b/addons/jellyfin/ui/src/i18n/jellyfin.es.json new file mode 100644 index 0000000000..1aa4023cf6 --- /dev/null +++ b/addons/jellyfin/ui/src/i18n/jellyfin.es.json @@ -0,0 +1,128 @@ +{ + "title": "Jellyfin", + "intro": "Deja que tus agentes busquen en tu biblioteca de Jellyfin y vean qué se está reproduciendo. Si lo permites, también pueden controlar reproductores a distancia, marcar contenidos como vistos, fijar favoritos y añadir a listas de reproducción.", + "limits": "Esta conexión no responde a mensajes. Un agente usa Jellyfin solo cuando se lo pides en un chat. No puede eliminar, escanear ni editar la biblioteca, y no puede gestionar usuarios.", + "new": "Nueva conexión de Jellyfin", + "edit": "Editar conexión de Jellyfin", + "name": "Nombre", + "token": "Clave de API", + "tokenHelp": "Se crea en el panel de Jellyfin. Se guarda cifrada y no se vuelve a mostrar.", + "tokenSaved": "Guardada. Escribe una clave para reemplazarla.", + "tokenSpace": "La clave no tiene espacios ni comillas. Cópiala de nuevo sin los caracteres de alrededor.", + "server": "Dirección del servidor", + "serverHelp": "La dirección con la que abres Jellyfin, por ejemplo http://192.168.1.10:8096. Una conexión guarda un servidor; añade otra conexión para un segundo servidor. El agente accede a esta dirección y a ninguna otra.", + "serverShape": "Esto no es una dirección web. Escríbela como http://192.168.1.10:8096 o https://media.example.", + "serverScheme": "La dirección empieza por http:// o https://.", + "serverUser": "Deja fuera de la dirección el nombre de usuario y la contraseña.", + "serverQuery": "Deja fuera todo lo que va después de ? y #. Usa el cuadro de abajo para una huella de certificado.", + "serverLong": "La dirección es demasiado larga.", + "httpNote": "Esta dirección no está cifrada: la clave de API viaja en texto plano. Úsala solo en una red de confianza.", + "pin": "Huella del certificado (solo para un certificado autofirmado)", + "pinHelp": "Huella SHA-256 del certificado del servidor, desde la vista de certificados de tu navegador. Con ella, LocalAI acepta ese certificado y ningún otro. Déjala vacía cuando el certificado venga de una autoridad pública.", + "pinShape": "Una huella SHA-256 son 64 dígitos hexadecimales. Los dos puntos y los espacios no importan.", + "pinHttp": "Una huella va con una dirección https://.", + "pinnedMark": "(certificado fijado)", + "agent": "Agente", + "agentNone": "Elige un agente", + "agentCredentialsOnly": "Sin agente (solo credenciales)", + "agentOptionalHint": "Opcional. Sin agente, esta conexión solo guarda la clave, el servidor y el modo de escritura para tus chats. No responde a ningún mensaje y no escucha nada.", + "credentialsOnly": "Solo credenciales", + "agentNoModel": "{{name}} (necesita un modelo)", + "test": "Probar", + "testOk": "Clave aceptada.", + "checkFailed": "Guardada. Falló la comprobación de la clave: {{reason}}", + "checkSeveral": "La comprobación del servidor se ejecuta cuando tienes una sola conexión de Jellyfin, porque una comprobación desde esta página no puede saber a cuál te refieres. En un chat, activa las conexiones en el menú Herramientas.", + "check": { + "title": "Comprobación de {{name}}", + "running": "Comprobando el servidor...", + "at": "Comprobado a las {{time}}", + "ok": "{{name}} {{version}} responde, y la clave es aceptada.", + "keyRefused": "{{name}} {{version}} responde, pero rechazó la clave. Comprueba que la clave no se haya eliminado en el panel.", + "refused": "El servidor rechazó la clave. Comprueba que no se haya eliminado en el panel.", + "certificate": "El certificado no es de confianza. Para un certificado autofirmado, añade su huella SHA-256; si ya lo hiciste, el servidor muestra ahora otro certificado.", + "failed": "El servidor no respondió. Comprueba la dirección y que el servidor esté encendido." + }, + "save": "Guardar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Eliminar", + "empty": "Aún no hay conexiones de Jellyfin.", + "noServer": "No hay ningún servidor en la lista: el agente no puede acceder a ninguno.", + "serverOf": "Servidor: {{server}} {{pinned}}", + "agentOf": "Agente: {{name}}", + "inactive": "No se está ejecutando: {{reason}}.", + "confirmDelete": { + "title": "¿Eliminar {{name}}?", + "message": "El agente ya no puede acceder a este servidor y se elimina la clave guardada con la conexión. Para configurarla de nuevo tendrás que volver a introducir la clave.", + "confirm": "Eliminar" + }, + "setup": { + "title": "Cómo crear la clave", + "step1": "Abre el panel de Jellyfin como administrador y ve a Advanced, y luego a API Keys.", + "step2": "Pulsa el botón más, ponle un nombre a la clave, por ejemplo LocalAI, y confirma.", + "step3": "Copia la clave que aparece en la lista y pégala aquí en el campo de la clave.", + "step4": "Escribe la dirección del servidor tal como la abres en un navegador, con el puerto.", + "admin": "Una clave de API de Jellyfin vale para todo el servidor, no para un solo usuario. El modo de escritura de abajo es lo que limita al agente. Si eliminas la clave en el panel, esta conexión deja de funcionar al instante." + }, + "writeMode": { + "label": "Qué puede cambiar el agente en Jellyfin", + "hint": "Control remoto de reproductores, marcas de visto, favoritos y añadir a listas de reproducción. Nunca elimina, escanea ni edita la biblioteca.", + "off": "Solo lectura", + "propose": "Te pregunta antes", + "auto": "Actúa sin preguntar", + "line": { + "off": "No puede cambiar nada.", + "propose": "Cada cambio espera tu visto bueno.", + "auto": "Tus chats actúan al instante." + }, + "note": { + "off": "El acceso de escritura está desactivado: esta conexión solo lee.", + "propose": "Cada cambio espera en tus aprobaciones hasta que lo confirmes. La tarjeta indica el reproductor y el usuario a quienes afecta.", + "auto": "Un chat tuyo actúa al instante, sin preguntar: puede pausar o detener lo que otras personas están viendo. Las ejecuciones sin ti presente siguen esperando tu confirmación." + }, + "row": { + "off": "Solo lectura", + "propose": "Te pregunta antes", + "auto": "Actúa sin preguntar" + } + }, + "approval": { + "command": { + "play": "Reproducir un elemento en un reproductor", + "pause": "Pausar un reproductor", + "resume": "Reanudar un reproductor", + "stop": "Detener un reproductor", + "seek": "Saltar a una posición", + "set-volume": "Fijar el volumen de un reproductor" + }, + "action": { + "mark-played": "Cambiar la marca de visto", + "set-favorite": "Cambiar un favorito", + "add-to-playlist": "Añadir a una lista de reproducción" + }, + "player": "Reproductor", + "ofUser": "Sesión de {{user}}", + "nowPlaying": "Reproduciendo ahora", + "at": "a las {{time}}", + "item": "Elemento", + "seekTo": "Saltar a", + "startAt": "Empezar en", + "volume": "Volumen", + "change": "Cambio", + "markPlayed": "Marcar como visto", + "markUnplayed": "Marcar como no visto", + "addFavorite": "Añadir a favoritos", + "removeFavorite": "Quitar de favoritos", + "playlist": "Lista de reproducción", + "items_one": "{{count}} elemento", + "items_other": "{{count}} elementos", + "more": "y {{count}} más", + "forUser": "Para el usuario", + "as": "Como", + "asOne": "La conexión {{name}}", + "asFirst": "La primera conexión que incluye {{server}}", + "asUnknown": "Una conexión de Jellyfin para {{server}}", + "others": "Puede que otras personas estén viendo este reproductor. Esto las interrumpe.", + "names": "Los nombres se leen de tu servidor de Jellyfin." + } +} diff --git a/addons/jellyfin/ui/src/i18n/jellyfin.fr.json b/addons/jellyfin/ui/src/i18n/jellyfin.fr.json new file mode 100644 index 0000000000..b6e259cba6 --- /dev/null +++ b/addons/jellyfin/ui/src/i18n/jellyfin.fr.json @@ -0,0 +1,128 @@ +{ + "title": "Jellyfin", + "intro": "Laissez vos agents chercher dans votre bibliothèque Jellyfin et voir ce qui est en cours de lecture. Si vous l’autorisez, ils peuvent aussi contrôler les lecteurs à distance, marquer des contenus comme vus, définir des favoris et ajouter à des listes de lecture.", + "limits": "Cette connexion ne répond pas aux messages. Un agent n’utilise Jellyfin que lorsque vous le lui demandez dans un chat. Il ne peut ni supprimer, ni analyser, ni modifier la bibliothèque, et il ne peut pas gérer les utilisateurs.", + "new": "Nouvelle connexion Jellyfin", + "edit": "Modifier la connexion Jellyfin", + "name": "Nom", + "token": "Clé API", + "tokenHelp": "Créée dans le tableau de bord Jellyfin. Elle est conservée scellée et n’est plus jamais affichée.", + "tokenSaved": "Enregistrée. Saisissez une clé pour la remplacer.", + "tokenSpace": "La clé ne contient ni espace ni guillemet. Copiez-la de nouveau sans les caractères autour.", + "server": "Adresse du serveur", + "serverHelp": "L’adresse avec laquelle vous ouvrez Jellyfin, par exemple http://192.168.1.10:8096. Une connexion correspond à un serveur ; ajoutez une autre connexion pour un second serveur. L’agent atteint cette adresse et aucune autre.", + "serverShape": "Ce n’est pas une adresse web. Écrivez-la comme http://192.168.1.10:8096 ou https://media.example.", + "serverScheme": "L’adresse commence par http:// ou https://.", + "serverUser": "N’indiquez ni nom d’utilisateur ni mot de passe dans l’adresse.", + "serverQuery": "Retirez tout ce qui suit ? et #. Utilisez le champ ci-dessous pour l’empreinte d’un certificat.", + "serverLong": "L’adresse est trop longue.", + "httpNote": "Cette adresse n’est pas chiffrée : la clé API circule en clair. Ne l’utilisez que sur un réseau de confiance.", + "pin": "Empreinte du certificat (seulement pour un certificat auto-signé)", + "pinHelp": "Empreinte SHA-256 du certificat du serveur, visible dans l’affichage du certificat de votre navigateur. Avec elle, LocalAI accepte ce certificat et aucun autre. Laissez vide quand le certificat vient d’une autorité publique.", + "pinShape": "Une empreinte SHA-256 compte 64 chiffres hexadécimaux. Les deux-points et les espaces sont acceptés.", + "pinHttp": "Une empreinte va avec une adresse https://.", + "pinnedMark": "(certificat épinglé)", + "agent": "Agent", + "agentNone": "Choisir un agent", + "agentCredentialsOnly": "Aucun agent (identifiants uniquement)", + "agentOptionalHint": "Facultatif. Sans agent, cette connexion ne garde que la clé, le serveur et le mode d’écriture pour vos chats. Elle ne répond à aucun message et n’écoute rien.", + "credentialsOnly": "Identifiants uniquement", + "agentNoModel": "{{name}} (a besoin d’un modèle)", + "test": "Tester", + "testOk": "Clé acceptée.", + "checkFailed": "Enregistré. La vérification de la clé a échoué : {{reason}}", + "checkSeveral": "La vérification du serveur s’exécute lorsque vous avez une seule connexion Jellyfin, car une vérification lancée depuis cette page ne peut pas savoir laquelle vous voulez. Dans un chat, activez les connexions dans le menu Outils.", + "check": { + "title": "Vérification de {{name}}", + "running": "Vérification du serveur...", + "at": "Vérifié à {{time}}", + "ok": "{{name}} {{version}} répond, et la clé est acceptée.", + "keyRefused": "{{name}} {{version}} répond, mais il a refusé la clé. Vérifiez que la clé n’a pas été supprimée dans le tableau de bord.", + "refused": "Le serveur a refusé la clé. Vérifiez qu’elle n’a pas été supprimée dans le tableau de bord.", + "certificate": "Le certificat n’est pas approuvé. Pour un certificat auto-signé, ajoutez son empreinte SHA-256 ; si c’est déjà fait, le serveur présente maintenant un autre certificat.", + "failed": "Le serveur n’a pas répondu. Vérifiez l’adresse et que le serveur est allumé." + }, + "save": "Enregistrer", + "cancel": "Annuler", + "editButton": "Modifier", + "delete": "Supprimer", + "empty": "Aucune connexion Jellyfin pour le moment.", + "noServer": "Aucun serveur indiqué : l’agent ne peut en atteindre aucun.", + "serverOf": "Serveur : {{server}} {{pinned}}", + "agentOf": "Agent : {{name}}", + "inactive": "À l’arrêt : {{reason}}.", + "confirmDelete": { + "title": "Supprimer {{name}} ?", + "message": "L’agent ne peut plus atteindre ce serveur, et la clé enregistrée avec la connexion est supprimée. Pour la reconfigurer, vous devrez saisir de nouveau la clé.", + "confirm": "Supprimer" + }, + "setup": { + "title": "Comment créer la clé", + "step1": "Ouvrez le tableau de bord Jellyfin en tant qu’administrateur et allez dans Avancé, puis Clés API.", + "step2": "Cliquez sur le bouton plus, donnez un nom à la clé, par exemple LocalAI, et confirmez.", + "step3": "Copiez la clé qui apparaît dans la liste et collez-la dans le champ de la clé ici.", + "step4": "Saisissez l’adresse du serveur telle que vous l’ouvrez dans un navigateur, avec le port.", + "admin": "Une clé API Jellyfin vaut pour tout le serveur, pas pour un seul utilisateur. Le mode d’écriture ci-dessous est ce qui limite l’agent. Si vous supprimez la clé dans le tableau de bord, cette connexion cesse de fonctionner aussitôt." + }, + "writeMode": { + "label": "Ce que l’agent peut modifier sur Jellyfin", + "hint": "Contrôle à distance des lecteurs, marques « vu », favoris et ajout à des listes de lecture. Il ne supprime, n’analyse et ne modifie jamais la bibliothèque.", + "off": "Lecture seule", + "propose": "Vous demande d’abord", + "auto": "Agit sans demander", + "line": { + "off": "Il ne peut rien modifier.", + "propose": "Chaque modification attend votre accord.", + "auto": "Vos chats agissent tout de suite." + }, + "note": { + "off": "L’accès en écriture est désactivé : cette connexion ne fait que lire.", + "propose": "Chaque modification attend dans vos approbations jusqu’à votre confirmation. La carte indique le lecteur et l’utilisateur concernés.", + "auto": "Un de vos chats agit tout de suite, sans demander : il peut mettre en pause ou arrêter ce que d’autres regardent. Les exécutions sans vous attendent toujours votre confirmation." + }, + "row": { + "off": "Lecture seule", + "propose": "Vous demande d’abord", + "auto": "Agit sans demander" + } + }, + "approval": { + "command": { + "play": "Lire un élément sur un lecteur", + "pause": "Mettre un lecteur en pause", + "resume": "Reprendre la lecture d’un lecteur", + "stop": "Arrêter un lecteur", + "seek": "Aller à une position", + "set-volume": "Régler le volume d’un lecteur" + }, + "action": { + "mark-played": "Modifier la marque « vu »", + "set-favorite": "Modifier un favori", + "add-to-playlist": "Ajouter à une liste de lecture" + }, + "player": "Lecteur", + "ofUser": "Session de {{user}}", + "nowPlaying": "En cours de lecture", + "at": "à {{time}}", + "item": "Élément", + "seekTo": "Aller à", + "startAt": "Démarrer à", + "volume": "Volume", + "change": "Modification", + "markPlayed": "Marquer comme vu", + "markUnplayed": "Marquer comme non vu", + "addFavorite": "Ajouter aux favoris", + "removeFavorite": "Retirer des favoris", + "playlist": "Liste de lecture", + "items_one": "{{count}} élément", + "items_other": "{{count}} éléments", + "more": "et {{count}} de plus", + "forUser": "Pour l’utilisateur", + "as": "En tant que", + "asOne": "La connexion {{name}}", + "asFirst": "La première connexion qui liste {{server}}", + "asUnknown": "Une connexion Jellyfin pour {{server}}", + "others": "D’autres personnes regardent peut-être sur ce lecteur. Cela les interrompt.", + "names": "Les noms sont lus depuis votre serveur Jellyfin." + } +} diff --git a/addons/jellyfin/ui/src/i18n/jellyfin.id.json b/addons/jellyfin/ui/src/i18n/jellyfin.id.json new file mode 100644 index 0000000000..e154b2e85b --- /dev/null +++ b/addons/jellyfin/ui/src/i18n/jellyfin.id.json @@ -0,0 +1,128 @@ +{ + "title": "Jellyfin", + "intro": "Biarkan agen Anda mencari di pustaka Jellyfin dan melihat apa yang sedang diputar. Jika Anda mengizinkan, mereka juga dapat mengendalikan pemutar dari jarak jauh, menandai yang sudah ditonton, mengatur favorit, dan menambahkan ke playlist.", + "limits": "Koneksi ini tidak menjawab pesan. Agen memakai Jellyfin hanya saat Anda memintanya di chat. Agen tidak dapat menghapus, memindai, atau mengubah pustaka, dan tidak dapat mengelola pengguna.", + "new": "Koneksi Jellyfin baru", + "edit": "Ubah koneksi Jellyfin", + "name": "Nama", + "token": "Kunci API", + "tokenHelp": "Dibuat di dashboard Jellyfin. Disimpan terenkripsi dan tidak pernah ditampilkan lagi.", + "tokenSaved": "Tersimpan. Ketik kunci untuk menggantinya.", + "tokenSpace": "Kunci tidak memiliki spasi atau tanda kutip. Salin lagi tanpa karakter di sekitarnya.", + "server": "Alamat server", + "serverHelp": "Alamat yang Anda pakai untuk membuka Jellyfin, misalnya http://192.168.1.10:8096. Satu koneksi untuk satu server; tambahkan koneksi lain untuk server kedua. Agen menjangkau alamat ini dan tidak ada yang lain.", + "serverShape": "Ini bukan alamat web. Tulis seperti http://192.168.1.10:8096 atau https://media.example.", + "serverScheme": "Alamat diawali http:// atau https://.", + "serverUser": "Jangan sertakan nama pengguna dan kata sandi di alamat.", + "serverQuery": "Hapus semua yang setelah ? dan #. Gunakan kolom di bawah untuk fingerprint sertifikat.", + "serverLong": "Alamat terlalu panjang.", + "httpNote": "Alamat ini tidak terenkripsi: kunci API dikirim sebagai teks biasa. Pakai hanya di jaringan yang Anda percayai.", + "pin": "Fingerprint sertifikat (hanya untuk sertifikat self-signed)", + "pinHelp": "Fingerprint SHA-256 sertifikat server, dari tampilan sertifikat di browser Anda. Dengan ini, LocalAI menerima sertifikat itu saja dan tidak ada yang lain. Kosongkan jika sertifikat berasal dari otoritas publik.", + "pinShape": "Fingerprint SHA-256 adalah 64 digit heksadesimal. Titik dua dan spasi tidak masalah.", + "pinHttp": "Fingerprint dipakai untuk alamat https://.", + "pinnedMark": "(sertifikat disematkan)", + "agent": "Agen", + "agentNone": "Pilih agen", + "agentCredentialsOnly": "Tanpa agen (hanya kredensial)", + "agentOptionalHint": "Opsional. Tanpa agen, koneksi ini hanya menyimpan kunci, server, dan mode tulis untuk chat Anda. Koneksi tidak menjawab pesan dan tidak mendengarkan apa pun.", + "credentialsOnly": "Hanya kredensial", + "agentNoModel": "{{name}} (perlu model)", + "test": "Uji", + "testOk": "Kunci diterima.", + "checkFailed": "Tersimpan. Pemeriksaan kunci gagal: {{reason}}", + "checkSeveral": "Pemeriksaan server berjalan jika Anda hanya punya satu koneksi Jellyfin, karena pemeriksaan dari halaman ini tidak dapat tahu koneksi mana yang Anda maksud. Di chat, aktifkan koneksi lewat menu Alat.", + "check": { + "title": "Pemeriksaan {{name}}", + "running": "Memeriksa server...", + "at": "Diperiksa pada {{time}}", + "ok": "{{name}} {{version}} menjawab, dan kunci diterima.", + "keyRefused": "{{name}} {{version}} menjawab, tetapi menolak kunci. Periksa apakah kunci belum dihapus di dashboard.", + "refused": "Server menolak kunci. Periksa apakah kunci belum dihapus di dashboard.", + "certificate": "Sertifikat tidak tepercaya. Untuk sertifikat self-signed, tambahkan fingerprint SHA-256-nya; jika sudah, server kini menampilkan sertifikat lain.", + "failed": "Server tidak menjawab. Periksa alamat dan pastikan server menyala." + }, + "save": "Simpan", + "cancel": "Batal", + "editButton": "Ubah", + "delete": "Hapus", + "empty": "Belum ada koneksi Jellyfin.", + "noServer": "Tidak ada server terdaftar: agen tidak dapat menjangkau satu pun.", + "serverOf": "Server: {{server}} {{pinned}}", + "agentOf": "Agen: {{name}}", + "inactive": "Tidak berjalan: {{reason}}.", + "confirmDelete": { + "title": "Hapus {{name}}?", + "message": "Agen tidak dapat lagi menjangkau server ini, dan kunci yang tersimpan bersamanya dihapus. Untuk menyiapkannya lagi, Anda harus memasukkan kunci lagi.", + "confirm": "Hapus" + }, + "setup": { + "title": "Cara membuat kunci", + "step1": "Buka dashboard Jellyfin sebagai administrator dan masuk ke Advanced, lalu API Keys.", + "step2": "Pilih tombol plus, beri nama kunci, misalnya LocalAI, lalu konfirmasi.", + "step3": "Salin kunci yang muncul di daftar dan tempel di kolom kunci di sini.", + "step4": "Masukkan alamat server seperti yang Anda buka di browser, beserta port-nya.", + "admin": "Kunci API Jellyfin berlaku untuk seluruh server, bukan untuk satu pengguna. Mode tulis di bawah yang membatasi agen. Jika Anda menghapus kunci di dashboard, koneksi ini langsung berhenti berfungsi." + }, + "writeMode": { + "label": "Apa yang boleh diubah agen di Jellyfin", + "hint": "Kendali jarak jauh pemutar, tanda sudah ditonton, favorit, dan menambah ke playlist. Agen tidak pernah menghapus, memindai, atau mengubah pustaka.", + "off": "Hanya baca", + "propose": "Tanya Anda dulu", + "auto": "Bertindak tanpa bertanya", + "line": { + "off": "Agen tidak dapat mengubah apa pun.", + "propose": "Setiap perubahan menunggu persetujuan Anda.", + "auto": "Chat Anda langsung bertindak." + }, + "note": { + "off": "Akses tulis nonaktif: koneksi ini hanya membaca.", + "propose": "Setiap perubahan menunggu di persetujuan Anda sampai Anda mengonfirmasinya. Kartu menyebut pemutar dan pengguna yang terpengaruh.", + "auto": "Chat Anda langsung bertindak, tanpa bertanya: ia dapat menjeda atau menghentikan apa yang sedang ditonton orang lain. Jalan tanpa kehadiran Anda tetap menunggu konfirmasi Anda." + }, + "row": { + "off": "Hanya baca", + "propose": "Tanya Anda dulu", + "auto": "Bertindak tanpa bertanya" + } + }, + "approval": { + "command": { + "play": "Putar item di pemutar", + "pause": "Jeda pemutar", + "resume": "Lanjutkan pemutar", + "stop": "Hentikan pemutar", + "seek": "Lompat ke posisi", + "set-volume": "Atur volume pemutar" + }, + "action": { + "mark-played": "Ubah tanda sudah ditonton", + "set-favorite": "Ubah favorit", + "add-to-playlist": "Tambah ke playlist" + }, + "player": "Pemutar", + "ofUser": "Sesi {{user}}", + "nowPlaying": "Sedang diputar", + "at": "pada {{time}}", + "item": "Item", + "seekTo": "Lompat ke", + "startAt": "Mulai pada", + "volume": "Volume", + "change": "Perubahan", + "markPlayed": "Tandai sudah ditonton", + "markUnplayed": "Tandai belum ditonton", + "addFavorite": "Tambah ke favorit", + "removeFavorite": "Hapus dari favorit", + "playlist": "Playlist", + "items_one": "{{count}} item", + "items_other": "{{count}} item", + "more": "dan {{count}} lagi", + "forUser": "Untuk pengguna", + "as": "Sebagai", + "asOne": "Koneksi {{name}}", + "asFirst": "Koneksi pertama yang mencantumkan {{server}}", + "asUnknown": "Koneksi Jellyfin untuk {{server}}", + "others": "Orang lain mungkin sedang menonton di pemutar ini. Ini akan menginterupsi mereka.", + "names": "Nama dibaca dari server Jellyfin Anda." + } +} diff --git a/addons/jellyfin/ui/src/i18n/jellyfin.it.json b/addons/jellyfin/ui/src/i18n/jellyfin.it.json new file mode 100644 index 0000000000..cf64c112da --- /dev/null +++ b/addons/jellyfin/ui/src/i18n/jellyfin.it.json @@ -0,0 +1,128 @@ +{ + "title": "Jellyfin", + "intro": "Permetti ai tuoi agenti di cercare nella tua libreria Jellyfin e di vedere cosa è in riproduzione. Se lo consenti, possono anche controllare i player da remoto, segnare i contenuti come visti, impostare i preferiti e aggiungere alle playlist.", + "limits": "Questa connessione non risponde ai messaggi. Un agente usa Jellyfin solo quando glielo chiedi in una chat. Non può eliminare, scansionare o modificare la libreria, né gestire gli utenti.", + "new": "Nuova connessione Jellyfin", + "edit": "Modifica la connessione Jellyfin", + "name": "Nome", + "token": "Chiave API", + "tokenHelp": "Si crea nella dashboard di Jellyfin. Viene cifrata e non viene più mostrata.", + "tokenSaved": "Salvata. Scrivi per sostituirla.", + "tokenSpace": "La chiave non contiene spazi né virgolette. Copiala di nuovo senza i caratteri attorno.", + "server": "Indirizzo del server", + "serverHelp": "L'indirizzo con cui apri Jellyfin, per esempio http://192.168.1.10:8096. Una connessione ha un solo server; per un secondo server aggiungi un'altra connessione. L'agente raggiunge questo indirizzo e nessun altro.", + "serverShape": "Questo non è un indirizzo web. Scrivilo come http://192.168.1.10:8096 oppure https://media.example.", + "serverScheme": "L'indirizzo inizia con http:// o https://.", + "serverUser": "Lascia fuori dall'indirizzo il nome utente e la password.", + "serverQuery": "Lascia fuori tutto ciò che segue ? e #. Per l'impronta del certificato usa il campo qui sotto.", + "serverLong": "L'indirizzo è troppo lungo.", + "httpNote": "Questo indirizzo non è cifrato: la chiave API viaggia in chiaro. Usalo solo su una rete di cui ti fidi.", + "pin": "Impronta del certificato (solo per un certificato autofirmato)", + "pinHelp": "Impronta SHA-256 del certificato del server, dalla vista certificato del tuo browser. Con essa, LocalAI accetta quel solo certificato e nessun altro. Lasciala vuota quando il certificato è di un'autorità pubblica.", + "pinShape": "Un'impronta SHA-256 è di 64 cifre esadecimali. Due punti e spazi vanno bene.", + "pinHttp": "Un'impronta va con un indirizzo https://.", + "pinnedMark": "(certificato fissato)", + "agent": "Agente", + "agentNone": "Scegli un agente", + "agentCredentialsOnly": "Nessun agente (solo credenziali)", + "agentOptionalHint": "Facoltativo. Senza un agente questa connessione conserva solo la chiave, il server e la modalità di scrittura per le tue chat. Non risponde ad alcun messaggio e non resta in ascolto.", + "credentialsOnly": "Solo credenziali", + "agentNoModel": "{{name}} (serve un modello)", + "test": "Prova", + "testOk": "Chiave accettata.", + "checkFailed": "Salvato. La verifica della chiave non è riuscita: {{reason}}", + "checkSeveral": "La verifica del server funziona quando hai una sola connessione Jellyfin, perché da questa pagina non si capisce a quale ti riferisci. In una chat, attiva le connessioni nel menu Strumenti.", + "check": { + "title": "Verifica di {{name}}", + "running": "Verifica del server...", + "at": "Verificato alle {{time}}", + "ok": "{{name}} {{version}} risponde e la chiave è accettata.", + "keyRefused": "{{name}} {{version}} risponde, ma ha rifiutato la chiave. Controlla che la chiave non sia stata eliminata nella dashboard.", + "refused": "Il server ha rifiutato la chiave. Controlla che non sia stata eliminata nella dashboard.", + "certificate": "Il certificato non è attendibile. Per un certificato autofirmato, aggiungi la sua impronta SHA-256; se l'hai già fatto, ora il server mostra un altro certificato.", + "failed": "Il server non ha risposto. Controlla l'indirizzo e che il server sia acceso." + }, + "save": "Salva", + "cancel": "Annulla", + "editButton": "Modifica", + "delete": "Elimina", + "empty": "Ancora nessuna connessione Jellyfin.", + "noServer": "Nessun server in elenco: l'agente non ne raggiunge nessuno.", + "serverOf": "Server: {{server}} {{pinned}}", + "agentOf": "Agente: {{name}}", + "inactive": "Non in esecuzione: {{reason}}.", + "confirmDelete": { + "title": "Eliminare {{name}}?", + "message": "L'agente non può più raggiungere questo server e la chiave salvata con la connessione viene eliminata. Per configurarla di nuovo dovrai inserire di nuovo la chiave.", + "confirm": "Elimina" + }, + "setup": { + "title": "Come creare la chiave", + "step1": "Apri la dashboard di Jellyfin come amministratore e vai su Advanced, poi API Keys.", + "step2": "Premi il pulsante più, dai un nome alla chiave, per esempio LocalAI, e conferma.", + "step3": "Copia la chiave che compare nell'elenco e incollala qui nel campo della chiave.", + "step4": "Inserisci l'indirizzo del server come lo apri in un browser, con la porta.", + "admin": "Una chiave API di Jellyfin vale per l'intero server, non per un solo utente. A limitare l'agente è la modalità di scrittura qui sotto. Se elimini la chiave nella dashboard, questa connessione smette subito di funzionare." + }, + "writeMode": { + "label": "Cosa può modificare l'agente su Jellyfin", + "hint": "Controllo remoto dei player, segni di visto, preferiti e aggiunta alle playlist. Non elimina, non scansiona e non modifica mai la libreria.", + "off": "Sola lettura", + "propose": "Chiede prima a te", + "auto": "Agisce senza chiedere", + "line": { + "off": "Non può cambiare nulla.", + "propose": "Ogni modifica aspetta il tuo OK.", + "auto": "Le tue chat agiscono subito." + }, + "note": { + "off": "L'accesso in scrittura è disattivato: questa connessione si limita a leggere.", + "propose": "Ogni modifica aspetta tra le tue approvazioni finché non la confermi. La scheda indica il player e l'utente interessati.", + "auto": "Una tua chat agisce subito, senza chiedere: può mettere in pausa o fermare ciò che stanno guardando altre persone. Le esecuzioni senza di te presente aspettano comunque la tua conferma." + }, + "row": { + "off": "Sola lettura", + "propose": "Chiede prima a te", + "auto": "Agisce senza chiedere" + } + }, + "approval": { + "command": { + "play": "Riprodurre un contenuto su un player", + "pause": "Mettere in pausa un player", + "resume": "Riprendere un player", + "stop": "Fermare un player", + "seek": "Saltare a una posizione", + "set-volume": "Impostare il volume di un player" + }, + "action": { + "mark-played": "Cambiare il segno di visto", + "set-favorite": "Cambiare un preferito", + "add-to-playlist": "Aggiungere a una playlist" + }, + "player": "Player", + "ofUser": "Sessione di {{user}}", + "nowPlaying": "In riproduzione ora", + "at": "alle {{time}}", + "item": "Contenuto", + "seekTo": "Salta a", + "startAt": "Inizia da", + "volume": "Volume", + "change": "Modifica", + "markPlayed": "Segna come visto", + "markUnplayed": "Segna come non visto", + "addFavorite": "Aggiungi ai preferiti", + "removeFavorite": "Rimuovi dai preferiti", + "playlist": "Playlist", + "items_one": "{{count}} contenuto", + "items_other": "{{count}} contenuti", + "more": "e altri {{count}}", + "forUser": "Per l'utente", + "as": "Come", + "asOne": "La connessione {{name}}", + "asFirst": "La prima connessione che elenca {{server}}", + "asUnknown": "Una connessione Jellyfin per {{server}}", + "others": "Altre persone potrebbero stare guardando questo player. Questo le interrompe.", + "names": "I nomi vengono letti dal tuo server Jellyfin." + } +} diff --git a/addons/jellyfin/ui/src/i18n/jellyfin.ko.json b/addons/jellyfin/ui/src/i18n/jellyfin.ko.json new file mode 100644 index 0000000000..edddbe15f3 --- /dev/null +++ b/addons/jellyfin/ui/src/i18n/jellyfin.ko.json @@ -0,0 +1,128 @@ +{ + "title": "Jellyfin", + "intro": "에이전트가 Jellyfin 라이브러리를 검색하고 재생 중인 항목을 볼 수 있게 합니다. 허용하면 플레이어 원격 제어, 시청 완료 표시, 즐겨찾기 설정, 재생 목록에 추가도 할 수 있습니다.", + "limits": "이 연결은 메시지에 답변하지 않습니다. 에이전트는 채팅에서 요청할 때만 Jellyfin을 사용합니다. 라이브러리를 삭제, 스캔, 편집할 수 없고 사용자를 관리할 수도 없습니다.", + "new": "새 Jellyfin 연결", + "edit": "Jellyfin 연결 편집", + "name": "이름", + "token": "API 키", + "tokenHelp": "Jellyfin 대시보드에서 만듭니다. 봉인되어 보관되며 다시 표시되지 않습니다.", + "tokenSaved": "저장됨. 교체하려면 키를 입력하세요.", + "tokenSpace": "키에는 공백이나 따옴표가 없습니다. 주변 문자 없이 다시 복사하세요.", + "server": "서버 주소", + "serverHelp": "Jellyfin을 열 때 쓰는 주소입니다. 예: http://192.168.1.10:8096. 연결 하나에 서버 하나입니다. 서버가 더 있으면 연결을 하나 더 추가하세요. 에이전트는 이 주소에만 접속합니다.", + "serverShape": "웹 주소가 아닙니다. http://192.168.1.10:8096 또는 https://media.example처럼 입력하세요.", + "serverScheme": "주소는 http:// 또는 https://로 시작합니다.", + "serverUser": "주소에 사용자 이름과 비밀번호를 넣지 마세요.", + "serverQuery": "? 와 # 뒤의 내용은 빼세요. 인증서 지문은 아래 입력란에 넣으세요.", + "serverLong": "주소가 너무 깁니다.", + "httpNote": "이 주소는 암호화되지 않아 API 키가 평문으로 전송됩니다. 신뢰하는 네트워크에서만 사용하세요.", + "pin": "인증서 지문(자체 서명 인증서에만 해당)", + "pinHelp": "브라우저의 인증서 보기에서 확인하는 서버 인증서의 SHA-256 지문입니다. 입력하면 LocalAI는 그 인증서 하나만 받아들입니다. 인증서가 공인 기관에서 발급된 것이면 비워 두세요.", + "pinShape": "SHA-256 지문은 16진수 64자리입니다. 콜론과 공백은 있어도 됩니다.", + "pinHttp": "지문은 https:// 주소와 함께 사용합니다.", + "pinnedMark": "(인증서 고정됨)", + "agent": "에이전트", + "agentNone": "에이전트 고르기", + "agentCredentialsOnly": "에이전트 없음(자격 증명만)", + "agentOptionalHint": "선택 사항입니다. 에이전트가 없으면 이 연결은 키, 서버, 내 채팅의 쓰기 모드만 보관합니다. 메시지에 답변하지 않고 아무것도 수신하지 않습니다.", + "credentialsOnly": "자격 증명만", + "agentNoModel": "{{name}}(모델 필요)", + "test": "테스트", + "testOk": "키가 확인되었습니다.", + "checkFailed": "저장했습니다. 키 확인에 실패했습니다: {{reason}}", + "checkSeveral": "이 페이지의 확인으로는 어느 연결인지 알 수 없으므로, 서버 확인은 Jellyfin 연결이 하나일 때만 실행됩니다. 채팅에서는 도구 메뉴에서 연결을 켜세요.", + "check": { + "title": "{{name}} 확인", + "running": "서버를 확인하는 중...", + "at": "{{time}}에 확인함", + "ok": "{{name}} {{version}}이(가) 응답하며 키가 확인되었습니다.", + "keyRefused": "{{name}} {{version}}이(가) 응답하지만 키를 거부했습니다. 대시보드에서 키가 삭제되지 않았는지 확인하세요.", + "refused": "서버가 키를 거부했습니다. 대시보드에서 삭제되지 않았는지 확인하세요.", + "certificate": "신뢰할 수 없는 인증서입니다. 자체 서명 인증서라면 SHA-256 지문을 추가하세요. 이미 추가했다면 서버가 지금 다른 인증서를 보여 주고 있습니다.", + "failed": "서버가 응답하지 않았습니다. 주소와 서버가 켜져 있는지 확인하세요." + }, + "save": "저장", + "cancel": "취소", + "editButton": "편집", + "delete": "삭제", + "empty": "아직 Jellyfin 연결이 없습니다.", + "noServer": "나열된 서버가 없습니다. 에이전트가 접속할 수 있는 곳이 없습니다.", + "serverOf": "서버: {{server}} {{pinned}}", + "agentOf": "에이전트: {{name}}", + "inactive": "실행 중이 아님: {{reason}}.", + "confirmDelete": { + "title": "{{name}}을(를) 삭제할까요?", + "message": "에이전트가 더 이상 이 서버에 접속할 수 없고, 함께 저장된 키가 삭제됩니다. 다시 설정하려면 키를 다시 입력해야 합니다.", + "confirm": "삭제" + }, + "setup": { + "title": "키 만드는 방법", + "step1": "관리자로 Jellyfin 대시보드를 열고 Advanced, API Keys로 이동하세요.", + "step2": "더하기 버튼을 누르고 키 이름(예: LocalAI)을 정한 뒤 확인하세요.", + "step3": "목록에 나타난 키를 복사해 여기 키 필드에 붙여 넣으세요.", + "step4": "브라우저에서 열 때 쓰는 서버 주소를 포트까지 입력하세요.", + "admin": "Jellyfin API 키는 한 사용자가 아니라 서버 전체에 적용됩니다. 에이전트를 제한하는 것은 아래의 쓰기 모드입니다. 대시보드에서 키를 삭제하면 이 연결은 즉시 작동을 멈춥니다." + }, + "writeMode": { + "label": "에이전트가 Jellyfin에서 바꿀 수 있는 것", + "hint": "플레이어 원격 제어, 시청 완료 표시, 즐겨찾기, 재생 목록에 추가. 라이브러리를 삭제, 스캔, 편집하지는 않습니다.", + "off": "읽기 전용", + "propose": "먼저 묻기", + "auto": "묻지 않고 실행", + "line": { + "off": "아무것도 바꿀 수 없습니다.", + "propose": "변경마다 내 승인을 기다립니다.", + "auto": "내 채팅은 바로 실행합니다." + }, + "note": { + "off": "쓰기 접근이 꺼져 있습니다. 이 연결은 읽기만 합니다.", + "propose": "변경마다 내가 확인할 때까지 승인 대기열에서 기다립니다. 카드에 영향을 받는 플레이어와 사용자가 표시됩니다.", + "auto": "내 채팅은 묻지 않고 바로 실행합니다. 다른 사람이 보고 있는 것을 일시 중지하거나 중지할 수 있습니다. 내가 없는 상태에서 실행되는 작업은 계속 내 확인을 기다립니다." + }, + "row": { + "off": "읽기 전용", + "propose": "먼저 묻기", + "auto": "묻지 않고 실행" + } + }, + "approval": { + "command": { + "play": "플레이어에서 항목 재생", + "pause": "플레이어 일시 중지", + "resume": "플레이어 재개", + "stop": "플레이어 중지", + "seek": "위치로 이동", + "set-volume": "플레이어 볼륨 설정" + }, + "action": { + "mark-played": "시청 완료 표시 변경", + "set-favorite": "즐겨찾기 변경", + "add-to-playlist": "재생 목록에 추가" + }, + "player": "플레이어", + "ofUser": "{{user}}의 세션", + "nowPlaying": "지금 재생 중", + "at": "{{time}}", + "item": "항목", + "seekTo": "이동할 위치", + "startAt": "시작 위치", + "volume": "볼륨", + "change": "변경", + "markPlayed": "시청 완료로 표시", + "markUnplayed": "시청 안 함으로 표시", + "addFavorite": "즐겨찾기에 추가", + "removeFavorite": "즐겨찾기에서 제거", + "playlist": "재생 목록", + "items_one": "항목 {{count}}개", + "items_other": "항목 {{count}}개", + "more": "외 {{count}}개", + "forUser": "대상 사용자", + "as": "사용할 연결", + "asOne": "연결 {{name}}", + "asFirst": "{{server}}을(를) 나열한 첫 번째 연결", + "asUnknown": "{{server}}용 Jellyfin 연결", + "others": "다른 사람이 이 플레이어를 보고 있을 수 있습니다. 이 동작은 그들을 방해합니다.", + "names": "이름은 Jellyfin 서버에서 읽어 옵니다." + } +} diff --git a/addons/jellyfin/ui/src/i18n/jellyfin.pt-BR.json b/addons/jellyfin/ui/src/i18n/jellyfin.pt-BR.json new file mode 100644 index 0000000000..e0a96156bb --- /dev/null +++ b/addons/jellyfin/ui/src/i18n/jellyfin.pt-BR.json @@ -0,0 +1,128 @@ +{ + "title": "Jellyfin", + "intro": "Deixe seus agentes pesquisarem a biblioteca do Jellyfin e verem o que está tocando. Se você permitir, eles também podem controlar players remotamente, marcar como assistido, definir favoritos e adicionar a playlists.", + "limits": "Esta conexão não responde a mensagens. Um agente só usa o Jellyfin quando você pede em um chat. Ele não pode excluir, escanear nem editar a biblioteca, e não pode gerenciar usuários.", + "new": "Nova conexão do Jellyfin", + "edit": "Editar conexão do Jellyfin", + "name": "Nome", + "token": "Chave de API", + "tokenHelp": "Criada no painel do Jellyfin. É guardada com segurança e nunca mais é mostrada.", + "tokenSaved": "Salva. Digite uma chave para substituí-la.", + "tokenSpace": "A chave não tem espaços nem aspas. Copie-a de novo sem os caracteres ao redor.", + "server": "Endereço do servidor", + "serverHelp": "O endereço com que você abre o Jellyfin, por exemplo http://192.168.1.10:8096. Uma conexão guarda um servidor; adicione outra conexão para um segundo servidor. O agente acessa este endereço e nenhum outro.", + "serverShape": "Isto não é um endereço web. Escreva como http://192.168.1.10:8096 ou https://media.example.", + "serverScheme": "O endereço começa com http:// ou https://.", + "serverUser": "Deixe o nome de usuário e a senha fora do endereço.", + "serverQuery": "Deixe de fora tudo depois de ? e #. Use o campo abaixo para a impressão digital de um certificado.", + "serverLong": "O endereço é longo demais.", + "httpNote": "Este endereço não é criptografado: a chave de API trafega em texto puro. Use-o só em uma rede em que você confia.", + "pin": "Impressão digital do certificado (só para certificado autoassinado)", + "pinHelp": "Impressão digital SHA-256 do certificado do servidor, na visualização de certificado do seu navegador. Com ela, o LocalAI aceita esse certificado e nenhum outro. Deixe vazio quando o certificado vem de uma autoridade pública.", + "pinShape": "Uma impressão digital SHA-256 tem 64 dígitos hexadecimais. Dois-pontos e espaços são aceitos.", + "pinHttp": "Uma impressão digital vai com um endereço https://.", + "pinnedMark": "(certificado fixado)", + "agent": "Agente", + "agentNone": "Escolha um agente", + "agentCredentialsOnly": "Sem agente (só credenciais)", + "agentOptionalHint": "Opcional. Sem um agente, esta conexão só guarda a chave, o servidor e o modo de escrita dos seus chats. Ela não responde a mensagens e não escuta nada.", + "credentialsOnly": "Só credenciais", + "agentNoModel": "{{name}} (precisa de um modelo)", + "test": "Testar", + "testOk": "Chave aceita.", + "checkFailed": "Salvo. A verificação da chave falhou: {{reason}}", + "checkSeveral": "A verificação do servidor roda quando você tem uma única conexão do Jellyfin, porque uma verificação feita desta página não sabe de qual você fala. Em um chat, ative as conexões no menu Ferramentas.", + "check": { + "title": "Verificação de {{name}}", + "running": "Verificando o servidor...", + "at": "Verificado às {{time}}", + "ok": "{{name}} {{version}} responde, e a chave foi aceita.", + "keyRefused": "{{name}} {{version}} responde, mas recusou a chave. Confira se a chave não foi excluída no painel.", + "refused": "O servidor recusou a chave. Confira se ela não foi excluída no painel.", + "certificate": "O certificado não é confiável. Para um certificado autoassinado, adicione a impressão digital SHA-256 dele; se você já fez isso, o servidor agora mostra outro certificado.", + "failed": "O servidor não respondeu. Confira o endereço e se o servidor está ligado." + }, + "save": "Salvar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Excluir", + "empty": "Ainda não há conexões do Jellyfin.", + "noServer": "Nenhum servidor na lista: o agente não alcança nenhum.", + "serverOf": "Servidor: {{server}} {{pinned}}", + "agentOf": "Agente: {{name}}", + "inactive": "Não está rodando: {{reason}}.", + "confirmDelete": { + "title": "Excluir {{name}}?", + "message": "O agente não alcança mais este servidor, e a chave salva com a conexão é excluída. Para configurá-la de novo, você precisará informar a chave outra vez.", + "confirm": "Excluir" + }, + "setup": { + "title": "Como criar a chave", + "step1": "Abra o painel do Jellyfin como administrador e vá em Avançado, depois Chaves de API.", + "step2": "Escolha o botão de mais, dê um nome à chave, por exemplo LocalAI, e confirme.", + "step3": "Copie a chave que aparece na lista e cole no campo da chave aqui.", + "step4": "Informe o endereço do servidor como você o abre no navegador, com a porta.", + "admin": "Uma chave de API do Jellyfin vale para o servidor inteiro, não para um usuário. O modo de escrita abaixo é o que limita o agente. Se você excluir a chave no painel, esta conexão para de funcionar na hora." + }, + "writeMode": { + "label": "O que o agente pode alterar no Jellyfin", + "hint": "Controle remoto de players, marcas de assistido, favoritos e adição a playlists. Ele nunca exclui, escaneia nem edita a biblioteca.", + "off": "Somente leitura", + "propose": "Pergunta antes", + "auto": "Age sem perguntar", + "line": { + "off": "Ele não pode alterar nada.", + "propose": "Cada alteração espera o seu OK.", + "auto": "Seus chats agem na hora." + }, + "note": { + "off": "O acesso de escrita está desligado: esta conexão só lê.", + "propose": "Cada alteração espera nas suas aprovações até você confirmar. O cartão informa o player e o usuário afetados.", + "auto": "Um chat seu age na hora, sem perguntar: ele pode pausar ou parar o que outras pessoas estão assistindo. Execuções sem você presente ainda esperam a sua confirmação." + }, + "row": { + "off": "Somente leitura", + "propose": "Pergunta antes", + "auto": "Age sem perguntar" + } + }, + "approval": { + "command": { + "play": "Reproduzir um item em um player", + "pause": "Pausar um player", + "resume": "Retomar um player", + "stop": "Parar um player", + "seek": "Ir para uma posição", + "set-volume": "Definir o volume de um player" + }, + "action": { + "mark-played": "Alterar a marca de assistido", + "set-favorite": "Alterar um favorito", + "add-to-playlist": "Adicionar a uma playlist" + }, + "player": "Player", + "ofUser": "Sessão de {{user}}", + "nowPlaying": "Tocando agora", + "at": "às {{time}}", + "item": "Item", + "seekTo": "Ir para", + "startAt": "Começar em", + "volume": "Volume", + "change": "Alteração", + "markPlayed": "Marcar como assistido", + "markUnplayed": "Marcar como não assistido", + "addFavorite": "Adicionar aos favoritos", + "removeFavorite": "Remover dos favoritos", + "playlist": "Playlist", + "items_one": "{{count}} item", + "items_other": "{{count}} itens", + "more": "e mais {{count}}", + "forUser": "Para o usuário", + "as": "Como", + "asOne": "A conexão {{name}}", + "asFirst": "A primeira conexão que lista {{server}}", + "asUnknown": "Uma conexão do Jellyfin para {{server}}", + "others": "Outras pessoas podem estar assistindo neste player. Isto as interrompe.", + "names": "Os nomes são lidos do seu servidor Jellyfin." + } +} diff --git a/addons/jellyfin/ui/src/i18n/jellyfin.zh-CN.json b/addons/jellyfin/ui/src/i18n/jellyfin.zh-CN.json new file mode 100644 index 0000000000..c9451584bf --- /dev/null +++ b/addons/jellyfin/ui/src/i18n/jellyfin.zh-CN.json @@ -0,0 +1,128 @@ +{ + "title": "Jellyfin", + "intro": "让你的智能体搜索 Jellyfin 媒体库并查看正在播放的内容。如果你允许,它们还可以遥控播放器、标记已观看、设置收藏并添加到播放列表。", + "limits": "此连接不回答消息。只有当你在聊天中要求时,智能体才会使用 Jellyfin。它不能删除、扫描或编辑媒体库,也不能管理用户。", + "new": "新建 Jellyfin 连接", + "edit": "编辑 Jellyfin 连接", + "name": "名称", + "token": "API 密钥", + "tokenHelp": "在 Jellyfin 控制台中创建。它会被加密保存,之后不再显示。", + "tokenSaved": "已保存。输入密钥可替换它。", + "tokenSpace": "密钥不含空格或引号。请重新复制,不要带周围的字符。", + "server": "服务器地址", + "serverHelp": "你打开 Jellyfin 所用的地址,例如 http://192.168.1.10:8096。一个连接对应一台服务器;需要第二台服务器时请再添加一个连接。智能体只会访问此地址。", + "serverShape": "这不是网址。请写成 http://192.168.1.10:8096 或 https://media.example 的形式。", + "serverScheme": "地址以 http:// 或 https:// 开头。", + "serverUser": "地址中不要包含用户名和密码。", + "serverQuery": "请去掉 ? 和 # 之后的所有内容。证书指纹请填写在下面的框中。", + "serverLong": "地址太长。", + "httpNote": "此地址未加密:API 密钥会以明文传输。请只在你信任的网络中使用。", + "pin": "证书指纹(仅用于自签名证书)", + "pinHelp": "服务器证书的 SHA-256 指纹,可在浏览器的证书视图中查看。填写后,LocalAI 只接受这一个证书。如果证书来自公共机构,请留空。", + "pinShape": "SHA-256 指纹是 64 位十六进制数字。可以带冒号和空格。", + "pinHttp": "指纹需要配合 https:// 地址使用。", + "pinnedMark": "(已固定证书)", + "agent": "智能体", + "agentNone": "选择智能体", + "agentCredentialsOnly": "没有智能体(仅凭据)", + "agentOptionalHint": "可选。没有智能体时,此连接只保存密钥、服务器和用于你的聊天的写入模式。它不回答任何消息,也不监听任何内容。", + "credentialsOnly": "仅凭据", + "agentNoModel": "{{name}}(需要模型)", + "test": "测试", + "testOk": "密钥已被接受。", + "checkFailed": "已保存。密钥检查失败:{{reason}}", + "checkSeveral": "服务器检查只在你有一个 Jellyfin 连接时运行,因为从此页面发起的检查无法判断你指的是哪一个。在聊天中,请通过“工具”菜单启用这些连接。", + "check": { + "title": "{{name}} 的检查结果", + "running": "正在检查服务器...", + "at": "检查时间:{{time}}", + "ok": "{{name}} {{version}} 有响应,密钥已被接受。", + "keyRefused": "{{name}} {{version}} 有响应,但拒绝了密钥。请检查密钥是否已在控制台中被删除。", + "refused": "服务器拒绝了密钥。请检查它是否已在控制台中被删除。", + "certificate": "证书不受信任。如果是自签名证书,请添加它的 SHA-256 指纹;如果已添加,说明服务器现在出示的是另一个证书。", + "failed": "服务器没有响应。请检查地址以及服务器是否已开机。" + }, + "save": "保存", + "cancel": "取消", + "editButton": "编辑", + "delete": "删除", + "empty": "还没有 Jellyfin 连接。", + "noServer": "没有列出服务器:智能体哪里也到不了。", + "serverOf": "服务器:{{server}} {{pinned}}", + "agentOf": "智能体:{{name}}", + "inactive": "未运行:{{reason}}。", + "confirmDelete": { + "title": "删除 {{name}}?", + "message": "智能体将无法再访问这台服务器,随之保存的密钥也会被删除。若要重新设置,你必须再次输入密钥。", + "confirm": "删除" + }, + "setup": { + "title": "如何创建密钥", + "step1": "以管理员身份打开 Jellyfin 控制台,进入 Advanced,再进入 API Keys。", + "step2": "点击加号按钮,为密钥命名,例如 LocalAI,然后确认。", + "step3": "复制列表中出现的密钥,并粘贴到这里的密钥字段。", + "step4": "输入你在浏览器中打开服务器所用的地址,包含端口。", + "admin": "Jellyfin 的 API 密钥作用于整台服务器,而不是某一个用户。限制智能体的是下面的写入模式。如果你在控制台中删除密钥,此连接会立即停止工作。" + }, + "writeMode": { + "label": "智能体可以在 Jellyfin 上更改什么", + "hint": "遥控播放器、已观看标记、收藏和添加到播放列表。它绝不会删除、扫描或编辑媒体库。", + "off": "只读", + "propose": "先问你", + "auto": "不经询问直接执行", + "line": { + "off": "它不能更改任何内容。", + "propose": "每项更改都等待你的确认。", + "auto": "你的聊天会立即执行。" + }, + "note": { + "off": "写入权限已关闭:此连接只读取。", + "propose": "每项更改都在你的审批中等待,直到你确认。卡片会写明受影响的播放器和用户。", + "auto": "你的聊天会立即执行,不会询问:它可以暂停或停止其他人正在观看的内容。你不在场时的运行仍然要等待你的确认。" + }, + "row": { + "off": "只读", + "propose": "先问你", + "auto": "不经询问直接执行" + } + }, + "approval": { + "command": { + "play": "在播放器上播放一个项目", + "pause": "暂停播放器", + "resume": "继续播放", + "stop": "停止播放器", + "seek": "跳到某个位置", + "set-volume": "设置播放器音量" + }, + "action": { + "mark-played": "更改已观看标记", + "set-favorite": "更改收藏", + "add-to-playlist": "添加到播放列表" + }, + "player": "播放器", + "ofUser": "{{user}} 的会话", + "nowPlaying": "正在播放", + "at": "位于 {{time}}", + "item": "项目", + "seekTo": "跳到", + "startAt": "从此处开始", + "volume": "音量", + "change": "更改", + "markPlayed": "标记为已观看", + "markUnplayed": "标记为未观看", + "addFavorite": "添加到收藏", + "removeFavorite": "从收藏中移除", + "playlist": "播放列表", + "items_one": "{{count}} 个项目", + "items_other": "{{count}} 个项目", + "more": "另有 {{count}} 个", + "forUser": "针对用户", + "as": "身份", + "asOne": "连接 {{name}}", + "asFirst": "第一个列出 {{server}} 的连接", + "asUnknown": "用于 {{server}} 的 Jellyfin 连接", + "others": "其他人可能正在观看此播放器。这会打断他们。", + "names": "名称读取自你的 Jellyfin 服务器。" + } +} diff --git a/addons/slack/ui/src/addon.jsx b/addons/slack/ui/src/addon.jsx index ede0b0e60a..b7e017b257 100644 --- a/addons/slack/ui/src/addon.jsx +++ b/addons/slack/ui/src/addon.jsx @@ -5,9 +5,18 @@ import { registerLocales } from '../../../internal/host-compat/src/i18n.js' import ConnectorPage from '../../../internal/connector-ui/src/ConnectorPage' import { Field, FieldError, TextInput } from '../../../internal/connector-ui/src/controls' import en from './i18n/slack.en.json' +import it from './i18n/slack.it.json' +import es from './i18n/slack.es.json' +import de from './i18n/slack.de.json' +import fr from './i18n/slack.fr.json' +import zh from './i18n/slack.zh-CN.json' +import id from './i18n/slack.id.json' +import ko from './i18n/slack.ko.json' +import pt from './i18n/slack.pt-BR.json' import { APP_MANIFEST, SLACK_LISTS, slackSecret, tokenProblem } from './tokens' -registerLocales({ slack: { en } }) +// The words of the page, in every language of the web UI; a key a language lacks reads in English. +registerLocales({ slack: { en, it, es, de, fr, 'zh-CN': zh, id, ko, 'pt-BR': pt } }) // The two tokens of a Slack app. They are typed only to replace them. function SlackTokens({ form, setForm, t }) { diff --git a/addons/slack/ui/src/i18n/slack.de.json b/addons/slack/ui/src/i18n/slack.de.json new file mode 100644 index 0000000000..a9e520e95a --- /dev/null +++ b/addons/slack/ui/src/i18n/slack.de.json @@ -0,0 +1,69 @@ +{ + "title": "Slack", + "intro": "Verbinde eine Slack-App mit einem deiner gespeicherten Agenten. Personen sprechen mit dem Agenten in einer Direktnachricht an die App oder indem sie ihn in einem Kanal erwähnen: Die Antwort kommt in einen Thread, und jeder Thread ist eine eigene Unterhaltung.", + "new": "Neue Slack-App", + "edit": "Slack-App bearbeiten", + "name": "Name", + "botToken": "Bot-Token", + "botTokenHelp": "Beginnt mit xoxb-. Aus OAuth & Permissions deiner App. Es wird versiegelt gespeichert und nie wieder angezeigt.", + "appToken": "App-Level-Token", + "appTokenHelp": "Beginnt mit xapp-, mit dem Scope connections:write. Aus Basic Information, App-Level Tokens. LocalAI nutzt es für den Socket Mode, daher braucht es keine öffentliche Adresse.", + "tokenSaved": "Gespeichert. Gib beide Token ein, um sie zu ersetzen.", + "tokensBoth": "Gib beide Token ein: das Bot-Token und das App-Level-Token.", + "botShape": "Das Bot-Token beginnt mit xoxb-.", + "appShape": "Das App-Level-Token beginnt mit xapp-.", + "agent": "Agent", + "agentNone": "Agent wählen", + "agentNoModel": "{{name}} (braucht ein Modell)", + "who": "Wer sie nutzen darf", + "onlyListed": "Nur die Personen und Kanäle, die ich aufführe", + "anyone": "Alle im Workspace, die der App schreiben können", + "anyoneWarning": "Wer dieser App schreiben oder sie erwähnen kann, kann die Werkzeuge dieses Agenten nutzen, als wärst du es. Jede Person und jeder Thread bekommt eine eigene Unterhaltung.", + "anyoneConfirm": "Verstanden", + "allowUsers": "Erlaubte Personen", + "allowUsersHint": "Nutzer-ID (U...) oder @name", + "allowChannels": "Erlaubte Kanäle", + "allowChannelsHint": "Kanal-ID (C...): Alle darin dürfen die App erwähnen", + "notifyChat": "Meine Benachrichtigungen an diese Nutzer- oder Kanal-ID senden", + "notifyHelp": "Optional. Mit deiner Nutzer-ID (U...) kommen sie als Direktnachrichten von der App.", + "test": "Test", + "testOk": "Mit {{bot}} verbunden.", + "checkFailed": "Gespeichert. Die Prüfung des Tokens ist fehlgeschlagen: {{reason}}", + "senders": "Absender", + "noSenders": "Noch hat niemand dieser App geschrieben.", + "allowButton": "Erlauben", + "ignored": "Ignoriert", + "accepted": "Akzeptiert", + "save": "Speichern", + "cancel": "Abbrechen", + "editButton": "Bearbeiten", + "delete": "Löschen", + "empty": "Noch keine Slack-Apps.", + "agentOf": "Agent: {{name}}", + "accepts": "Akzeptiert: {{who}}", + "modes": { + "anyone": "alle", + "nobody": "noch niemand", + "listed_one": "1 aufgeführter Eintrag", + "listed_other": "{{count}} aufgeführte Einträge" + }, + "on": "An", + "onFor": "{{name}} an", + "inactive": "Läuft nicht: {{reason}}.", + "confirmDelete": { + "title": "{{name}} löschen?", + "message": "Die App antwortet nicht mehr, und die mit ihr gespeicherten Token werden gelöscht. Um sie erneut einzurichten, musst du die Token noch einmal eingeben.", + "confirm": "Löschen" + }, + "setup": { + "title": "So erstellst du die Slack-App", + "step1": "Wähle unter api.slack.com/apps Create New App, From an app manifest, such deinen Workspace aus und füge das Manifest unten ein.", + "step2": "Installiere die App im Workspace und kopiere das Bot-Token (xoxb-) aus OAuth & Permissions.", + "step3": "Erstelle unter Basic Information, App-Level Tokens ein Token mit dem Scope connections:write und kopiere es (xapp-).", + "step4": "Lege hier eine Verbindung mit beiden Token an. Um die App in einem Kanal zu nutzen, lade sie dort ein (/invite @LocalAI).", + "copy": "Manifest kopieren", + "copied": "Kopiert", + "manifest": "Slack-App-Manifest" + }, + "back": "Zurück zu den Konnektoren" +} diff --git a/addons/slack/ui/src/i18n/slack.es.json b/addons/slack/ui/src/i18n/slack.es.json new file mode 100644 index 0000000000..8dff33ab4d --- /dev/null +++ b/addons/slack/ui/src/i18n/slack.es.json @@ -0,0 +1,69 @@ +{ + "title": "Slack", + "intro": "Conecta una aplicación de Slack a uno de tus agentes guardados. Las personas hablan con el agente en un mensaje directo con la aplicación, o mencionándola en un canal: la respuesta va en un hilo, y cada hilo es una conversación propia.", + "new": "Nueva aplicación de Slack", + "edit": "Editar aplicación de Slack", + "name": "Nombre", + "botToken": "Token del bot", + "botTokenHelp": "Empieza por xoxb-. Está en OAuth & Permissions de tu aplicación. Se guarda cifrado y no se vuelve a mostrar.", + "appToken": "Token de nivel de aplicación", + "appTokenHelp": "Empieza por xapp-, con el ámbito connections:write. Está en Basic Information, App-Level Tokens. LocalAI lo usa para Socket Mode, así que no necesita dirección pública.", + "tokenSaved": "Guardado. Escribe los dos tokens para reemplazarlos.", + "tokensBoth": "Escribe los dos tokens: el token del bot y el token de nivel de aplicación.", + "botShape": "El token del bot empieza por xoxb-.", + "appShape": "El token de nivel de aplicación empieza por xapp-.", + "agent": "Agente", + "agentNone": "Elige un agente", + "agentNoModel": "{{name}} (necesita un modelo)", + "who": "Quién puede usarla", + "onlyListed": "Solo las personas y canales que yo añada a la lista", + "anyone": "Cualquiera del workspace que pueda escribir a la aplicación", + "anyoneWarning": "Cualquiera que pueda escribir a esta aplicación o mencionarla puede usar las herramientas de este agente, como si fueras tú. Cada persona, y cada hilo, tiene su propia conversación.", + "anyoneConfirm": "Lo entiendo", + "allowUsers": "Personas permitidas", + "allowUsersHint": "ID de usuario (U...) o @nombre", + "allowChannels": "Canales permitidos", + "allowChannelsHint": "ID de canal (C...): todos los que estén en él pueden mencionar la aplicación", + "notifyChat": "Enviar mis notificaciones a este ID de usuario o de canal", + "notifyHelp": "Opcional. Tu ID de usuario (U...) las envía como mensajes directos desde la aplicación.", + "test": "Probar", + "testOk": "Conectado a {{bot}}.", + "checkFailed": "Guardado. Falló la comprobación del token: {{reason}}", + "senders": "Remitentes", + "noSenders": "Nadie ha escrito aún a esta aplicación.", + "allowButton": "Permitir", + "ignored": "Ignorado", + "accepted": "Aceptado", + "save": "Guardar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Eliminar", + "empty": "Aún no hay aplicaciones de Slack.", + "agentOf": "Agente: {{name}}", + "accepts": "Acepta: {{who}}", + "modes": { + "anyone": "cualquiera", + "nobody": "nadie por ahora", + "listed_one": "1 entrada en la lista", + "listed_other": "{{count}} entradas en la lista" + }, + "on": "Activada", + "onFor": "{{name}} activada", + "inactive": "No se está ejecutando: {{reason}}.", + "confirmDelete": { + "title": "¿Eliminar {{name}}?", + "message": "La aplicación deja de responder y se eliminan los tokens guardados con ella. Para configurarla de nuevo tendrás que volver a introducir los tokens.", + "confirm": "Eliminar" + }, + "setup": { + "title": "Cómo crear la aplicación de Slack", + "step1": "En api.slack.com/apps elige Create New App, From an app manifest, selecciona tu workspace y pega el manifiesto de abajo.", + "step2": "Instala la aplicación en el workspace y copia el token del bot (xoxb-) de OAuth & Permissions.", + "step3": "En Basic Information, App-Level Tokens, crea un token con el ámbito connections:write y cópialo (xapp-).", + "step4": "Crea aquí una vinculación con los dos tokens. Para usar la aplicación en un canal, invítala a él (/invite @LocalAI).", + "copy": "Copiar el manifiesto", + "copied": "Copiado", + "manifest": "Manifiesto de la aplicación de Slack" + }, + "back": "Volver a los conectores" +} diff --git a/addons/slack/ui/src/i18n/slack.fr.json b/addons/slack/ui/src/i18n/slack.fr.json new file mode 100644 index 0000000000..51d3c72080 --- /dev/null +++ b/addons/slack/ui/src/i18n/slack.fr.json @@ -0,0 +1,69 @@ +{ + "title": "Slack", + "intro": "Associez une app Slack à l’un de vos agents enregistrés. On parle à l’agent en message privé avec l’app, ou en la mentionnant dans un canal : la réponse arrive dans un fil, et chaque fil est une conversation à part.", + "new": "Nouvelle app Slack", + "edit": "Modifier l’app Slack", + "name": "Nom", + "botToken": "Jeton du bot", + "botTokenHelp": "Commence par xoxb-. Se trouve dans OAuth & Permissions de votre app. Il est conservé scellé et n’est plus jamais affiché.", + "appToken": "Jeton de niveau app", + "appTokenHelp": "Commence par xapp-, avec la portée connections:write. Se trouve dans Basic Information, App-Level Tokens. LocalAI l’utilise pour Socket Mode, aucune adresse publique n’est donc nécessaire.", + "tokenSaved": "Enregistré. Saisissez les deux jetons pour les remplacer.", + "tokensBoth": "Saisissez les deux jetons : le jeton du bot et le jeton de niveau app.", + "botShape": "Le jeton du bot commence par xoxb-.", + "appShape": "Le jeton de niveau app commence par xapp-.", + "agent": "Agent", + "agentNone": "Choisir un agent", + "agentNoModel": "{{name}} (a besoin d’un modèle)", + "who": "Qui peut l’utiliser", + "onlyListed": "Seulement les personnes et canaux que j’indique", + "anyone": "Toute personne de l’espace de travail pouvant écrire à l’app", + "anyoneWarning": "Toute personne pouvant écrire à cette app ou la mentionner peut utiliser les outils de cet agent, en votre nom. Chaque personne, et chaque fil, a sa propre conversation.", + "anyoneConfirm": "J’ai compris", + "allowUsers": "Personnes autorisées", + "allowUsersHint": "ID d’utilisateur (U...) ou @nom", + "allowChannels": "Canaux autorisés", + "allowChannelsHint": "ID de canal (C...) : tous ses membres peuvent mentionner l’app", + "notifyChat": "Envoyer mes notifications à cet ID d’utilisateur ou de canal", + "notifyHelp": "Facultatif. Votre ID d’utilisateur (U...) les envoie en messages privés depuis l’app.", + "test": "Tester", + "testOk": "Connecté à {{bot}}.", + "checkFailed": "Enregistré. La vérification du jeton a échoué : {{reason}}", + "senders": "Expéditeurs", + "noSenders": "Personne n’a encore écrit à cette app.", + "allowButton": "Autoriser", + "ignored": "Ignoré", + "accepted": "Accepté", + "save": "Enregistrer", + "cancel": "Annuler", + "editButton": "Modifier", + "delete": "Supprimer", + "empty": "Aucune app Slack pour le moment.", + "agentOf": "Agent : {{name}}", + "accepts": "Accepte : {{who}}", + "modes": { + "anyone": "tout le monde", + "nobody": "personne pour l’instant", + "listed_one": "1 entrée dans la liste", + "listed_other": "{{count}} entrées dans la liste" + }, + "on": "Activé", + "onFor": "{{name}} activé", + "inactive": "À l’arrêt : {{reason}}.", + "confirmDelete": { + "title": "Supprimer {{name}} ?", + "message": "L’app cesse de répondre, et les jetons enregistrés avec elle sont supprimés. Pour la reconfigurer, vous devrez saisir de nouveau les jetons.", + "confirm": "Supprimer" + }, + "setup": { + "title": "Comment créer l’app Slack", + "step1": "Sur api.slack.com/apps, choisissez Create New App, From an app manifest, sélectionnez votre espace de travail et collez le manifeste ci-dessous.", + "step2": "Installez l’app dans l’espace de travail, puis copiez le jeton du bot (xoxb-) depuis OAuth & Permissions.", + "step3": "Dans Basic Information, App-Level Tokens, créez un jeton avec la portée connections:write et copiez-le (xapp-).", + "step4": "Créez ici une association avec les deux jetons. Pour utiliser l’app dans un canal, invitez-la-y (/invite @LocalAI).", + "copy": "Copier le manifeste", + "copied": "Copié", + "manifest": "Manifeste de l’app Slack" + }, + "back": "Retour aux connecteurs" +} diff --git a/addons/slack/ui/src/i18n/slack.id.json b/addons/slack/ui/src/i18n/slack.id.json new file mode 100644 index 0000000000..c3c7f20581 --- /dev/null +++ b/addons/slack/ui/src/i18n/slack.id.json @@ -0,0 +1,69 @@ +{ + "title": "Slack", + "intro": "Hubungkan aplikasi Slack ke salah satu agen tersimpan Anda. Orang berbicara dengan agen lewat pesan langsung ke aplikasi, atau dengan menyebutnya di kanal: jawaban masuk ke thread, dan setiap thread adalah percakapan tersendiri.", + "new": "Aplikasi Slack baru", + "edit": "Ubah aplikasi Slack", + "name": "Nama", + "botToken": "Token bot", + "botTokenHelp": "Diawali xoxb-. Dari OAuth & Permissions pada aplikasi Anda. Disimpan terenkripsi dan tidak pernah ditampilkan lagi.", + "appToken": "Token tingkat aplikasi", + "appTokenHelp": "Diawali xapp-, dengan scope connections:write. Dari Basic Information, App-Level Tokens. LocalAI memakainya untuk Socket Mode, jadi tidak memerlukan alamat publik.", + "tokenSaved": "Tersimpan. Ketik kedua token untuk menggantinya.", + "tokensBoth": "Ketik kedua token: token bot dan token tingkat aplikasi.", + "botShape": "Token bot diawali xoxb-.", + "appShape": "Token tingkat aplikasi diawali xapp-.", + "agent": "Agen", + "agentNone": "Pilih agen", + "agentNoModel": "{{name}} (perlu model)", + "who": "Siapa yang boleh memakai", + "onlyListed": "Hanya orang dan kanal yang saya daftarkan", + "anyone": "Siapa saja di workspace yang dapat mengirim pesan ke aplikasi", + "anyoneWarning": "Siapa saja yang dapat mengirim pesan ke aplikasi ini atau menyebutnya dapat memakai alat agen ini, sebagai Anda. Setiap orang, dan setiap thread, mendapat percakapan tersendiri.", + "anyoneConfirm": "Saya mengerti", + "allowUsers": "Orang yang diizinkan", + "allowUsersHint": "ID pengguna (U...) atau @nama", + "allowChannels": "Kanal yang diizinkan", + "allowChannelsHint": "ID kanal (C...): semua orang di dalamnya boleh menyebut aplikasi", + "notifyChat": "Kirim notifikasi saya ke ID pengguna atau kanal ini", + "notifyHelp": "Opsional. ID pengguna Anda (U...) mengirimnya sebagai pesan langsung dari aplikasi.", + "test": "Uji", + "testOk": "Terhubung ke {{bot}}.", + "checkFailed": "Tersimpan. Pemeriksaan token gagal: {{reason}}", + "senders": "Pengirim", + "noSenders": "Belum ada yang menulis ke aplikasi ini.", + "allowButton": "Izinkan", + "ignored": "Diabaikan", + "accepted": "Diterima", + "save": "Simpan", + "cancel": "Batal", + "editButton": "Ubah", + "delete": "Hapus", + "empty": "Belum ada aplikasi Slack.", + "agentOf": "Agen: {{name}}", + "accepts": "Menerima: {{who}}", + "modes": { + "anyone": "siapa saja", + "nobody": "belum ada", + "listed_one": "1 entri terdaftar", + "listed_other": "{{count}} entri terdaftar" + }, + "on": "Aktif", + "onFor": "{{name}} aktif", + "inactive": "Tidak berjalan: {{reason}}.", + "confirmDelete": { + "title": "Hapus {{name}}?", + "message": "Aplikasi berhenti menjawab, dan token yang tersimpan bersamanya dihapus. Untuk menyiapkannya lagi, Anda harus memasukkan token lagi.", + "confirm": "Hapus" + }, + "setup": { + "title": "Cara membuat aplikasi Slack", + "step1": "Di api.slack.com/apps pilih Create New App, From an app manifest, pilih workspace Anda, lalu tempel manifest di bawah.", + "step2": "Pasang aplikasi ke workspace, lalu salin token bot (xoxb-) dari OAuth & Permissions.", + "step3": "Di Basic Information, App-Level Tokens, buat token dengan scope connections:write lalu salin (xapp-).", + "step4": "Buat koneksi di sini dengan kedua token. Untuk memakai aplikasi di kanal, undang aplikasi ke sana (/invite @LocalAI).", + "copy": "Salin manifest", + "copied": "Tersalin", + "manifest": "Manifest aplikasi Slack" + }, + "back": "Kembali ke konektor" +} diff --git a/addons/slack/ui/src/i18n/slack.it.json b/addons/slack/ui/src/i18n/slack.it.json new file mode 100644 index 0000000000..ef4d6b1dd6 --- /dev/null +++ b/addons/slack/ui/src/i18n/slack.it.json @@ -0,0 +1,69 @@ +{ + "title": "Slack", + "intro": "Collega un'app Slack a uno dei tuoi agenti salvati. Le persone parlano con l'agente in un messaggio diretto con l'app, oppure menzionandola in un canale: la risposta va in un thread, e ogni thread è una conversazione a sé.", + "new": "Nuova app Slack", + "edit": "Modifica l'app Slack", + "name": "Nome", + "botToken": "Token del bot", + "botTokenHelp": "Inizia con xoxb-. Lo trovi in OAuth & Permissions della tua app. Viene cifrato e non viene più mostrato.", + "appToken": "Token a livello di app", + "appTokenHelp": "Inizia con xapp-, con lo scope connections:write. Lo trovi in Basic Information, App-Level Tokens. LocalAI lo usa per il Socket Mode, quindi non serve un indirizzo pubblico.", + "tokenSaved": "Salvato. Scrivi i token per sostituirli.", + "tokensBoth": "Scrivi entrambi i token: quello del bot e quello a livello di app.", + "botShape": "Il token del bot inizia con xoxb-.", + "appShape": "Il token a livello di app inizia con xapp-.", + "agent": "Agente", + "agentNone": "Scegli un agente", + "agentNoModel": "{{name}} (serve un modello)", + "who": "Chi può usarla", + "onlyListed": "Solo le persone e i canali che elenco", + "anyone": "Chiunque nel workspace possa scrivere all'app", + "anyoneWarning": "Chiunque possa scrivere a questa app o menzionarla può usare gli strumenti di questo agente, come te. Ogni persona, e ogni thread, ha una conversazione a sé.", + "anyoneConfirm": "Ho capito", + "allowUsers": "Persone consentite", + "allowUsersHint": "ID utente (U...) o @nome", + "allowChannels": "Canali consentiti", + "allowChannelsHint": "ID del canale (C...): chiunque ne faccia parte può menzionare l'app", + "notifyChat": "Invia le mie notifiche a questo ID di utente o canale", + "notifyHelp": "Facoltativo. Con il tuo ID utente (U...) le notifiche arrivano come messaggi diretti dall'app.", + "test": "Prova", + "testOk": "Connesso a {{bot}}.", + "checkFailed": "Salvato. La verifica del token non è riuscita: {{reason}}", + "senders": "Mittenti", + "noSenders": "Nessuno ha ancora scritto a questa app.", + "allowButton": "Consenti", + "ignored": "Ignorato", + "accepted": "Accettato", + "save": "Salva", + "cancel": "Annulla", + "editButton": "Modifica", + "delete": "Elimina", + "empty": "Ancora nessuna app Slack.", + "agentOf": "Agente: {{name}}", + "accepts": "Accetta: {{who}}", + "modes": { + "anyone": "chiunque", + "nobody": "nessuno per ora", + "listed_one": "1 voce in elenco", + "listed_other": "{{count}} voci in elenco" + }, + "on": "Attiva", + "onFor": "{{name}} attiva", + "inactive": "Non in esecuzione: {{reason}}.", + "confirmDelete": { + "title": "Eliminare {{name}}?", + "message": "L'app smette di rispondere e i token salvati con lei vengono eliminati. Per configurarla di nuovo dovrai inserire di nuovo i token.", + "confirm": "Elimina" + }, + "setup": { + "title": "Come creare l'app Slack", + "step1": "Su api.slack.com/apps scegli Create New App, poi From an app manifest, seleziona il tuo workspace e incolla il manifest qui sotto.", + "step2": "Installa l'app nel workspace e copia il token del bot (xoxb-) da OAuth & Permissions.", + "step3": "In Basic Information, App-Level Tokens, crea un token con lo scope connections:write e copialo (xapp-).", + "step4": "Crea qui un collegamento con entrambi i token. Per usare l'app in un canale, invitala lì (/invite @LocalAI).", + "copy": "Copia il manifest", + "copied": "Copiato", + "manifest": "Manifest dell'app Slack" + }, + "back": "Torna ai connettori" +} diff --git a/addons/slack/ui/src/i18n/slack.ko.json b/addons/slack/ui/src/i18n/slack.ko.json new file mode 100644 index 0000000000..62a50dfd83 --- /dev/null +++ b/addons/slack/ui/src/i18n/slack.ko.json @@ -0,0 +1,69 @@ +{ + "title": "Slack", + "intro": "Slack 앱을 저장된 에이전트 중 하나에 연결하세요. 사람들은 앱과의 다이렉트 메시지로 대화하거나 채널에서 앱을 멘션해 에이전트와 대화합니다. 답변은 스레드로 달리며, 스레드마다 별개의 대화가 됩니다.", + "new": "새 Slack 앱", + "edit": "Slack 앱 편집", + "name": "이름", + "botToken": "봇 토큰", + "botTokenHelp": "xoxb-로 시작합니다. 앱의 OAuth & Permissions에서 받습니다. 봉인되어 보관되며 다시 표시되지 않습니다.", + "appToken": "앱 수준 토큰", + "appTokenHelp": "xapp-로 시작하며 connections:write 범위가 있어야 합니다. Basic Information의 App-Level Tokens에서 받습니다. LocalAI가 Socket Mode에 사용하므로 공개 주소가 필요 없습니다.", + "tokenSaved": "저장됨. 교체하려면 토큰 두 개를 모두 입력하세요.", + "tokensBoth": "토큰 두 개를 모두 입력하세요: 봇 토큰과 앱 수준 토큰.", + "botShape": "봇 토큰은 xoxb-로 시작합니다.", + "appShape": "앱 수준 토큰은 xapp-로 시작합니다.", + "agent": "에이전트", + "agentNone": "에이전트 고르기", + "agentNoModel": "{{name}}(모델 필요)", + "who": "사용할 수 있는 사람", + "onlyListed": "내가 나열한 사람과 채널만", + "anyone": "앱에 메시지를 보낼 수 있는 워크스페이스의 모든 사람", + "anyoneWarning": "이 앱에 메시지를 보내거나 멘션할 수 있는 사람은 누구나 이 에이전트의 도구를 내 권한으로 사용할 수 있습니다. 사람마다, 스레드마다 별개의 대화가 생깁니다.", + "anyoneConfirm": "이해했습니다", + "allowUsers": "허용된 사람", + "allowUsersHint": "사용자 ID(U...) 또는 @name", + "allowChannels": "허용된 채널", + "allowChannelsHint": "채널 ID(C...): 채널의 모든 사람이 앱을 멘션할 수 있습니다", + "notifyChat": "내 알림을 이 사용자 또는 채널 ID로 보내기", + "notifyHelp": "선택 사항입니다. 내 사용자 ID(U...)를 입력하면 앱이 다이렉트 메시지로 보냅니다.", + "test": "테스트", + "testOk": "{{bot}}에 연결되었습니다.", + "checkFailed": "저장했습니다. 토큰 확인에 실패했습니다: {{reason}}", + "senders": "보낸 사람", + "noSenders": "아직 이 앱에 메시지를 보낸 사람이 없습니다.", + "allowButton": "허용", + "ignored": "무시됨", + "accepted": "수락됨", + "save": "저장", + "cancel": "취소", + "editButton": "편집", + "delete": "삭제", + "empty": "아직 Slack 앱이 없습니다.", + "agentOf": "에이전트: {{name}}", + "accepts": "수락: {{who}}", + "modes": { + "anyone": "모든 사람", + "nobody": "아직 없음", + "listed_one": "나열된 항목 1개", + "listed_other": "나열된 항목 {{count}}개" + }, + "on": "켜짐", + "onFor": "{{name}} 켜짐", + "inactive": "실행 중이 아님: {{reason}}.", + "confirmDelete": { + "title": "{{name}}을(를) 삭제할까요?", + "message": "앱이 답변을 멈추고 함께 저장된 토큰이 삭제됩니다. 다시 설정하려면 토큰을 다시 입력해야 합니다.", + "confirm": "삭제" + }, + "setup": { + "title": "Slack 앱 만드는 방법", + "step1": "api.slack.com/apps에서 Create New App을 고르고 From an app manifest를 선택한 다음, 워크스페이스를 고르고 아래 매니페스트를 붙여 넣으세요.", + "step2": "앱을 워크스페이스에 설치하고 OAuth & Permissions에서 봇 토큰(xoxb-)을 복사하세요.", + "step3": "Basic Information의 App-Level Tokens에서 connections:write 범위로 토큰을 만들고 복사하세요(xapp-).", + "step4": "두 토큰으로 여기서 바인딩을 만드세요. 채널에서 앱을 쓰려면 그곳에 초대하세요(/invite @LocalAI).", + "copy": "매니페스트 복사", + "copied": "복사됨", + "manifest": "Slack 앱 매니페스트" + }, + "back": "커넥터로 돌아가기" +} diff --git a/addons/slack/ui/src/i18n/slack.pt-BR.json b/addons/slack/ui/src/i18n/slack.pt-BR.json new file mode 100644 index 0000000000..19f2f8a20d --- /dev/null +++ b/addons/slack/ui/src/i18n/slack.pt-BR.json @@ -0,0 +1,69 @@ +{ + "title": "Slack", + "intro": "Vincule um app do Slack a um dos seus agentes salvos. As pessoas conversam com o agente em uma mensagem direta com o app, ou mencionando-o em um canal: a resposta vai em uma thread, e cada thread é uma conversa própria.", + "new": "Novo app do Slack", + "edit": "Editar app do Slack", + "name": "Nome", + "botToken": "Token do bot", + "botTokenHelp": "Começa com xoxb-. Vem de OAuth & Permissions do seu app. É guardado com segurança e nunca mais é mostrado.", + "appToken": "Token de nível de app", + "appTokenHelp": "Começa com xapp-, com o escopo connections:write. Vem de Basic Information, App-Level Tokens. O LocalAI o usa para o Socket Mode, então não precisa de endereço público.", + "tokenSaved": "Salvo. Digite os dois tokens para substituí-los.", + "tokensBoth": "Digite os dois tokens: o token do bot e o token de nível de app.", + "botShape": "O token do bot começa com xoxb-.", + "appShape": "O token de nível de app começa com xapp-.", + "agent": "Agente", + "agentNone": "Escolha um agente", + "agentNoModel": "{{name}} (precisa de um modelo)", + "who": "Quem pode usar", + "onlyListed": "Só as pessoas e os canais que eu listar", + "anyone": "Qualquer pessoa do workspace que possa enviar mensagem ao app", + "anyoneWarning": "Qualquer pessoa que possa enviar mensagem a este app ou mencioná-lo pode usar as ferramentas deste agente, como se fosse você. Cada pessoa, e cada thread, tem uma conversa própria.", + "anyoneConfirm": "Entendi", + "allowUsers": "Pessoas permitidas", + "allowUsersHint": "ID do usuário (U...) ou @nome", + "allowChannels": "Canais permitidos", + "allowChannelsHint": "ID do canal (C...): todos nele podem mencionar o app", + "notifyChat": "Enviar minhas notificações para este ID de usuário ou canal", + "notifyHelp": "Opcional. Seu ID de usuário (U...) as envia como mensagens diretas do app.", + "test": "Testar", + "testOk": "Conectado a {{bot}}.", + "checkFailed": "Salvo. A verificação do token falhou: {{reason}}", + "senders": "Remetentes", + "noSenders": "Ninguém escreveu para este app ainda.", + "allowButton": "Permitir", + "ignored": "Ignorado", + "accepted": "Aceito", + "save": "Salvar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Excluir", + "empty": "Ainda não há apps do Slack.", + "agentOf": "Agente: {{name}}", + "accepts": "Aceita: {{who}}", + "modes": { + "anyone": "qualquer pessoa", + "nobody": "ninguém ainda", + "listed_one": "1 entrada na lista", + "listed_other": "{{count}} entradas na lista" + }, + "on": "Ligado", + "onFor": "{{name}} ligado", + "inactive": "Não está rodando: {{reason}}.", + "confirmDelete": { + "title": "Excluir {{name}}?", + "message": "O app deixa de responder, e os tokens salvos com ele são excluídos. Para configurá-lo de novo, você precisará informar os tokens outra vez.", + "confirm": "Excluir" + }, + "setup": { + "title": "Como criar o app do Slack", + "step1": "Em api.slack.com/apps escolha Create New App, From an app manifest, selecione seu workspace e cole o manifesto abaixo.", + "step2": "Instale o app no workspace e copie o token do bot (xoxb-) em OAuth & Permissions.", + "step3": "Em Basic Information, App-Level Tokens, crie um token com o escopo connections:write e copie-o (xapp-).", + "step4": "Crie uma vinculação aqui com os dois tokens. Para usar o app em um canal, convide-o para lá (/invite @LocalAI).", + "copy": "Copiar o manifesto", + "copied": "Copiado", + "manifest": "Manifesto do app do Slack" + }, + "back": "Voltar aos conectores" +} diff --git a/addons/slack/ui/src/i18n/slack.zh-CN.json b/addons/slack/ui/src/i18n/slack.zh-CN.json new file mode 100644 index 0000000000..7b1c34931d --- /dev/null +++ b/addons/slack/ui/src/i18n/slack.zh-CN.json @@ -0,0 +1,69 @@ +{ + "title": "Slack", + "intro": "把 Slack 应用绑定到你已保存的某个智能体。人们可以在与应用的私信中与智能体交谈,也可以在频道中提及它:回答会发在讨论串中,每个讨论串是一个独立的对话。", + "new": "新建 Slack 应用", + "edit": "编辑 Slack 应用", + "name": "名称", + "botToken": "机器人 token", + "botTokenHelp": "以 xoxb- 开头。来自应用的 OAuth & Permissions。它会被加密保存,之后不再显示。", + "appToken": "应用级 token", + "appTokenHelp": "以 xapp- 开头,作用域为 connections:write。来自 Basic Information 中的 App-Level Tokens。LocalAI 用它连接 Socket Mode,因此不需要公网地址。", + "tokenSaved": "已保存。输入两个 token 可替换它们。", + "tokensBoth": "请输入两个 token:机器人 token 和应用级 token。", + "botShape": "机器人 token 以 xoxb- 开头。", + "appShape": "应用级 token 以 xapp- 开头。", + "agent": "智能体", + "agentNone": "选择智能体", + "agentNoModel": "{{name}}(需要模型)", + "who": "谁可以使用", + "onlyListed": "仅我列出的人员和频道", + "anyone": "工作区中任何能给应用发消息的人", + "anyoneWarning": "任何能给此应用发消息或提及它的人,都可以以你的身份使用此智能体的工具。每个人和每个讨论串都有自己的对话。", + "anyoneConfirm": "我已了解", + "allowUsers": "允许的人员", + "allowUsersHint": "用户 ID(U...)或 @name", + "allowChannels": "允许的频道", + "allowChannelsHint": "频道 ID(C...):其中所有人都可以提及该应用", + "notifyChat": "把我的通知发送到此用户或频道 ID", + "notifyHelp": "可选。填你的用户 ID(U...),通知会以应用私信的形式发给你。", + "test": "测试", + "testOk": "已连接到 {{bot}}。", + "checkFailed": "已保存。token 检查失败:{{reason}}", + "senders": "发送者", + "noSenders": "还没有人给此应用发过消息。", + "allowButton": "允许", + "ignored": "已忽略", + "accepted": "已接受", + "save": "保存", + "cancel": "取消", + "editButton": "编辑", + "delete": "删除", + "empty": "还没有 Slack 应用。", + "agentOf": "智能体:{{name}}", + "accepts": "接受:{{who}}", + "modes": { + "anyone": "任何人", + "nobody": "暂无", + "listed_one": "1 个已列出的条目", + "listed_other": "{{count}} 个已列出的条目" + }, + "on": "已开启", + "onFor": "{{name}} 已开启", + "inactive": "未运行:{{reason}}。", + "confirmDelete": { + "title": "删除 {{name}}?", + "message": "应用将停止回答,随之保存的 token 也会被删除。若要重新设置,你必须再次输入 token。", + "confirm": "删除" + }, + "setup": { + "title": "如何创建 Slack 应用", + "step1": "在 api.slack.com/apps 选择 Create New App,再选 From an app manifest,选好你的工作区,粘贴下面的 manifest。", + "step2": "把应用安装到工作区,并从 OAuth & Permissions 复制机器人 token(xoxb-)。", + "step3": "在 Basic Information 的 App-Level Tokens 中创建作用域为 connections:write 的 token,并复制它(xapp-)。", + "step4": "在这里用这两个 token 创建绑定。若要在某个频道中使用该应用,请把它邀请进去(/invite @LocalAI)。", + "copy": "复制 manifest", + "copied": "已复制", + "manifest": "Slack 应用 manifest" + }, + "back": "返回连接器" +} diff --git a/addons/talk/ui/src/addon.jsx b/addons/talk/ui/src/addon.jsx index 26f5992f40..5b399a7851 100644 --- a/addons/talk/ui/src/addon.jsx +++ b/addons/talk/ui/src/addon.jsx @@ -2,9 +2,18 @@ import { RouteProvider } from 'react-router-dom' import { registerLocales } from '../../../internal/host-compat/src/i18n.js' import Talk from './pages/Talk' import en from './i18n/talk.en.json' +import it from './i18n/talk.it.json' +import es from './i18n/talk.es.json' +import de from './i18n/talk.de.json' +import fr from './i18n/talk.fr.json' +import zh from './i18n/talk.zh-CN.json' +import id from './i18n/talk.id.json' +import ko from './i18n/talk.ko.json' +import pt from './i18n/talk.pt-BR.json' // The words of the page are the host's own `talk` locale file. -registerLocales({ talk: { en } }) +// The words of the page, in every language of the web UI; a key a language lacks reads in English. +registerLocales({ talk: { en, it, es, de, fr, 'zh-CN': zh, id, ko, 'pt-BR': pt } }) export function createTalk() { function Component({ page, path, search }) { diff --git a/addons/talk/ui/src/i18n/talk.de.json b/addons/talk/ui/src/i18n/talk.de.json new file mode 100644 index 0000000000..795d69c525 --- /dev/null +++ b/addons/talk/ui/src/i18n/talk.de.json @@ -0,0 +1,149 @@ +{ + "title": "Talk", + "subtitle": "Echtzeit-Sprache", + "stage": "Sprachsitzung", + "picker": { + "title": "Sprach-Pipeline: zuhören, denken, sprechen", + "heading": "Pipeline-Modelle", + "label": "Pipeline-Modell", + "none": "Keine Pipeline", + "defaultVoice": "Standardstimme", + "auto": "auto" + }, + "view": { + "idle": { + "title": "Bereit", + "body": "Starte eine Sitzung und sprich. Das Mikrofon wird nur genutzt, solange eine Sitzung offen ist.", + "note": "Dein Mikrofon überträgt, solange die Sitzung läuft. Der Server erkennt, wenn du aufhörst zu sprechen, und antwortet von selbst.", + "noteManual": "Dein Mikrofon überträgt, solange die Sitzung läuft. Dieses Modell hat keine Spracherkennung, drücke daher am Ende jedes Beitrags auf Fertig gesprochen." + }, + "connecting": { + "title": "Verbindung wird aufgebaut", + "body": "Der Anruf wird eingerichtet. Das Modell hört noch nicht zu." + }, + "listening": { + "title": "Ich höre zu", + "body": "Sag, was du brauchst. Ich antworte, wenn du aufhörst.", + "hearing": "Ich höre dich sprechen.", + "manual": "Sag, was du brauchst, und drücke dann auf Fertig gesprochen." + }, + "interrupted": { + "title": "Unterbrochen", + "body": "Ich habe aufgehört. Sprich ruhig, ich höre zu." + }, + "thinking": { + "title": "Ich denke nach", + "body": "Eine Antwort wird erarbeitet." + }, + "speaking": { + "title": "Ich spreche", + "body": "Sprich jederzeit dazwischen, um mich zu unterbrechen." + }, + "blocked": { + "title": "Mikrofon blockiert", + "body": "Der Browser hat dieser Seite das Mikrofon nicht gegeben.", + "cardTitle": "Mikrofon erlauben", + "card": "Klicke auf das Schloss-Symbol in der Adressleiste, stelle Mikrofon auf Zulassen und versuche es dann erneut. Es wurde nichts aufgenommen." + }, + "lost": { + "title": "Verbindung verloren", + "body": "Die Verbindung zum Server ist abgebrochen.", + "cardTitle": "Die Sitzung ist beendet", + "card": "Die WebRTC-Verbindung ist fehlgeschlagen. Das bisherige Gespräch bleibt im Transkript." + }, + "error": { + "title": "Etwas ist schiefgelaufen", + "body": "Der Anruf konnte nicht eingerichtet werden, oder der Server hat einen Fehler gemeldet." + }, + "nopipe": { + "title": "Talk braucht ein Pipeline-Modell", + "body": "Ein Pipeline-Modell verbindet ein Speech-to-Text-Modell, ein Chat-Modell und ein Text-to-Speech-Modell zu einem Sprachassistenten. Noch ist keines eingerichtet. Erstelle eines oder suche zuerst die Modelle in der Galerie.", + "create": "Pipeline-Modell erstellen", + "gallery": "Galerie durchsuchen" + } + }, + "detail": { + "tool": "{{name}} wird ausgeführt.", + "processing": "Eine Antwort wird erarbeitet.", + "generating": "Die Antwort wird geschrieben.", + "server": "Der Server hat einen Fehler gemeldet: {{message}}", + "insecure": "Der Mikrofonzugriff braucht HTTPS oder localhost.", + "noMic": "Es wurde kein Mikrofon gefunden.", + "failed": "Der Anruf konnte nicht eingerichtet werden: {{message}}", + "unknown": "unbekannter Fehler" + }, + "controls": { + "start": "Sitzung starten", + "end": "Sitzung beenden", + "testTone": "Testton", + "endTurn": "Fertig gesprochen", + "diagnostics": "Diagnose", + "viewTraces": "Traces ansehen", + "retry": { + "blocked": "Erneut versuchen", + "lost": "Neu verbinden", + "error": "Erneut versuchen" + } + }, + "transcript": { + "title": "Transkript", + "copy": "Kopieren", + "empty": "Was du sagst und was das Modell antwortet, erscheint hier, während es passiert. Es wird nicht gespeichert.", + "emptyNoPipeline": "Noch nichts anzuzeigen.", + "user": "Du", + "assistant": "Antwort", + "tool_call": "Werkzeug", + "tool_result": "Ergebnis", + "interrupted": "Antwort unterbrochen", + "toneRequested": "Testton angefordert" + }, + "settings": { + "title": "Sitzungseinstellungen", + "desc": "Gilt, wenn die Sitzung startet.", + "close": "Schließen", + "voiceLanguage": "Stimme und Sprache", + "voice": "Stimme", + "voicePlaceholder": "Name der Stimme (leer lassen für den Modellstandard)", + "language": "Transkriptionssprache", + "languagePlaceholder": "Sprachcode, zum Beispiel de (leer erkennt sie automatisch)", + "instructions": "Anweisungen", + "instructionsPlaceholder": "Systemanweisungen für das Modell", + "instructionsHelp": "Werden als Sitzungsanweisungen gesendet. Halte sie kurz: Antworten werden gesprochen.", + "tools": "Werkzeuge", + "manage": "Verwaltungsmodus", + "manageDesc": "Das Modell darf LocalAI abfragen: Modelle, Backends und Systeminfos.", + "manageLocked": "Fest, solange eine Sitzung offen ist. Beende die Sitzung, um es zu ändern.", + "pipeline": "{{name}} im Detail", + "anyToAny": "Speech-to-Speech-Modell: Ein Modell hört dich, antwortet und spricht. Es braucht kein getrenntes Transkriptions-, Sprach- oder Sprachausgabemodell.", + "noVad": "Es ist kein Modell zur Spracherkennung gesetzt. Drücke nach jedem Beitrag auf Fertig gesprochen.", + "editConfig": "Modellkonfiguration bearbeiten", + "editPipeline": "Pipeline bearbeiten", + "slot": { + "vad": "Spracherkennung", + "transcription": "Transkription", + "llm": "Sprachmodell", + "tts": "Sprachausgabe" + } + }, + "diag": { + "title": "Audio-Diagnose", + "waveform": "Wellenform", + "spectrum": "Spektrum (FFT)", + "waiting": "Warte auf Statistiken...", + "peakFreq": "Spitzenfrequenz", + "thd": "THD", + "rms": "RMS-Pegel", + "sampleRate": "Abtastrate", + "packetsRecv": "Empfangene Pakete", + "packetsLost": "Verlorene Pakete", + "jitter": "Jitter", + "concealed": "Verdeckte Samples" + }, + "toasts": { + "selectModel": "Wähle zuerst ein Pipeline-Modell.", + "modelsFailed": "Die Echtzeit-Modelle konnten nicht geladen werden: {{message}}", + "viewTraces": "Traces ansehen", + "copied": "Transkript kopiert", + "copyFailed": "Kopieren in die Zwischenablage nicht möglich" + } +} diff --git a/addons/talk/ui/src/i18n/talk.es.json b/addons/talk/ui/src/i18n/talk.es.json new file mode 100644 index 0000000000..f1389180e7 --- /dev/null +++ b/addons/talk/ui/src/i18n/talk.es.json @@ -0,0 +1,149 @@ +{ + "title": "Talk", + "subtitle": "voz en tiempo real", + "stage": "Sesión de voz", + "picker": { + "title": "Pipeline de voz: escuchar, pensar, hablar", + "heading": "Modelos de pipeline", + "label": "Modelo de pipeline", + "none": "Sin pipeline", + "defaultVoice": "voz por defecto", + "auto": "auto" + }, + "view": { + "idle": { + "title": "Listo", + "body": "Inicia una sesión y habla. El micrófono solo se usa mientras hay una sesión abierta.", + "note": "Tu micrófono transmite mientras dura la sesión. El servidor detecta cuándo dejas de hablar y responde por sí solo.", + "noteManual": "Tu micrófono transmite mientras dura la sesión. Este modelo no detecta la voz, así que pulsa Terminé de hablar cuando acabes un turno." + }, + "connecting": { + "title": "Conectando", + "body": "Preparando la llamada. El modelo aún no escucha." + }, + "listening": { + "title": "Escuchando", + "body": "Dime lo que necesitas. Respondo cuando termines.", + "hearing": "Te estoy oyendo hablar.", + "manual": "Dime lo que necesitas y luego pulsa Terminé de hablar." + }, + "interrupted": { + "title": "Interrumpido", + "body": "Me detuve. Adelante, te escucho." + }, + "thinking": { + "title": "Pensando", + "body": "Preparando una respuesta." + }, + "speaking": { + "title": "Hablando", + "body": "Habla encima de mí en cualquier momento para interrumpirme." + }, + "blocked": { + "title": "Micrófono bloqueado", + "body": "El navegador no dio el micrófono a esta página.", + "cardTitle": "Permite el micrófono", + "card": "Haz clic en el icono del candado de la barra de direcciones, pon Micrófono en Permitir y vuelve a intentarlo. No se grabó nada." + }, + "lost": { + "title": "Conexión perdida", + "body": "Se cayó el enlace con el servidor.", + "cardTitle": "La sesión terminó", + "card": "Falló el enlace WebRTC. La conversación hasta ahora queda en la transcripción." + }, + "error": { + "title": "Algo salió mal", + "body": "No se pudo preparar la llamada o el servidor informó de un error." + }, + "nopipe": { + "title": "Talk necesita un modelo de pipeline", + "body": "Un modelo de pipeline une un modelo de voz a texto, un modelo de chat y un modelo de texto a voz en un solo asistente de voz. Todavía no hay ninguno configurado. Crea uno, o busca primero los modelos en la galería.", + "create": "Crear un modelo de pipeline", + "gallery": "Explorar la galería" + } + }, + "detail": { + "tool": "Ejecutando {{name}}.", + "processing": "Preparando una respuesta.", + "generating": "Escribiendo la respuesta.", + "server": "El servidor informó de un error: {{message}}", + "insecure": "El acceso al micrófono necesita HTTPS o localhost.", + "noMic": "No se encontró ningún micrófono.", + "failed": "No se pudo preparar la llamada: {{message}}", + "unknown": "error desconocido" + }, + "controls": { + "start": "Iniciar sesión", + "end": "Terminar sesión", + "testTone": "Tono de prueba", + "endTurn": "Terminé de hablar", + "diagnostics": "Diagnóstico", + "viewTraces": "Ver trazas", + "retry": { + "blocked": "Reintentar", + "lost": "Reconectar", + "error": "Reintentar" + } + }, + "transcript": { + "title": "Transcripción", + "copy": "Copiar", + "empty": "Lo que dices y lo que responde el modelo aparece aquí a medida que ocurre. No se guarda.", + "emptyNoPipeline": "Aún no hay nada que mostrar.", + "user": "Tú", + "assistant": "Respuesta", + "tool_call": "Herramienta", + "tool_result": "Resultado", + "interrupted": "Respuesta interrumpida", + "toneRequested": "Tono de prueba solicitado" + }, + "settings": { + "title": "Ajustes de la sesión", + "desc": "Se aplican al iniciar la sesión.", + "close": "Cerrar", + "voiceLanguage": "Voz e idioma", + "voice": "Voz", + "voicePlaceholder": "Nombre de la voz (vacío para la del modelo por defecto)", + "language": "Idioma de transcripción", + "languagePlaceholder": "Código de idioma, por ejemplo es (vacío para detectarlo)", + "instructions": "Instrucciones", + "instructionsPlaceholder": "Instrucciones del sistema para el modelo", + "instructionsHelp": "Se envían como instrucciones de la sesión. Que sean breves: las respuestas se dicen en voz alta.", + "tools": "Herramientas", + "manage": "Modo de gestión", + "manageDesc": "Deja que el modelo consulte LocalAI: modelos, backends e información del sistema.", + "manageLocked": "Fijo mientras hay una sesión abierta. Termina la sesión para cambiarlo.", + "pipeline": "{{name}} en detalle", + "anyToAny": "Modelo de voz a voz: un solo modelo te oye, responde y habla. No necesita un modelo aparte de transcripción, de idioma ni de voz.", + "noVad": "No hay ningún modelo de detección de voz. Pulsa Terminé de hablar después de cada turno.", + "editConfig": "Editar la configuración del modelo", + "editPipeline": "Editar el pipeline", + "slot": { + "vad": "Detección de voz", + "transcription": "Transcripción", + "llm": "Modelo de lenguaje", + "tts": "Voz" + } + }, + "diag": { + "title": "Diagnóstico de audio", + "waveform": "Forma de onda", + "spectrum": "Espectro (FFT)", + "waiting": "Esperando estadísticas...", + "peakFreq": "Frecuencia pico", + "thd": "THD", + "rms": "Nivel RMS", + "sampleRate": "Frecuencia de muestreo", + "packetsRecv": "Paquetes recibidos", + "packetsLost": "Paquetes perdidos", + "jitter": "Jitter", + "concealed": "Muestras ocultadas" + }, + "toasts": { + "selectModel": "Elige primero un modelo de pipeline.", + "modelsFailed": "No se pudieron cargar los modelos en tiempo real: {{message}}", + "viewTraces": "Ver trazas", + "copied": "Transcripción copiada", + "copyFailed": "No se pudo copiar al portapapeles" + } +} diff --git a/addons/talk/ui/src/i18n/talk.fr.json b/addons/talk/ui/src/i18n/talk.fr.json new file mode 100644 index 0000000000..99aac8b110 --- /dev/null +++ b/addons/talk/ui/src/i18n/talk.fr.json @@ -0,0 +1,149 @@ +{ + "title": "Talk", + "subtitle": "voix en temps réel", + "stage": "Session vocale", + "picker": { + "title": "Pipeline vocal : écouter, réfléchir, parler", + "heading": "Modèles de pipeline", + "label": "Modèle de pipeline", + "none": "Aucun pipeline", + "defaultVoice": "voix par défaut", + "auto": "auto" + }, + "view": { + "idle": { + "title": "Prêt", + "body": "Démarrez une session et parlez. Le microphone n’est utilisé que pendant qu’une session est ouverte.", + "note": "Votre microphone diffuse tant que la session dure. Le serveur détecte quand vous cessez de parler et répond seul.", + "noteManual": "Votre microphone diffuse tant que la session dure. Ce modèle n’a pas de détection vocale : appuyez sur Terminé quand vous finissez un tour de parole." + }, + "connecting": { + "title": "Connexion", + "body": "Mise en place de l’appel. Le modèle n’écoute pas encore." + }, + "listening": { + "title": "À l’écoute", + "body": "Dites ce qu’il vous faut. Je réponds quand vous vous arrêtez.", + "hearing": "Je vous entends parler.", + "manual": "Dites ce qu’il vous faut, puis appuyez sur Terminé." + }, + "interrupted": { + "title": "Interrompu", + "body": "Je me suis arrêté. Allez-y, je vous écoute." + }, + "thinking": { + "title": "Réflexion", + "body": "Élaboration d’une réponse." + }, + "speaking": { + "title": "Je parle", + "body": "Parlez par-dessus ma voix à tout moment pour m’interrompre." + }, + "blocked": { + "title": "Microphone bloqué", + "body": "Le navigateur n’a pas donné le microphone à cette page.", + "cardTitle": "Autoriser le microphone", + "card": "Cliquez sur l’icône de cadenas dans la barre d’adresse, réglez Microphone sur Autoriser, puis réessayez. Rien n’a été enregistré." + }, + "lost": { + "title": "Connexion perdue", + "body": "Le lien avec le serveur s’est interrompu.", + "cardTitle": "La session est terminée", + "card": "Le lien WebRTC a échoué. La conversation jusque-là reste dans la transcription." + }, + "error": { + "title": "Un problème est survenu", + "body": "L’appel n’a pas pu être établi ou le serveur a signalé une erreur." + }, + "nopipe": { + "title": "Talk a besoin d’un modèle de pipeline", + "body": "Un modèle de pipeline réunit un modèle de reconnaissance vocale, un modèle de chat et un modèle de synthèse vocale en un seul assistant vocal. Aucun n’est encore configuré. Créez-en un, ou cherchez d’abord les modèles dans la galerie.", + "create": "Créer un modèle de pipeline", + "gallery": "Parcourir la galerie" + } + }, + "detail": { + "tool": "Exécution de {{name}}.", + "processing": "Élaboration d’une réponse.", + "generating": "Rédaction de la réponse.", + "server": "Le serveur a signalé une erreur : {{message}}", + "insecure": "L’accès au microphone exige HTTPS ou localhost.", + "noMic": "Aucun microphone n’a été trouvé.", + "failed": "L’appel n’a pas pu être établi : {{message}}", + "unknown": "erreur inconnue" + }, + "controls": { + "start": "Démarrer la session", + "end": "Terminer la session", + "testTone": "Tonalité de test", + "endTurn": "Terminé", + "diagnostics": "Diagnostic", + "viewTraces": "Voir les traces", + "retry": { + "blocked": "Réessayer", + "lost": "Se reconnecter", + "error": "Réessayer" + } + }, + "transcript": { + "title": "Transcription", + "copy": "Copier", + "empty": "Ce que vous dites et ce que le modèle répond apparaît ici au fur et à mesure. Rien n’est enregistré.", + "emptyNoPipeline": "Rien à afficher pour le moment.", + "user": "Vous", + "assistant": "Réponse", + "tool_call": "Outil", + "tool_result": "Résultat", + "interrupted": "Réponse interrompue", + "toneRequested": "Tonalité de test demandée" + }, + "settings": { + "title": "Paramètres de session", + "desc": "S’appliquent au démarrage de la session.", + "close": "Fermer", + "voiceLanguage": "Voix et langue", + "voice": "Voix", + "voicePlaceholder": "Nom de la voix (laisser vide pour celle du modèle)", + "language": "Langue de transcription", + "languagePlaceholder": "Code de langue, par exemple en (vide : détection automatique)", + "instructions": "Instructions", + "instructionsPlaceholder": "Instructions système pour le modèle", + "instructionsHelp": "Envoyées comme instructions de la session. Restez bref : les réponses sont prononcées.", + "tools": "Outils", + "manage": "Mode gestion", + "manageDesc": "Laisser le modèle interroger LocalAI : modèles, backends et infos système.", + "manageLocked": "Fixé tant qu’une session est ouverte. Terminez la session pour le modifier.", + "pipeline": "{{name}} en détail", + "anyToAny": "Modèle parole-à-parole : un seul modèle vous entend, répond et parle. Il n’a besoin d’aucun modèle séparé de transcription, de langue ou de synthèse vocale.", + "noVad": "Aucun modèle de détection vocale n’est défini. Appuyez sur Terminé après chaque tour.", + "editConfig": "Modifier la config du modèle", + "editPipeline": "Modifier le pipeline", + "slot": { + "vad": "Détection vocale", + "transcription": "Transcription", + "llm": "Modèle de langage", + "tts": "Synthèse vocale" + } + }, + "diag": { + "title": "Diagnostic audio", + "waveform": "Forme d’onde", + "spectrum": "Spectre (FFT)", + "waiting": "En attente des statistiques...", + "peakFreq": "Fréquence de crête", + "thd": "THD", + "rms": "Niveau RMS", + "sampleRate": "Fréquence d’échantillonnage", + "packetsRecv": "Paquets reçus", + "packetsLost": "Paquets perdus", + "jitter": "Gigue", + "concealed": "Échantillons masqués" + }, + "toasts": { + "selectModel": "Sélectionnez d’abord un modèle de pipeline.", + "modelsFailed": "Échec du chargement des modèles temps réel : {{message}}", + "viewTraces": "Voir les traces", + "copied": "Transcription copiée", + "copyFailed": "Impossible de copier dans le presse-papiers" + } +} diff --git a/addons/talk/ui/src/i18n/talk.id.json b/addons/talk/ui/src/i18n/talk.id.json new file mode 100644 index 0000000000..713eba438f --- /dev/null +++ b/addons/talk/ui/src/i18n/talk.id.json @@ -0,0 +1,149 @@ +{ + "title": "Talk", + "subtitle": "suara realtime", + "stage": "Sesi suara", + "picker": { + "title": "Pipeline suara: mendengar, berpikir, berbicara", + "heading": "Model pipeline", + "label": "Model pipeline", + "none": "Tanpa pipeline", + "defaultVoice": "suara bawaan", + "auto": "otomatis" + }, + "view": { + "idle": { + "title": "Siap", + "body": "Mulai sesi dan bicaralah. Mikrofon hanya dipakai selama sesi terbuka.", + "note": "Mikrofon Anda dialirkan selama sesi berjalan. Server mengetahui saat Anda berhenti bicara dan menjawab sendiri.", + "noteManual": "Mikrofon Anda dialirkan selama sesi berjalan. Model ini tidak punya deteksi suara, jadi tekan Selesai bicara saat Anda mengakhiri satu giliran." + }, + "connecting": { + "title": "Menghubungkan", + "body": "Menyiapkan panggilan. Model belum mendengarkan." + }, + "listening": { + "title": "Mendengarkan", + "body": "Katakan apa yang Anda perlukan. Saya menjawab saat Anda berhenti.", + "hearing": "Mendengar Anda berbicara.", + "manual": "Katakan apa yang Anda perlukan, lalu tekan Selesai bicara." + }, + "interrupted": { + "title": "Terinterupsi", + "body": "Saya berhenti. Silakan, saya mendengarkan." + }, + "thinking": { + "title": "Berpikir", + "body": "Menyusun balasan." + }, + "speaking": { + "title": "Berbicara", + "body": "Bicaralah kapan saja untuk menyela saya." + }, + "blocked": { + "title": "Mikrofon diblokir", + "body": "Browser tidak memberi halaman ini akses ke mikrofon.", + "cardTitle": "Izinkan mikrofon", + "card": "Klik ikon gembok di bilah alamat, setel Mikrofon ke Izinkan, lalu coba lagi. Tidak ada yang direkam." + }, + "lost": { + "title": "Koneksi terputus", + "body": "Tautan ke server terputus.", + "cardTitle": "Sesi berakhir", + "card": "Tautan WebRTC gagal. Percakapan sejauh ini tetap ada di transkrip." + }, + "error": { + "title": "Terjadi kesalahan", + "body": "Panggilan tidak dapat disiapkan atau server melaporkan kesalahan." + }, + "nopipe": { + "title": "Talk memerlukan model pipeline", + "body": "Model pipeline menggabungkan model speech-to-text, model chat, dan model text-to-speech menjadi satu asisten suara. Belum ada yang dikonfigurasi. Buat satu, atau cari modelnya di galeri terlebih dahulu.", + "create": "Buat model pipeline", + "gallery": "Jelajahi galeri" + } + }, + "detail": { + "tool": "Menjalankan {{name}}.", + "processing": "Menyusun balasan.", + "generating": "Menulis balasan.", + "server": "Server melaporkan kesalahan: {{message}}", + "insecure": "Akses mikrofon memerlukan HTTPS atau localhost.", + "noMic": "Mikrofon tidak ditemukan.", + "failed": "Panggilan tidak dapat disiapkan: {{message}}", + "unknown": "kesalahan tidak diketahui" + }, + "controls": { + "start": "Mulai sesi", + "end": "Akhiri sesi", + "testTone": "Nada uji", + "endTurn": "Selesai bicara", + "diagnostics": "Diagnostik", + "viewTraces": "Lihat trace", + "retry": { + "blocked": "Coba lagi", + "lost": "Hubungkan ulang", + "error": "Coba lagi" + } + }, + "transcript": { + "title": "Transkrip", + "copy": "Salin", + "empty": "Apa yang Anda ucapkan dan jawaban model muncul di sini saat terjadi. Tidak disimpan.", + "emptyNoPipeline": "Belum ada yang ditampilkan.", + "user": "Anda", + "assistant": "Balasan", + "tool_call": "Alat", + "tool_result": "Hasil", + "interrupted": "Balasan terinterupsi", + "toneRequested": "Nada uji diminta" + }, + "settings": { + "title": "Pengaturan sesi", + "desc": "Berlaku saat sesi dimulai.", + "close": "Tutup", + "voiceLanguage": "Suara dan bahasa", + "voice": "Suara", + "voicePlaceholder": "Nama suara (kosongkan untuk bawaan model)", + "language": "Bahasa transkripsi", + "languagePlaceholder": "Kode bahasa, misalnya en (kosong untuk mendeteksinya)", + "instructions": "Instruksi", + "instructionsPlaceholder": "Instruksi sistem untuk model", + "instructionsHelp": "Dikirim sebagai instruksi sesi. Buat singkat: balasan diucapkan.", + "tools": "Alat", + "manage": "Mode kelola", + "manageDesc": "Biarkan model menanyakan LocalAI: model, backend, dan info sistem.", + "manageLocked": "Terkunci selama sesi terbuka. Akhiri sesi untuk mengubahnya.", + "pipeline": "{{name}} secara rinci", + "anyToAny": "Model speech-to-speech: satu model mendengar Anda, menjawab, dan berbicara. Tidak perlu model transkripsi, bahasa, atau suara terpisah.", + "noVad": "Model deteksi suara belum diatur. Tekan Selesai bicara setelah tiap giliran.", + "editConfig": "Ubah konfigurasi model", + "editPipeline": "Ubah pipeline", + "slot": { + "vad": "Deteksi suara", + "transcription": "Transkripsi", + "llm": "Model bahasa", + "tts": "Suara ucap" + } + }, + "diag": { + "title": "Diagnostik audio", + "waveform": "Bentuk gelombang", + "spectrum": "Spektrum (FFT)", + "waiting": "Menunggu statistik...", + "peakFreq": "Frekuensi puncak", + "thd": "THD", + "rms": "Level RMS", + "sampleRate": "Sample rate", + "packetsRecv": "Paket diterima", + "packetsLost": "Paket hilang", + "jitter": "Jitter", + "concealed": "Sampel yang disamarkan" + }, + "toasts": { + "selectModel": "Pilih model pipeline terlebih dahulu.", + "modelsFailed": "Gagal memuat model realtime: {{message}}", + "viewTraces": "Lihat trace", + "copied": "Transkrip disalin", + "copyFailed": "Tidak dapat menyalin ke clipboard" + } +} diff --git a/addons/talk/ui/src/i18n/talk.it.json b/addons/talk/ui/src/i18n/talk.it.json new file mode 100644 index 0000000000..e678bb4c72 --- /dev/null +++ b/addons/talk/ui/src/i18n/talk.it.json @@ -0,0 +1,149 @@ +{ + "title": "Talk", + "subtitle": "voce in tempo reale", + "stage": "Sessione vocale", + "picker": { + "title": "Pipeline vocale: ascolta, pensa, parla", + "heading": "Modelli pipeline", + "label": "Modello pipeline", + "none": "Nessuna pipeline", + "defaultVoice": "voce predefinita", + "auto": "auto" + }, + "view": { + "idle": { + "title": "Pronto", + "body": "Avvia una sessione e parla. Il microfono viene usato solo mentre una sessione è aperta.", + "note": "Il microfono trasmette mentre la sessione è in corso. Il server si accorge quando smetti di parlare e risponde da solo.", + "noteManual": "Il microfono trasmette mentre la sessione è in corso. Questo modello non rileva la voce, quindi premi Ho finito alla fine di ogni turno." + }, + "connecting": { + "title": "Connessione", + "body": "Preparazione della chiamata. Il modello non sta ancora ascoltando." + }, + "listening": { + "title": "In ascolto", + "body": "Dimmi cosa ti serve. Rispondo quando smetti.", + "hearing": "Ti sto sentendo parlare.", + "manual": "Dimmi cosa ti serve, poi premi Ho finito." + }, + "interrupted": { + "title": "Interrotto", + "body": "Mi sono fermato. Prosegui pure, ti ascolto." + }, + "thinking": { + "title": "Sto pensando", + "body": "Sto preparando una risposta." + }, + "speaking": { + "title": "Sto parlando", + "body": "Parla sopra di me in qualsiasi momento per interrompermi." + }, + "blocked": { + "title": "Microfono bloccato", + "body": "Il browser non ha dato il microfono a questa pagina.", + "cardTitle": "Consenti il microfono", + "card": "Fai clic sull'icona del lucchetto nella barra degli indirizzi, imposta Microfono su Consenti, poi riprova. Non è stato registrato nulla." + }, + "lost": { + "title": "Connessione persa", + "body": "Il collegamento con il server è caduto.", + "cardTitle": "La sessione è terminata", + "card": "Il collegamento WebRTC è fallito. La conversazione fin qui resta nella trascrizione." + }, + "error": { + "title": "Qualcosa è andato storto", + "body": "Non è stato possibile preparare la chiamata, oppure il server ha segnalato un errore." + }, + "nopipe": { + "title": "Talk ha bisogno di un modello pipeline", + "body": "Un modello pipeline unisce un modello da voce a testo, un modello di chat e un modello da testo a voce in un unico assistente vocale. Non ne è ancora configurato nessuno. Creane uno, oppure cerca prima i modelli nella galleria.", + "create": "Crea un modello pipeline", + "gallery": "Sfoglia la galleria" + } + }, + "detail": { + "tool": "Esecuzione di {{name}}.", + "processing": "Sto preparando una risposta.", + "generating": "Sto scrivendo la risposta.", + "server": "Il server ha segnalato un errore: {{message}}", + "insecure": "L'accesso al microfono richiede HTTPS o localhost.", + "noMic": "Nessun microfono trovato.", + "failed": "Non è stato possibile preparare la chiamata: {{message}}", + "unknown": "errore sconosciuto" + }, + "controls": { + "start": "Avvia sessione", + "end": "Termina sessione", + "testTone": "Tono di prova", + "endTurn": "Ho finito", + "diagnostics": "Diagnostica", + "viewTraces": "Vedi le tracce", + "retry": { + "blocked": "Riprova", + "lost": "Riconnetti", + "error": "Riprova" + } + }, + "transcript": { + "title": "Trascrizione", + "copy": "Copia", + "empty": "Ciò che dici e ciò che risponde il modello compare qui man mano. Non viene salvato.", + "emptyNoPipeline": "Ancora niente da mostrare.", + "user": "Tu", + "assistant": "Risposta", + "tool_call": "Strumento", + "tool_result": "Risultato", + "interrupted": "Risposta interrotta", + "toneRequested": "Tono di prova richiesto" + }, + "settings": { + "title": "Impostazioni della sessione", + "desc": "Valgono all'avvio della sessione.", + "close": "Chiudi", + "voiceLanguage": "Voce e lingua", + "voice": "Voce", + "voicePlaceholder": "Nome della voce (lascia vuoto per quella predefinita del modello)", + "language": "Lingua di trascrizione", + "languagePlaceholder": "Codice della lingua, per esempio it (vuoto: rilevamento automatico)", + "instructions": "Istruzioni", + "instructionsPlaceholder": "Istruzioni di sistema per il modello", + "instructionsHelp": "Inviate come istruzioni della sessione. Tienile brevi: le risposte vengono pronunciate.", + "tools": "Strumenti", + "manage": "Modalità di gestione", + "manageDesc": "Lascia che il modello interroghi LocalAI: modelli, backend e informazioni di sistema.", + "manageLocked": "Non modificabile mentre una sessione è aperta. Termina la sessione per cambiarla.", + "pipeline": "{{name}} nel dettaglio", + "anyToAny": "Modello da voce a voce: un solo modello ti ascolta, risponde e parla. Non servono un modello di trascrizione, di lingua o di sintesi vocale separati.", + "noVad": "Nessun modello di rilevamento della voce è impostato. Premi Ho finito dopo ogni turno.", + "editConfig": "Modifica la configurazione del modello", + "editPipeline": "Modifica la pipeline", + "slot": { + "vad": "Rilevamento della voce", + "transcription": "Trascrizione", + "llm": "Modello linguistico", + "tts": "Sintesi vocale" + } + }, + "diag": { + "title": "Diagnostica audio", + "waveform": "Forma d'onda", + "spectrum": "Spettro (FFT)", + "waiting": "In attesa delle statistiche...", + "peakFreq": "Frequenza di picco", + "thd": "THD", + "rms": "Livello RMS", + "sampleRate": "Frequenza di campionamento", + "packetsRecv": "Pacchetti ricevuti", + "packetsLost": "Pacchetti persi", + "jitter": "Jitter", + "concealed": "Campioni mascherati" + }, + "toasts": { + "selectModel": "Seleziona prima un modello pipeline.", + "modelsFailed": "Impossibile caricare i modelli realtime: {{message}}", + "viewTraces": "Vedi le tracce", + "copied": "Trascrizione copiata", + "copyFailed": "Impossibile copiare negli appunti" + } +} diff --git a/addons/talk/ui/src/i18n/talk.ko.json b/addons/talk/ui/src/i18n/talk.ko.json new file mode 100644 index 0000000000..5bf73302c9 --- /dev/null +++ b/addons/talk/ui/src/i18n/talk.ko.json @@ -0,0 +1,149 @@ +{ + "title": "Talk", + "subtitle": "실시간 음성 대화", + "stage": "음성 세션", + "picker": { + "title": "음성 파이프라인: 듣고, 생각하고, 말하기", + "heading": "파이프라인 모델", + "label": "파이프라인 모델", + "none": "파이프라인 없음", + "defaultVoice": "기본 음성", + "auto": "자동" + }, + "view": { + "idle": { + "title": "준비됨", + "body": "세션을 시작하고 말하세요. 마이크는 세션이 열려 있는 동안에만 사용됩니다.", + "note": "세션이 실행되는 동안 마이크 음성이 스트리밍됩니다. 서버가 말을 멈춘 것을 감지해 스스로 답변합니다.", + "noteManual": "세션이 실행되는 동안 마이크 음성이 스트리밍됩니다. 이 모델에는 음성 감지 기능이 없으므로 말을 마치면 말하기 완료를 누르세요." + }, + "connecting": { + "title": "연결 중", + "body": "통화를 준비하는 중입니다. 모델은 아직 듣고 있지 않습니다." + }, + "listening": { + "title": "듣는 중", + "body": "필요한 것을 말씀하세요. 말을 멈추면 답변합니다.", + "hearing": "말씀하시는 것을 듣고 있습니다.", + "manual": "필요한 것을 말한 다음 말하기 완료를 누르세요." + }, + "interrupted": { + "title": "중단됨", + "body": "멈췄습니다. 말씀하세요, 듣고 있습니다." + }, + "thinking": { + "title": "생각 중", + "body": "답변을 준비하는 중입니다." + }, + "speaking": { + "title": "말하는 중", + "body": "언제든 말을 겹쳐서 끼어들 수 있습니다." + }, + "blocked": { + "title": "마이크 차단됨", + "body": "브라우저가 이 페이지에 마이크를 허용하지 않았습니다.", + "cardTitle": "마이크 허용", + "card": "주소 표시줄의 자물쇠 아이콘을 누르고 마이크를 허용으로 설정한 다음 다시 시도하세요. 아무것도 녹음되지 않았습니다." + }, + "lost": { + "title": "연결 끊김", + "body": "서버와의 연결이 끊어졌습니다.", + "cardTitle": "세션이 종료되었습니다", + "card": "WebRTC 연결에 실패했습니다. 지금까지의 대화는 대화 기록에 남아 있습니다." + }, + "error": { + "title": "문제가 발생했습니다", + "body": "통화를 준비하지 못했거나 서버가 오류를 보고했습니다." + }, + "nopipe": { + "title": "Talk에는 파이프라인 모델이 필요합니다", + "body": "파이프라인 모델은 음성 인식 모델, 채팅 모델, 음성 합성 모델을 하나의 음성 어시스턴트로 묶습니다. 아직 구성된 것이 없습니다. 하나 만들거나 먼저 갤러리에서 모델을 찾아보세요.", + "create": "파이프라인 모델 만들기", + "gallery": "갤러리 둘러보기" + } + }, + "detail": { + "tool": "{{name}} 실행 중.", + "processing": "답변을 준비하는 중.", + "generating": "답변을 작성하는 중.", + "server": "서버가 오류를 보고했습니다: {{message}}", + "insecure": "마이크를 사용하려면 HTTPS 또는 localhost가 필요합니다.", + "noMic": "마이크를 찾을 수 없습니다.", + "failed": "통화를 준비하지 못했습니다: {{message}}", + "unknown": "알 수 없는 오류" + }, + "controls": { + "start": "세션 시작", + "end": "세션 종료", + "testTone": "테스트 톤", + "endTurn": "말하기 완료", + "diagnostics": "진단", + "viewTraces": "트레이스 보기", + "retry": { + "blocked": "다시 시도", + "lost": "다시 연결", + "error": "다시 시도" + } + }, + "transcript": { + "title": "대화 기록", + "copy": "복사", + "empty": "내가 말한 내용과 모델의 답변이 진행되는 대로 여기에 표시됩니다. 저장되지 않습니다.", + "emptyNoPipeline": "아직 표시할 내용이 없습니다.", + "user": "나", + "assistant": "답변", + "tool_call": "도구", + "tool_result": "결과", + "interrupted": "답변이 중단됨", + "toneRequested": "테스트 톤 요청됨" + }, + "settings": { + "title": "세션 설정", + "desc": "세션을 시작할 때 적용됩니다.", + "close": "닫기", + "voiceLanguage": "음성과 언어", + "voice": "음성", + "voicePlaceholder": "음성 이름(비워 두면 모델 기본값)", + "language": "받아쓰기 언어", + "languagePlaceholder": "언어 코드, 예: ko(비워 두면 자동 감지)", + "instructions": "지침", + "instructionsPlaceholder": "모델에 줄 시스템 지침", + "instructionsHelp": "세션 지침으로 전송됩니다. 답변은 음성으로 나오므로 짧게 유지하세요.", + "tools": "도구", + "manage": "관리 모드", + "manageDesc": "모델이 LocalAI에 질의할 수 있게 합니다: 모델, 백엔드, 시스템 정보.", + "manageLocked": "세션이 열려 있는 동안은 고정됩니다. 바꾸려면 세션을 종료하세요.", + "pipeline": "{{name}} 자세히", + "anyToAny": "음성 대 음성 모델: 모델 하나가 듣고, 답변하고, 말합니다. 별도의 음성 인식, 언어, 음성 합성 모델이 필요하지 않습니다.", + "noVad": "음성 감지 모델이 설정되어 있지 않습니다. 말할 때마다 끝나면 말하기 완료를 누르세요.", + "editConfig": "모델 구성 편집", + "editPipeline": "파이프라인 편집", + "slot": { + "vad": "음성 감지", + "transcription": "받아쓰기", + "llm": "언어 모델", + "tts": "음성 합성" + } + }, + "diag": { + "title": "오디오 진단", + "waveform": "파형", + "spectrum": "스펙트럼(FFT)", + "waiting": "통계를 기다리는 중...", + "peakFreq": "피크 주파수", + "thd": "THD", + "rms": "RMS 레벨", + "sampleRate": "샘플 레이트", + "packetsRecv": "수신한 패킷", + "packetsLost": "손실된 패킷", + "jitter": "지터", + "concealed": "보정된 샘플" + }, + "toasts": { + "selectModel": "먼저 파이프라인 모델을 선택하세요.", + "modelsFailed": "실시간 모델을 불러오지 못했습니다: {{message}}", + "viewTraces": "트레이스 보기", + "copied": "대화 기록을 복사했습니다", + "copyFailed": "클립보드에 복사할 수 없습니다" + } +} diff --git a/addons/talk/ui/src/i18n/talk.pt-BR.json b/addons/talk/ui/src/i18n/talk.pt-BR.json new file mode 100644 index 0000000000..c92db27b31 --- /dev/null +++ b/addons/talk/ui/src/i18n/talk.pt-BR.json @@ -0,0 +1,149 @@ +{ + "title": "Talk", + "subtitle": "voz em tempo real", + "stage": "Sessão de voz", + "picker": { + "title": "Pipeline de voz: ouvir, pensar, falar", + "heading": "Modelos de pipeline", + "label": "Modelo de pipeline", + "none": "Nenhum pipeline", + "defaultVoice": "voz padrão", + "auto": "auto" + }, + "view": { + "idle": { + "title": "Pronto", + "body": "Inicie uma sessão e fale. O microfone só é usado enquanto uma sessão está aberta.", + "note": "Seu microfone transmite enquanto a sessão roda. O servidor percebe quando você para de falar e responde sozinho.", + "noteManual": "Seu microfone transmite enquanto a sessão roda. Este modelo não tem detecção de voz, então pressione Terminei de falar ao fim de cada turno." + }, + "connecting": { + "title": "Conectando", + "body": "Preparando a chamada. O modelo ainda não está ouvindo." + }, + "listening": { + "title": "Ouvindo", + "body": "Diga o que precisa. Respondo quando você parar.", + "hearing": "Ouvindo você falar.", + "manual": "Diga o que precisa e depois pressione Terminei de falar." + }, + "interrupted": { + "title": "Interrompido", + "body": "Parei. Pode falar, estou ouvindo." + }, + "thinking": { + "title": "Pensando", + "body": "Elaborando uma resposta." + }, + "speaking": { + "title": "Falando", + "body": "Fale por cima de mim a qualquer momento para interromper." + }, + "blocked": { + "title": "Microfone bloqueado", + "body": "O navegador não deu o microfone a esta página.", + "cardTitle": "Permitir o microfone", + "card": "Clique no ícone de cadeado na barra de endereço, defina Microfone como Permitir e tente de novo. Nada foi gravado." + }, + "lost": { + "title": "Conexão perdida", + "body": "A ligação com o servidor caiu.", + "cardTitle": "A sessão terminou", + "card": "A ligação WebRTC falhou. A conversa até aqui continua na transcrição." + }, + "error": { + "title": "Algo deu errado", + "body": "Não foi possível preparar a chamada ou o servidor informou um erro." + }, + "nopipe": { + "title": "O Talk precisa de um modelo de pipeline", + "body": "Um modelo de pipeline junta um modelo de fala para texto, um modelo de chat e um modelo de texto para fala em um único assistente de voz. Nenhum está configurado ainda. Crie um, ou procure antes os modelos na galeria.", + "create": "Criar um modelo de pipeline", + "gallery": "Explorar a galeria" + } + }, + "detail": { + "tool": "Executando {{name}}.", + "processing": "Elaborando uma resposta.", + "generating": "Escrevendo a resposta.", + "server": "O servidor informou um erro: {{message}}", + "insecure": "O acesso ao microfone exige HTTPS ou localhost.", + "noMic": "Nenhum microfone foi encontrado.", + "failed": "Não foi possível preparar a chamada: {{message}}", + "unknown": "erro desconhecido" + }, + "controls": { + "start": "Iniciar sessão", + "end": "Encerrar sessão", + "testTone": "Tom de teste", + "endTurn": "Terminei de falar", + "diagnostics": "Diagnóstico", + "viewTraces": "Ver traces", + "retry": { + "blocked": "Tentar de novo", + "lost": "Reconectar", + "error": "Tentar de novo" + } + }, + "transcript": { + "title": "Transcrição", + "copy": "Copiar", + "empty": "O que você diz e o que o modelo responde aparece aqui conforme acontece. Não é salvo.", + "emptyNoPipeline": "Nada para mostrar ainda.", + "user": "Você", + "assistant": "Resposta", + "tool_call": "Ferramenta", + "tool_result": "Resultado", + "interrupted": "Resposta interrompida", + "toneRequested": "Tom de teste solicitado" + }, + "settings": { + "title": "Configurações da sessão", + "desc": "Vale quando a sessão começa.", + "close": "Fechar", + "voiceLanguage": "Voz e idioma", + "voice": "Voz", + "voicePlaceholder": "Nome da voz (deixe em branco para o padrão do modelo)", + "language": "Idioma da transcrição", + "languagePlaceholder": "Código do idioma, por exemplo pt (em branco detecta)", + "instructions": "Instruções", + "instructionsPlaceholder": "Instruções de sistema para o modelo", + "instructionsHelp": "Enviadas como as instruções da sessão. Mantenha-as curtas: as respostas são faladas.", + "tools": "Ferramentas", + "manage": "Modo de gerenciamento", + "manageDesc": "Deixe o modelo consultar o LocalAI: modelos, backends e informações do sistema.", + "manageLocked": "Fixo enquanto uma sessão está aberta. Encerre a sessão para alterá-lo.", + "pipeline": "{{name}} em detalhe", + "anyToAny": "Modelo de fala para fala: um único modelo ouve você, responde e fala. Não precisa de um modelo separado de transcrição, de idioma nem de fala.", + "noVad": "Nenhum modelo de detecção de voz está definido. Pressione Terminei de falar a cada turno.", + "editConfig": "Editar configuração do modelo", + "editPipeline": "Editar pipeline", + "slot": { + "vad": "Detecção de voz", + "transcription": "Transcrição", + "llm": "Modelo de linguagem", + "tts": "Fala" + } + }, + "diag": { + "title": "Diagnóstico de áudio", + "waveform": "Forma de onda", + "spectrum": "Espectro (FFT)", + "waiting": "Aguardando estatísticas...", + "peakFreq": "Frequência de pico", + "thd": "THD", + "rms": "Nível RMS", + "sampleRate": "Taxa de amostragem", + "packetsRecv": "Pacotes recebidos", + "packetsLost": "Pacotes perdidos", + "jitter": "Jitter", + "concealed": "Amostras ocultadas" + }, + "toasts": { + "selectModel": "Selecione primeiro um modelo de pipeline.", + "modelsFailed": "Falha ao carregar os modelos de tempo real: {{message}}", + "viewTraces": "Ver traces", + "copied": "Transcrição copiada", + "copyFailed": "Não foi possível copiar para a área de transferência" + } +} diff --git a/addons/talk/ui/src/i18n/talk.zh-CN.json b/addons/talk/ui/src/i18n/talk.zh-CN.json new file mode 100644 index 0000000000..a266ed8a2f --- /dev/null +++ b/addons/talk/ui/src/i18n/talk.zh-CN.json @@ -0,0 +1,149 @@ +{ + "title": "对话", + "subtitle": "实时语音", + "stage": "语音会话", + "picker": { + "title": "语音流水线:听、想、说", + "heading": "流水线模型", + "label": "流水线模型", + "none": "没有流水线", + "defaultVoice": "默认声音", + "auto": "自动" + }, + "view": { + "idle": { + "title": "就绪", + "body": "开始会话,然后说话。只有会话打开时才会使用麦克风。", + "note": "会话进行期间,你的麦克风会持续传输音频。服务器会察觉你停止说话,并自行回答。", + "noteManual": "会话进行期间,你的麦克风会持续传输音频。此模型没有语音检测,因此每轮说完后请按“说完了”。" + }, + "connecting": { + "title": "正在连接", + "body": "正在建立通话。模型还没有在听。" + }, + "listening": { + "title": "正在聆听", + "body": "说出你的需求。你一停下,我就回答。", + "hearing": "正在听你说话。", + "manual": "说出你的需求,然后按“说完了”。" + }, + "interrupted": { + "title": "已打断", + "body": "我已停下。请继续,我在听。" + }, + "thinking": { + "title": "正在思考", + "body": "正在组织回复。" + }, + "speaking": { + "title": "正在说话", + "body": "随时开口即可打断我。" + }, + "blocked": { + "title": "麦克风被阻止", + "body": "浏览器没有向此页面开放麦克风。", + "cardTitle": "允许使用麦克风", + "card": "点击地址栏中的锁形图标,把“麦克风”设为允许,然后重试。没有录下任何内容。" + }, + "lost": { + "title": "连接已断开", + "body": "与服务器的链接中断了。", + "cardTitle": "会话已结束", + "card": "WebRTC 链接失败。到目前为止的对话仍保留在转录中。" + }, + "error": { + "title": "出了点问题", + "body": "无法建立通话,或服务器报告了错误。" + }, + "nopipe": { + "title": "“对话”需要一个流水线模型", + "body": "流水线模型把语音转文字模型、聊天模型和文字转语音模型组合成一个语音助手。目前还没有配置。请创建一个,或先到库中查找这些模型。", + "create": "创建流水线模型", + "gallery": "浏览库" + } + }, + "detail": { + "tool": "正在运行 {{name}}。", + "processing": "正在组织回复。", + "generating": "正在撰写回复。", + "server": "服务器报告了错误:{{message}}", + "insecure": "使用麦克风需要 HTTPS 或 localhost。", + "noMic": "没有找到麦克风。", + "failed": "无法建立通话:{{message}}", + "unknown": "未知错误" + }, + "controls": { + "start": "开始会话", + "end": "结束会话", + "testTone": "测试音", + "endTurn": "说完了", + "diagnostics": "诊断", + "viewTraces": "查看追踪", + "retry": { + "blocked": "重试", + "lost": "重新连接", + "error": "重试" + } + }, + "transcript": { + "title": "转录", + "copy": "复制", + "empty": "你说的话和模型的回答会在发生时显示在这里。不会被保存。", + "emptyNoPipeline": "暂无内容可显示。", + "user": "你", + "assistant": "回复", + "tool_call": "工具", + "tool_result": "结果", + "interrupted": "回复已被打断", + "toneRequested": "已请求测试音" + }, + "settings": { + "title": "会话设置", + "desc": "会话开始时生效。", + "close": "关闭", + "voiceLanguage": "声音和语言", + "voice": "声音", + "voicePlaceholder": "声音名称(留空则使用模型默认值)", + "language": "转录语言", + "languagePlaceholder": "语言代码,例如 en(留空则自动检测)", + "instructions": "指令", + "instructionsPlaceholder": "给模型的系统指令", + "instructionsHelp": "作为会话指令发送。请保持简短:回复是用语音说出来的。", + "tools": "工具", + "manage": "管理模式", + "manageDesc": "让模型查询 LocalAI:模型、后端和系统信息。", + "manageLocked": "会话打开期间无法更改。请先结束会话再修改。", + "pipeline": "{{name}} 详情", + "anyToAny": "语音到语音模型:一个模型负责听、回答和说话。它不需要单独的转录、语言或语音模型。", + "noVad": "未设置语音检测模型。每轮说完后请按“说完了”。", + "editConfig": "编辑模型配置", + "editPipeline": "编辑流水线", + "slot": { + "vad": "语音检测", + "transcription": "转录", + "llm": "语言模型", + "tts": "语音" + } + }, + "diag": { + "title": "音频诊断", + "waveform": "波形", + "spectrum": "频谱 (FFT)", + "waiting": "正在等待统计数据...", + "peakFreq": "峰值频率", + "thd": "THD", + "rms": "RMS 电平", + "sampleRate": "采样率", + "packetsRecv": "已接收数据包", + "packetsLost": "丢失数据包", + "jitter": "抖动", + "concealed": "已补偿的采样" + }, + "toasts": { + "selectModel": "请先选择流水线模型。", + "modelsFailed": "无法加载实时模型:{{message}}", + "viewTraces": "查看追踪", + "copied": "转录已复制", + "copyFailed": "无法复制到剪贴板" + } +} diff --git a/addons/telegram/ui/src/addon.jsx b/addons/telegram/ui/src/addon.jsx index dd24f3280b..db06eec994 100644 --- a/addons/telegram/ui/src/addon.jsx +++ b/addons/telegram/ui/src/addon.jsx @@ -2,8 +2,17 @@ import { RouteProvider } from 'react-router-dom' import { registerLocales } from '../../../internal/host-compat/src/i18n.js' import ConnectorPage from '../../../internal/connector-ui/src/ConnectorPage' import en from './i18n/telegram.en.json' +import it from './i18n/telegram.it.json' +import es from './i18n/telegram.es.json' +import de from './i18n/telegram.de.json' +import fr from './i18n/telegram.fr.json' +import zh from './i18n/telegram.zh-CN.json' +import id from './i18n/telegram.id.json' +import ko from './i18n/telegram.ko.json' +import pt from './i18n/telegram.pt-BR.json' -registerLocales({ telegram: { en } }) +// The words of the page, in every language of the web UI; a key a language lacks reads in English. +registerLocales({ telegram: { en, it, es, de, fr, 'zh-CN': zh, id, ko, 'pt-BR': pt } }) // The page of the Telegram add-on: the shared connector page with one bot // token and one allowlist of chat IDs, user IDs and @usernames. diff --git a/addons/telegram/ui/src/i18n/telegram.de.json b/addons/telegram/ui/src/i18n/telegram.de.json new file mode 100644 index 0000000000..06e524693e --- /dev/null +++ b/addons/telegram/ui/src/i18n/telegram.de.json @@ -0,0 +1,52 @@ +{ + "title": "Telegram", + "intro": "Verbinde einen Telegram-Bot mit einem deiner gespeicherten Agenten. Wer dem Bot schreibt, spricht mit diesem Agenten, in einer eigenen Unterhaltung. Möglich sind Text, Fotos, Dateien und Sprachnachrichten (wie groß, zeigt der Abschnitt Dateien); eine Sprachnachricht wird mit dem Chat-Transkriptionsmodell aus den Einstellungen transkribiert.", + "new": "Neuer Telegram-Bot", + "edit": "Telegram-Bot bearbeiten", + "name": "Name", + "token": "Bot-Token", + "tokenHelp": "Von @BotFather. Es wird versiegelt gespeichert und nie wieder angezeigt.", + "tokenSaved": "Gespeichert. Gib ein neues Token ein, um es zu ersetzen.", + "agent": "Agent", + "agentNone": "Agent wählen", + "agentNoModel": "{{name}} (braucht ein Modell)", + "who": "Wer ihn nutzen darf", + "onlyListed": "Nur die Absender, die ich aufführe", + "anyone": "Alle, die dem Bot schreiben können", + "anyoneWarning": "Wer diesem Bot schreiben kann, kann die Werkzeuge dieses Agenten nutzen, als wärst du es. Jede Person bekommt eine eigene Unterhaltung.", + "anyoneConfirm": "Verstanden", + "allow": "Erlaubte Absender", + "allowHint": "Chat-ID, Nutzer-ID oder @username", + "notifyChat": "Meine Benachrichtigungen an diese Chat-ID senden", + "notifyHelp": "Optional. Eine numerische Telegram-Chat-ID.", + "test": "Test", + "testOk": "Mit @{{bot}} verbunden.", + "checkFailed": "Gespeichert. Die Prüfung des Tokens ist fehlgeschlagen: {{reason}}", + "senders": "Absender", + "noSenders": "Noch hat niemand diesem Bot geschrieben.", + "allowButton": "Erlauben", + "ignored": "Ignoriert", + "accepted": "Akzeptiert", + "save": "Speichern", + "cancel": "Abbrechen", + "editButton": "Bearbeiten", + "delete": "Löschen", + "empty": "Noch keine Telegram-Bots.", + "agentOf": "Agent: {{name}}", + "accepts": "Akzeptiert: {{who}}", + "modes": { + "anyone": "alle", + "nobody": "noch niemand", + "listed_one": "1 aufgeführter Absender", + "listed_other": "{{count}} aufgeführte Absender" + }, + "on": "An", + "onFor": "{{name}} an", + "inactive": "Läuft nicht: {{reason}}.", + "confirmDelete": { + "title": "{{name}} löschen?", + "message": "Der Bot antwortet nicht mehr, und das mit ihm gespeicherte Bot-Token wird gelöscht. Um den Bot erneut einzurichten, musst du das Token noch einmal eingeben.", + "confirm": "Löschen" + }, + "back": "Zurück zu den Konnektoren" +} diff --git a/addons/telegram/ui/src/i18n/telegram.es.json b/addons/telegram/ui/src/i18n/telegram.es.json new file mode 100644 index 0000000000..7cba5ca06c --- /dev/null +++ b/addons/telegram/ui/src/i18n/telegram.es.json @@ -0,0 +1,52 @@ +{ + "title": "Telegram", + "intro": "Conecta un bot de Telegram a uno de tus agentes guardados. Quien escriba al bot habla con ese agente, en una conversación propia. Puede enviar texto, fotos, archivos y mensajes de voz (la sección Archivos muestra el tamaño máximo); un mensaje de voz se transcribe con el modelo de transcripción del chat de la configuración.", + "new": "Nuevo bot de Telegram", + "edit": "Editar bot de Telegram", + "name": "Nombre", + "token": "Token del bot", + "tokenHelp": "De @BotFather. Se guarda cifrado y no se vuelve a mostrar.", + "tokenSaved": "Guardado. Escribe un token nuevo para reemplazarlo.", + "agent": "Agente", + "agentNone": "Elige un agente", + "agentNoModel": "{{name}} (necesita un modelo)", + "who": "Quién puede usarlo", + "onlyListed": "Solo los remitentes que yo añada a la lista", + "anyone": "Cualquiera que pueda escribir al bot", + "anyoneWarning": "Cualquiera que pueda escribir a este bot puede usar las herramientas de este agente, como si fueras tú. Cada persona tiene su propia conversación.", + "anyoneConfirm": "Lo entiendo", + "allow": "Remitentes permitidos", + "allowHint": "ID de chat, ID de usuario o @username", + "notifyChat": "Enviar mis notificaciones a este ID de chat", + "notifyHelp": "Opcional. Un ID de chat numérico de Telegram.", + "test": "Probar", + "testOk": "Conectado a @{{bot}}.", + "checkFailed": "Guardado. Falló la comprobación del token: {{reason}}", + "senders": "Remitentes", + "noSenders": "Nadie ha escrito aún a este bot.", + "allowButton": "Permitir", + "ignored": "Ignorado", + "accepted": "Aceptado", + "save": "Guardar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Eliminar", + "empty": "Aún no hay bots de Telegram.", + "agentOf": "Agente: {{name}}", + "accepts": "Acepta: {{who}}", + "modes": { + "anyone": "cualquiera", + "nobody": "nadie por ahora", + "listed_one": "1 remitente en la lista", + "listed_other": "{{count}} remitentes en la lista" + }, + "on": "Activado", + "onFor": "{{name}} activado", + "inactive": "No se está ejecutando: {{reason}}.", + "confirmDelete": { + "title": "¿Eliminar {{name}}?", + "message": "El bot deja de responder y se elimina el token del bot guardado con él. Para configurar el bot de nuevo tendrás que volver a introducir el token.", + "confirm": "Eliminar" + }, + "back": "Volver a los conectores" +} diff --git a/addons/telegram/ui/src/i18n/telegram.fr.json b/addons/telegram/ui/src/i18n/telegram.fr.json new file mode 100644 index 0000000000..6b85a1ce93 --- /dev/null +++ b/addons/telegram/ui/src/i18n/telegram.fr.json @@ -0,0 +1,52 @@ +{ + "title": "Telegram", + "intro": "Associez un bot Telegram à l’un de vos agents enregistrés. Une personne qui écrit au bot parle à cet agent, dans une conversation qui lui est propre. Elle peut envoyer du texte, des photos, des fichiers et des messages vocaux (la section Fichiers indique la taille maximale) ; un message vocal est transcrit avec le modèle de transcription du chat défini dans les paramètres.", + "new": "Nouveau bot Telegram", + "edit": "Modifier le bot Telegram", + "name": "Nom", + "token": "Jeton du bot", + "tokenHelp": "Fourni par @BotFather. Il est conservé scellé et n’est plus jamais affiché.", + "tokenSaved": "Enregistré. Saisissez un nouveau jeton pour le remplacer.", + "agent": "Agent", + "agentNone": "Choisir un agent", + "agentNoModel": "{{name}} (a besoin d’un modèle)", + "who": "Qui peut l’utiliser", + "onlyListed": "Seulement les expéditeurs que j’indique", + "anyone": "Toute personne pouvant écrire au bot", + "anyoneWarning": "Toute personne pouvant écrire à ce bot peut utiliser les outils de cet agent, en votre nom. Chaque personne a sa propre conversation.", + "anyoneConfirm": "J’ai compris", + "allow": "Expéditeurs autorisés", + "allowHint": "ID de chat, ID d’utilisateur ou @username", + "notifyChat": "Envoyer mes notifications à cet ID de chat", + "notifyHelp": "Facultatif. Un ID de chat Telegram numérique.", + "test": "Tester", + "testOk": "Connecté à @{{bot}}.", + "checkFailed": "Enregistré. La vérification du jeton a échoué : {{reason}}", + "senders": "Expéditeurs", + "noSenders": "Personne n’a encore écrit à ce bot.", + "allowButton": "Autoriser", + "ignored": "Ignoré", + "accepted": "Accepté", + "save": "Enregistrer", + "cancel": "Annuler", + "editButton": "Modifier", + "delete": "Supprimer", + "empty": "Aucun bot Telegram pour le moment.", + "agentOf": "Agent : {{name}}", + "accepts": "Accepte : {{who}}", + "modes": { + "anyone": "tout le monde", + "nobody": "personne pour l’instant", + "listed_one": "1 expéditeur dans la liste", + "listed_other": "{{count}} expéditeurs dans la liste" + }, + "on": "Activé", + "onFor": "{{name}} activé", + "inactive": "À l’arrêt : {{reason}}.", + "confirmDelete": { + "title": "Supprimer {{name}} ?", + "message": "Le bot cesse de répondre, et le jeton enregistré avec lui est supprimé. Pour le reconfigurer, vous devrez saisir de nouveau le jeton.", + "confirm": "Supprimer" + }, + "back": "Retour aux connecteurs" +} diff --git a/addons/telegram/ui/src/i18n/telegram.id.json b/addons/telegram/ui/src/i18n/telegram.id.json new file mode 100644 index 0000000000..acc04ab338 --- /dev/null +++ b/addons/telegram/ui/src/i18n/telegram.id.json @@ -0,0 +1,52 @@ +{ + "title": "Telegram", + "intro": "Hubungkan bot Telegram ke salah satu agen tersimpan Anda. Orang yang mengirim pesan ke bot berbicara dengan agen itu, dalam percakapan tersendiri. Mereka dapat mengirim teks, foto, file, dan pesan suara (bagian File menunjukkan batas ukurannya); pesan suara ditranskripsikan dengan model transkripsi Chat di pengaturan.", + "new": "Bot Telegram baru", + "edit": "Ubah bot Telegram", + "name": "Nama", + "token": "Token bot", + "tokenHelp": "Dari @BotFather. Disimpan terenkripsi dan tidak pernah ditampilkan lagi.", + "tokenSaved": "Tersimpan. Ketik token baru untuk menggantinya.", + "agent": "Agen", + "agentNone": "Pilih agen", + "agentNoModel": "{{name}} (perlu model)", + "who": "Siapa yang boleh memakai", + "onlyListed": "Hanya pengirim yang saya daftarkan", + "anyone": "Siapa saja yang dapat mengirim pesan ke bot", + "anyoneWarning": "Siapa saja yang dapat mengirim pesan ke bot ini dapat memakai alat agen ini, sebagai Anda. Setiap orang mendapat percakapan tersendiri.", + "anyoneConfirm": "Saya mengerti", + "allow": "Pengirim yang diizinkan", + "allowHint": "ID chat, ID pengguna, atau @username", + "notifyChat": "Kirim notifikasi saya ke ID chat ini", + "notifyHelp": "Opsional. ID chat Telegram berupa angka.", + "test": "Uji", + "testOk": "Terhubung ke @{{bot}}.", + "checkFailed": "Tersimpan. Pemeriksaan token gagal: {{reason}}", + "senders": "Pengirim", + "noSenders": "Belum ada yang menulis ke bot ini.", + "allowButton": "Izinkan", + "ignored": "Diabaikan", + "accepted": "Diterima", + "save": "Simpan", + "cancel": "Batal", + "editButton": "Ubah", + "delete": "Hapus", + "empty": "Belum ada bot Telegram.", + "agentOf": "Agen: {{name}}", + "accepts": "Menerima: {{who}}", + "modes": { + "anyone": "siapa saja", + "nobody": "belum ada", + "listed_one": "1 pengirim terdaftar", + "listed_other": "{{count}} pengirim terdaftar" + }, + "on": "Aktif", + "onFor": "{{name}} aktif", + "inactive": "Tidak berjalan: {{reason}}.", + "confirmDelete": { + "title": "Hapus {{name}}?", + "message": "Bot berhenti menjawab, dan token bot yang tersimpan bersamanya dihapus. Untuk menyiapkan bot lagi, Anda harus memasukkan token lagi.", + "confirm": "Hapus" + }, + "back": "Kembali ke konektor" +} diff --git a/addons/telegram/ui/src/i18n/telegram.it.json b/addons/telegram/ui/src/i18n/telegram.it.json new file mode 100644 index 0000000000..2e162a1c8c --- /dev/null +++ b/addons/telegram/ui/src/i18n/telegram.it.json @@ -0,0 +1,52 @@ +{ + "title": "Telegram", + "intro": "Collega un bot Telegram a uno dei tuoi agenti salvati. Chi scrive al bot parla con quell'agente, in una conversazione tutta sua. Può inviare testo, foto, file e messaggi vocali (la sezione File indica la dimensione massima); un messaggio vocale viene trascritto con il modello di trascrizione delle chat nelle impostazioni.", + "new": "Nuovo bot Telegram", + "edit": "Modifica il bot Telegram", + "name": "Nome", + "token": "Token del bot", + "tokenHelp": "Da @BotFather. Viene cifrato e non viene più mostrato.", + "tokenSaved": "Salvato. Scrivi per sostituirlo.", + "agent": "Agente", + "agentNone": "Scegli un agente", + "agentNoModel": "{{name}} (serve un modello)", + "who": "Chi può usarlo", + "onlyListed": "Solo i mittenti che elenco", + "anyone": "Chiunque possa scrivere al bot", + "anyoneWarning": "Chiunque possa scrivere a questo bot può usare gli strumenti di questo agente, come te. Ogni persona ha una conversazione tutta sua.", + "anyoneConfirm": "Ho capito", + "allow": "Mittenti consentiti", + "allowHint": "ID della chat, ID utente o @nomeutente", + "notifyChat": "Invia le mie notifiche a questo ID di chat", + "notifyHelp": "Facoltativo. Un ID di chat Telegram numerico.", + "test": "Prova", + "testOk": "Connesso a @{{bot}}.", + "checkFailed": "Salvato. La verifica del token non è riuscita: {{reason}}", + "senders": "Mittenti", + "noSenders": "Nessuno ha ancora scritto a questo bot.", + "allowButton": "Consenti", + "ignored": "Ignorato", + "accepted": "Accettato", + "save": "Salva", + "cancel": "Annulla", + "editButton": "Modifica", + "delete": "Elimina", + "empty": "Ancora nessun bot Telegram.", + "agentOf": "Agente: {{name}}", + "accepts": "Accetta: {{who}}", + "modes": { + "anyone": "chiunque", + "nobody": "nessuno per ora", + "listed_one": "1 mittente in elenco", + "listed_other": "{{count}} mittenti in elenco" + }, + "on": "Attivo", + "onFor": "{{name}} attivo", + "inactive": "Non in esecuzione: {{reason}}.", + "confirmDelete": { + "title": "Eliminare {{name}}?", + "message": "Il bot smette di rispondere e il token salvato con lui viene eliminato. Per configurarlo di nuovo dovrai inserire di nuovo il token.", + "confirm": "Elimina" + }, + "back": "Torna ai connettori" +} diff --git a/addons/telegram/ui/src/i18n/telegram.ko.json b/addons/telegram/ui/src/i18n/telegram.ko.json new file mode 100644 index 0000000000..a1a31b46a2 --- /dev/null +++ b/addons/telegram/ui/src/i18n/telegram.ko.json @@ -0,0 +1,52 @@ +{ + "title": "Telegram", + "intro": "Telegram 봇을 저장된 에이전트 중 하나에 연결하세요. 봇에 메시지를 보낸 사람은 그 에이전트와 자신만의 대화를 나눕니다. 텍스트, 사진, 파일, 음성 메시지를 보낼 수 있으며(크기는 파일 섹션에서 확인), 음성 메시지는 설정의 채팅 받아쓰기 모델로 텍스트로 변환됩니다.", + "new": "새 Telegram 봇", + "edit": "Telegram 봇 편집", + "name": "이름", + "token": "봇 토큰", + "tokenHelp": "@BotFather에서 받습니다. 봉인되어 보관되며 다시 표시되지 않습니다.", + "tokenSaved": "저장됨. 교체하려면 새 토큰을 입력하세요.", + "agent": "에이전트", + "agentNone": "에이전트 고르기", + "agentNoModel": "{{name}}(모델 필요)", + "who": "사용할 수 있는 사람", + "onlyListed": "내가 나열한 보낸 사람만", + "anyone": "봇에 메시지를 보낼 수 있는 모든 사람", + "anyoneWarning": "이 봇에 메시지를 보낼 수 있는 사람은 누구나 이 에이전트의 도구를 내 권한으로 사용할 수 있습니다. 사람마다 자신만의 대화가 생깁니다.", + "anyoneConfirm": "이해했습니다", + "allow": "허용된 보낸 사람", + "allowHint": "채팅 ID, 사용자 ID 또는 @username", + "notifyChat": "내 알림을 이 채팅 ID로 보내기", + "notifyHelp": "선택 사항입니다. 숫자로 된 Telegram 채팅 ID입니다.", + "test": "테스트", + "testOk": "@{{bot}}에 연결되었습니다.", + "checkFailed": "저장했습니다. 토큰 확인에 실패했습니다: {{reason}}", + "senders": "보낸 사람", + "noSenders": "아직 이 봇에 메시지를 보낸 사람이 없습니다.", + "allowButton": "허용", + "ignored": "무시됨", + "accepted": "수락됨", + "save": "저장", + "cancel": "취소", + "editButton": "편집", + "delete": "삭제", + "empty": "아직 Telegram 봇이 없습니다.", + "agentOf": "에이전트: {{name}}", + "accepts": "수락: {{who}}", + "modes": { + "anyone": "모든 사람", + "nobody": "아직 없음", + "listed_one": "나열된 보낸 사람 1명", + "listed_other": "나열된 보낸 사람 {{count}}명" + }, + "on": "켜짐", + "onFor": "{{name}} 켜짐", + "inactive": "실행 중이 아님: {{reason}}.", + "confirmDelete": { + "title": "{{name}}을(를) 삭제할까요?", + "message": "봇이 답변을 멈추고 함께 저장된 봇 토큰이 삭제됩니다. 봇을 다시 설정하려면 토큰을 다시 입력해야 합니다.", + "confirm": "삭제" + }, + "back": "커넥터로 돌아가기" +} diff --git a/addons/telegram/ui/src/i18n/telegram.pt-BR.json b/addons/telegram/ui/src/i18n/telegram.pt-BR.json new file mode 100644 index 0000000000..31bca00a8f --- /dev/null +++ b/addons/telegram/ui/src/i18n/telegram.pt-BR.json @@ -0,0 +1,52 @@ +{ + "title": "Telegram", + "intro": "Vincule um bot do Telegram a um dos seus agentes salvos. Quem enviar mensagem ao bot conversa com esse agente, em uma conversa própria. Pode enviar texto, fotos, arquivos e mensagens de voz (a seção Arquivos mostra o tamanho máximo); a mensagem de voz é transcrita com o modelo de transcrição do chat definido nas configurações.", + "new": "Novo bot do Telegram", + "edit": "Editar bot do Telegram", + "name": "Nome", + "token": "Token do bot", + "tokenHelp": "Vem do @BotFather. É guardado com segurança e nunca mais é mostrado.", + "tokenSaved": "Salvo. Digite um novo token para substituí-lo.", + "agent": "Agente", + "agentNone": "Escolha um agente", + "agentNoModel": "{{name}} (precisa de um modelo)", + "who": "Quem pode usar", + "onlyListed": "Só os remetentes que eu listar", + "anyone": "Qualquer pessoa que possa enviar mensagem ao bot", + "anyoneWarning": "Qualquer pessoa que possa enviar mensagem a este bot pode usar as ferramentas deste agente, como se fosse você. Cada pessoa tem uma conversa própria.", + "anyoneConfirm": "Entendi", + "allow": "Remetentes permitidos", + "allowHint": "ID do chat, ID do usuário ou @username", + "notifyChat": "Enviar minhas notificações para este ID de chat", + "notifyHelp": "Opcional. Um ID numérico de chat do Telegram.", + "test": "Testar", + "testOk": "Conectado a @{{bot}}.", + "checkFailed": "Salvo. A verificação do token falhou: {{reason}}", + "senders": "Remetentes", + "noSenders": "Ninguém escreveu para este bot ainda.", + "allowButton": "Permitir", + "ignored": "Ignorado", + "accepted": "Aceito", + "save": "Salvar", + "cancel": "Cancelar", + "editButton": "Editar", + "delete": "Excluir", + "empty": "Ainda não há bots do Telegram.", + "agentOf": "Agente: {{name}}", + "accepts": "Aceita: {{who}}", + "modes": { + "anyone": "qualquer pessoa", + "nobody": "ninguém ainda", + "listed_one": "1 remetente na lista", + "listed_other": "{{count}} remetentes na lista" + }, + "on": "Ligado", + "onFor": "{{name}} ligado", + "inactive": "Não está rodando: {{reason}}.", + "confirmDelete": { + "title": "Excluir {{name}}?", + "message": "O bot deixa de responder, e o token do bot salvo com ele é excluído. Para configurar o bot de novo, você precisará informar o token outra vez.", + "confirm": "Excluir" + }, + "back": "Voltar aos conectores" +} diff --git a/addons/telegram/ui/src/i18n/telegram.zh-CN.json b/addons/telegram/ui/src/i18n/telegram.zh-CN.json new file mode 100644 index 0000000000..d1d799a71a --- /dev/null +++ b/addons/telegram/ui/src/i18n/telegram.zh-CN.json @@ -0,0 +1,52 @@ +{ + "title": "Telegram", + "intro": "把 Telegram 机器人绑定到你已保存的某个智能体。向机器人发消息的人会与该智能体交谈,每人有自己的对话。他们可以发送文字、照片、文件和语音消息(“文件”部分会显示大小上限);语音消息由设置中的聊天转录模型转写。", + "new": "新建 Telegram 机器人", + "edit": "编辑 Telegram 机器人", + "name": "名称", + "token": "机器人 token", + "tokenHelp": "来自 @BotFather。它会被加密保存,之后不再显示。", + "tokenSaved": "已保存。输入新的 token 可替换它。", + "agent": "智能体", + "agentNone": "选择智能体", + "agentNoModel": "{{name}}(需要模型)", + "who": "谁可以使用", + "onlyListed": "仅我列出的发送者", + "anyone": "任何能给机器人发消息的人", + "anyoneWarning": "任何能给此机器人发消息的人,都可以以你的身份使用此智能体的工具。每个人有自己的对话。", + "anyoneConfirm": "我已了解", + "allow": "允许的发送者", + "allowHint": "聊天 ID、用户 ID 或 @username", + "notifyChat": "把我的通知发送到此聊天 ID", + "notifyHelp": "可选。Telegram 聊天的数字 ID。", + "test": "测试", + "testOk": "已连接到 @{{bot}}。", + "checkFailed": "已保存。token 检查失败:{{reason}}", + "senders": "发送者", + "noSenders": "还没有人给此机器人发过消息。", + "allowButton": "允许", + "ignored": "已忽略", + "accepted": "已接受", + "save": "保存", + "cancel": "取消", + "editButton": "编辑", + "delete": "删除", + "empty": "还没有 Telegram 机器人。", + "agentOf": "智能体:{{name}}", + "accepts": "接受:{{who}}", + "modes": { + "anyone": "任何人", + "nobody": "暂无", + "listed_one": "1 个已列出的发送者", + "listed_other": "{{count}} 个已列出的发送者" + }, + "on": "已开启", + "onFor": "{{name}} 已开启", + "inactive": "未运行:{{reason}}。", + "confirmDelete": { + "title": "删除 {{name}}?", + "message": "机器人将停止回答,随之保存的机器人 token 也会被删除。若要重新设置机器人,你必须再次输入 token。", + "confirm": "删除" + }, + "back": "返回连接器" +} diff --git a/addons/telegram/ui/test/locale.test.mjs b/addons/telegram/ui/test/locale.test.mjs new file mode 100644 index 0000000000..ca4aaa72c8 --- /dev/null +++ b/addons/telegram/ui/test/locale.test.mjs @@ -0,0 +1,29 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { clearLocales, registerLocales, translator } from '../../../internal/host-compat/src/i18n.js' +import { readFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +// The shared connector page: its words are JSON bundles in every language of +// the web UI, read through the same translator (namespace "connector"). + +const connectorDir = join(dirname(fileURLToPath(import.meta.url)), '../../../internal/connector-ui/src/i18n') +const connector = (lng) => JSON.parse(readFileSync(join(connectorDir, `connector.${lng}.json`), 'utf8')) + +test('connector words: Italian wins, plural forms follow the language, and a missing key reads in English', () => { + const it = connector('it') + const gap = 'ready.title' + delete it[gap] + clearLocales() + registerLocales({ connector: { en: connector('en'), it, 'zh-CN': connector('zh-CN') }, slack: { en: { title: 'Slack' } } }) + const t = translator('it', ['slack', 'connector']) + assert.equal(t('ready.who.listed', { count: 1 }), '1 persona o chat è consentita.') + assert.equal(t('ready.who.listed', { count: 3 }), '3 persone o chat sono consentite.') + assert.equal(t('can.title'), 'Cosa può fare questo bot') + assert.equal(t(gap), 'Readiness', 'a key the Italian bundle lacks reads in English') + assert.equal(t('title'), 'Slack', 'the namespace of the add-on is read first') + const zh = translator('zh-CN', ['connector']) + assert.ok(zh('ready.who.listed', { count: 1 }).includes('1'), 'a language with one plural form still gets the count') + assert.equal(translator('sv', ['connector'])('can.title'), 'What this bot can do', 'a language without a bundle reads in English') +}) diff --git a/core/http/react-ui/e2e/addon-locale.spec.js b/core/http/react-ui/e2e/addon-locale.spec.js new file mode 100644 index 0000000000..f3ea1473f6 --- /dev/null +++ b/core/http/react-ui/e2e/addon-locale.spec.js @@ -0,0 +1,203 @@ +import { test, expect } from './coverage-fixtures.js' +import { mockChat } from './chat-fixtures.js' +import { installStudioAddons } from './studio-addon.js' +import { connectorApi, ROLES_KIND } from './connector-fixtures.js' + +// The pages of the add-ons follow the language of the web UI. The words of the +// shared connector page and of each add-on page are bundles of the add-on, in +// every language of the web UI; a key a bundle lacks reads in English. These +// specs set the language to Italian, draw the real built bundles with the core +// API stubbed, and check a few translated words. With UI_SHOTS_DIR set, they +// also save screenshots (light and dark, wide and phone) for a visual review. + +const SHOTS = process.env.UI_SHOTS_DIR +const SLACK = '/app/addons/slack/apps' +const SAVED = { + id: 'b1', name: 'Team app', kind: 'slack', agent_id: 'a1', enabled: true, accept_any: false, allow: ['U001'], has_token: true, + caps: ['memory.write', 'files.upload', 'triggers.propose', 'talkback.channels'], + grants: [{ role: 'workspace_admin', caps: ['memory.write'] }], channels: [{ id: 'C0123', label: 'ops', approved: true }], + paired: [{ sender_id: 'U001', paired_at: '2026-11-01T10:00:00Z', expires_at: '2027-01-30T10:00:00Z' }], + approval: { mode: 'ask', acts: ['act.files'] }, +} +const VIEWS = [ + { name: 'wide', width: 1280, height: 900 }, + { name: 'phone', width: 390, height: 844 }, +] + +async function setLanguage(page, lng) { + await page.addInitScript((l) => localStorage.setItem('localai-language', l), lng) +} + +// The elements inside the box that run past its right edge or clip their own +// text. A long Italian word that does not fit shows up here. +function overflowing(box) { + return box.evaluate((root) => { + const edge = root.getBoundingClientRect().right + const out = [] + for (const el of root.querySelectorAll('*')) { + const r = el.getBoundingClientRect() + if (r.width <= 1 || r.height <= 1 || el.closest('.cn-vh')) continue + const style = getComputedStyle(el) + const scrolls = ['auto', 'scroll'].includes(style.overflowX) + const clipped = !scrolls && el.scrollWidth > el.clientWidth + 1 && style.display !== 'inline' && !['INPUT', 'SELECT', 'TEXTAREA', 'svg'].includes(el.tagName) + if (r.right > edge + 1 || clipped) out.push(`${el.tagName.toLowerCase()}.${String(el.className).split(' ')[0]} "${(el.textContent || '').trim().slice(0, 40)}"`) + } + return out + }) +} + +// Saves a screenshot of the element in each theme and size, and fails when +// something in it runs sideways out of its box. +async function shots(page, name, locator) { + for (const theme of ['light', 'dark']) { + await page.emulateMedia({ colorScheme: theme }) + await page.evaluate((t) => { document.documentElement.setAttribute('data-theme', t) }, theme) + for (const v of VIEWS) { + await page.setViewportSize({ width: v.width, height: v.height }) + await locator.scrollIntoViewIfNeeded() + expect(await overflowing(locator), `${name} ${theme} ${v.name}: text runs out of its box`).toEqual([]) + if (SHOTS) await locator.screenshot({ path: `${SHOTS}/${name}-${theme}-${v.name}.png` }) + } + } +} + +test.describe('Add-on pages in Italian', () => { + test.beforeEach(async ({ page }) => { + await setLanguage(page, 'it') + await mockChat(page) + await installStudioAddons(page, { ids: ['slack'] }) + }) + + test('the connection page draws the shared words in Italian, with a name that stays in English', async ({ page }) => { + const a = connectorApi(page, [SAVED], 'slack', [], undefined, { kindExtra: ROLES_KIND }) + await a.install() + await page.goto(SLACK) + // The words of the add-on itself. + await expect(page.getByRole('button', { name: 'Nuova app Slack' })).toBeVisible() + await page.getByRole('button', { name: 'Modifica', exact: true }).click() + await expect(page.getByLabel('Token del bot')).toBeVisible() + // The shared words: readiness, what the bot can do, permissions, approval, pairing. + await expect(page.getByTestId('connector-readiness')).toContainText('Stato di preparazione') + await page.getByTestId('connector-ready-toggle').click() + await expect(page.getByTestId('connector-ready-toggle')).toContainText('Mostra solo ciò che richiede attenzione') + await expect(page.getByTestId('connector-ready-who')).toContainText('Chi può usarlo') + await expect(page.getByTestId('connector-ready-who')).toContainText('1 persona o chat è consentita.') + await expect(page.getByTestId('connector-can')).toContainText('Cosa può fare questo bot') + await expect(page.getByTestId('connector-permissions')).toContainText('Cosa possono fare i mittenti') + await expect(page.getByTestId('connector-pairing')).toContainText('Mittenti abbinati') + await expect(page.getByTestId('connector-pair-start')).toHaveText('Abbina un mittente') + await expect(page.getByTestId('connector-approval')).toContainText('Le esecuzioni possono agire') + // No English left in the words of the form that the bundles own. + await expect(page.getByTestId('connector-form')).not.toContainText('What senders may do') + await expect(page.getByTestId('connector-form')).not.toContainText('Paired senders') + await expect(page.getByTestId('connector-form')).not.toContainText('Readiness') + }) + + test('one person is singular and two are plural', async ({ page }) => { + const a = connectorApi(page, [{ ...SAVED, allow: ['U001', 'U002'] }], 'slack', [], undefined, { kindExtra: ROLES_KIND }) + await a.install() + await page.goto(SLACK) + await page.getByRole('button', { name: 'Modifica', exact: true }).click() + await page.getByTestId('connector-ready-toggle').click() + await expect(page.getByTestId('connector-ready-who')).toContainText('2 persone o chat sono consentite.') + }) + + test('screenshots of the connection page', async ({ page }) => { + const a = connectorApi(page, [SAVED], 'slack', [], undefined, { kindExtra: ROLES_KIND }) + await a.install() + await page.goto(SLACK) + await page.getByRole('button', { name: 'Modifica', exact: true }).click() + await page.getByTestId('connector-ready-toggle').click() + await expect(page.getByTestId('connector-ready-who')).toBeVisible() + await expect(page.getByTestId('connector-can-memory.write')).toBeVisible() + await shots(page, 'it-connection', page.getByTestId('connector-form')) + await shots(page, 'it-readiness', page.getByTestId('connector-readiness')) + await shots(page, 'it-can', page.getByTestId('connector-can')) + await shots(page, 'it-permissions', page.getByTestId('connector-permissions')) + await shots(page, 'it-approval', page.getByTestId('connector-approval')) + await shots(page, 'it-pairing', page.getByTestId('connector-pairing')) + }) + + test('a new connection shows the states that need attention, in Italian', async ({ page }) => { + await connectorApi(page, [], 'slack', [], undefined, { kindExtra: ROLES_KIND }).install() + await page.goto(SLACK) + await page.getByRole('button', { name: 'Nuova app Slack' }).click() + await expect(page.getByTestId('connector-ready-token')).toContainText('Richiede attenzione') + await expect(page.getByTestId('connector-ready-agent')).toContainText('Scegli l\'agente che risponde.') + await shots(page, 'it-readiness-new', page.getByTestId('connector-readiness')) + await shots(page, 'it-form-new', page.getByTestId('connector-form')) + }) +}) + +test.describe('Add-on pages follow the language', () => { + for (const lng of ['de', 'es', 'fr', 'id', 'ko', 'pt-BR', 'zh-CN']) { + test(`the connection page in ${lng} has no text running out of its box on a phone`, async ({ page }) => { + await setLanguage(page, lng) + await mockChat(page) + await installStudioAddons(page, { ids: ['slack'] }) + await connectorApi(page, [SAVED], 'slack', [], undefined, { kindExtra: ROLES_KIND }).install() + await page.setViewportSize({ width: 390, height: 844 }) + await page.goto(SLACK) + await page.locator('.cn-row__actions .dk-btn').nth(2).click() + await page.getByTestId('connector-ready-toggle').click() + await expect(page.getByTestId('connector-pairing')).toBeVisible() + expect(await overflowing(page.getByTestId('connector-form')), lng).toEqual([]) + }) + } + + + test('a language the bundles lack reads in English', async ({ page }) => { + await setLanguage(page, 'sv') + await mockChat(page) + await installStudioAddons(page, { ids: ['slack'] }) + await connectorApi(page, [SAVED], 'slack', [], undefined, { kindExtra: ROLES_KIND }).install() + await page.goto(SLACK) + await expect(page.getByRole('button', { name: 'New Slack app' })).toBeVisible() + }) +}) + +// The Jellyfin and Home Assistant pages, with the core API and the add-on tools stubbed. +const ADDRESS_ONLY = { files: undefined, agent_optional: true } +const CREDENTIALS = (kind, resource) => ({ id: 'b1', name: kind === 'jellyfin' ? 'Salotto' : 'Casa', kind, agent_id: '', credentials_only: true, enabled: false, accept_any: false, allow: [], resources: [resource], write_mode: 'propose', has_token: true }) +const POLICY = { allow: ['light.turn_on', 'light.turn_off', 'climate.set_temperature', 'script.turn_on'], excludeDomains: ['lock', 'camera'], excludeAreas: [], excludePatterns: [], excludeLabel: 'sensitive', maxTargets: 5, pins: [] } +const HARD = { domains: ['lock', 'alarm_control_panel', 'siren'], serviceNames: ['restart', 'stop'], servicePrefixes: ['reload'], coverClasses: ['blind'] } +const dispatch = (body) => { + const a = body.arguments || {} + const ok = (data) => ({ status: 200, contentType: 'application/json', body: JSON.stringify({ payload: { data } }) }) + if (a.tool === 'get-policy') return ok({ policy: POLICY, defaults: POLICY, admin: true, broken: false, hard: HARD, physical: ['climate', 'script'] }) + if (a.tool === 'get-config') return ok({ ok: true, version: '2026.11.0', name: 'Casa', integrations: 12 }) + return null +} + +test.describe('Jellyfin and Home Assistant in Italian', () => { + test.beforeEach(async ({ page }) => { + await setLanguage(page, 'it') + await mockChat(page) + }) + + test('the Jellyfin page', async ({ page }) => { + await installStudioAddons(page, { ids: ['jellyfin'], dispatch }) + await connectorApi(page, [CREDENTIALS('jellyfin', 'http://192.168.1.10:8096')], 'jellyfin', [], undefined, { capabilities: [], kindExtra: { title: 'Jellyfin', ...ADDRESS_ONLY } }).install() + await page.goto('/app/addons/jellyfin/jellyfin') + await expect(page.getByRole('button', { name: 'Nuova connessione Jellyfin' })).toBeVisible() + await expect(page.getByTestId('connector-row')).toContainText('Solo credenziali') + await expect(page.locator('.cn-page')).toContainText('Come creare la chiave') + await shots(page, 'it-jellyfin', page.locator('.cn-page')) + await page.getByRole('button', { name: 'Modifica', exact: true }).click() + await expect(page.getByLabel('Chiave API')).toBeVisible() + await shots(page, 'it-jellyfin-form', page.getByTestId('connector-form')) + }) + + test('the Home Assistant page', async ({ page }) => { + await installStudioAddons(page, { ids: ['home-assistant'], dispatch }) + await connectorApi(page, [CREDENTIALS('home-assistant', 'https://ha.example.org:8123')], 'home-assistant', [], undefined, { capabilities: [], kindExtra: { title: 'Home Assistant', ...ADDRESS_ONLY } }).install() + await page.goto('/app/addons/home-assistant/apps') + await expect(page.getByRole('button', { name: 'Nuova connessione Home Assistant' })).toBeVisible() + await page.getByRole('button', { name: 'Prova' }).click() + await expect(page.getByTestId('home-assistant-checks')).toContainText('Raggiungibile. Home Assistant 2026.11.0, 12 integrazioni.') + await expect(page.getByTestId('home-assistant-policy')).toContainText('Cosa possono fare le chat nella tua casa') + await shots(page, 'it-home-assistant', page.locator('.cn-page')) + await page.getByRole('button', { name: 'Modifica', exact: true }).click() + await shots(page, 'it-home-assistant-form', page.getByTestId('connector-form')) + }) +}) diff --git a/core/http/react-ui/e2e/studio-addon.js b/core/http/react-ui/e2e/studio-addon.js index 6ba3c10578..15a2a3ca58 100644 --- a/core/http/react-ui/e2e/studio-addon.js +++ b/core/http/react-ui/e2e/studio-addon.js @@ -25,7 +25,7 @@ export async function buildAddonBundles(ids) { // The add-ons the specs serve from their build output: Studio, Talk and Voice // through installStudioAddons, Face and Fine-tuning through their own routes. -export async function buildStudioAddons(ids = ['studio', 'talk', 'voice', 'face', 'fine-tuning', 'telegram', 'slack', 'github', 'discord', 'home-assistant']) { +export async function buildStudioAddons(ids = ['studio', 'talk', 'voice', 'face', 'fine-tuning', 'telegram', 'slack', 'github', 'discord', 'home-assistant', 'jellyfin']) { await buildAddonBundles(ids) process.env[BUILT] = '1' } @@ -55,7 +55,7 @@ export function packagedApp(id, over = {}) { export async function installStudioAddons(page, { ids = ['studio', 'talk'], ...options } = {}) { const dirs = await Promise.all(ids.map(build)) await stubAddonRoutes(page) - await page.route(/\/api\/addons\/(studio|talk|voice|telegram|slack|github|discord|home-assistant)\/assets\//, route => { + await page.route(/\/api\/addons\/(studio|talk|voice|telegram|slack|github|discord|home-assistant|jellyfin)\/assets\//, route => { const parts = new URL(route.request().url()).pathname.split('/') const id = parts[3] const file = parts.slice(6).join('/') diff --git a/docs/content/features/addons.md b/docs/content/features/addons.md index af0ec5cb0f..3993250466 100644 --- a/docs/content/features/addons.md +++ b/docs/content/features/addons.md @@ -4215,8 +4215,8 @@ The Talk add-on (`talk`) is a packaged add-on, separate from Studio. Install it users with the `addons` feature. The page opens a WebRTC session to a realtime pipeline through `/v1/realtime/calls` and reads the pipeline list from `/api/pipeline-models`, with the signed-in user's own session; the core keeps both routes. The page uses the MCP servers -that the person added in the browser, the same list as Chat. The words are the English `talk` locale -file of the web UI; other languages read the English text. +that the person added in the browser, the same list as Chat. The words are the `talk` locale +files of the add-on, in every language of the web UI. ### The Voice add-on @@ -4831,6 +4831,21 @@ history, message or send hook, and an add-on cannot intercept submission or over route. An action that is undeclared, duplicated, without a label or without `open` is ignored; an error inside `open` never reaches the host. +#### Translating an add-on page + +An add-on page reads its words from JSON bundles named `..json` in +`addons//ui/src/i18n/`, one for English and one for each language of the web UI +(`core/http/react-ui/public/locales`). The page follows the language of the web UI, and a key that a bundle +lacks reads in English. The connector pages (Telegram, Slack, Discord, GitHub, Jellyfin, Home Assistant) +also share the bundles in `addons/internal/connector-ui/src/i18n/`, which hold the readiness list, the +permissions, the approval, the pairing and the file limits. A bundle uses `{{name}}` placeholders and +`_one` / `_other` plural keys. Do not translate product names, identifiers, capability keys or code. + +`node addons/internal/i18ncheck/check.mjs` (part of `make test-i18n` and of the UI unit tests in CI) fails +when a language lacks a bundle, a bundle lacks a key of English or has an extra one, a placeholder differs +from English, or a value is empty. There is no baseline: add a key to English and every language in the +same change. + Compile an add-on with classic JSX transformed to `React.createElement` over `sdk.React`, inside the `createAddon` factory. Do not import a JSX runtime. The fixture packages under `tests/addons/fixture/ui` are written in that form.