mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 04:23:15 -05:00
feat(chats): translate capability titles and refusal texts by code
The access view, the preview and the kinds call returned English text
for each capability and each refusal. They now add the same text in the
language of the request (lang query, then Accept-Language), in new
localized_* fields. Codes, English fields and the model-facing text stay
as they were.
The texts live in the new connectortext package, one JSON file per
language (it, de, es, fr, id, ko, pt-BR, zh-CN), keyed by capability
and by refusal code. A test fails when a capability or a code lacks a
text in any language, or when {action} is lost.
The shared connector page asks for the page language and shows the
localized fields, with English as the fallback for each field.
Assisted-by: Claude:claude-sonnet-5-5
This commit is contained in:
25 files changed
+971
-21
No files matched your search
@@ -18,7 +18,7 @@ import FilesFields from './FilesFields'
|
||||
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 { agentsApi, connectorsApi, setApiLanguage } from './api'
|
||||
import { canSave, channelOnly, connectorForm, hasNoCaps, connectorPayload, emptyConnector, listEntries, replaceList, senderLine, usualCaps } from './connector'
|
||||
import { registerConnectorLocales } from './i18n'
|
||||
import './connector.css'
|
||||
@@ -90,7 +90,9 @@ const ONE_LIST = [{ key: 'allow', label: 'allow', hint: 'allowHint', testId: 'co
|
||||
*/
|
||||
export default function ConnectorPage({ kind, ns, testId, TokenFields = SingleToken, tokenOf = (f) => f.token || '', lists = ONE_LIST, Help, notifyMode = 'text',
|
||||
messaging = true, ExtraFields, extraBody, extraForm, extraValid, testExtra, RowInfo }) {
|
||||
const { t } = useTranslation([ns, 'connector'])
|
||||
const { t, i18n } = useTranslation([ns, 'connector'])
|
||||
const uiLanguage = i18n?.resolvedLanguage || i18n?.language || ''
|
||||
setApiLanguage(uiLanguage)
|
||||
const [items, setItems] = useState([])
|
||||
const [agents, setAgents] = useState([])
|
||||
const [off, setOff] = useState('')
|
||||
@@ -135,7 +137,14 @@ export default function ConnectorPage({ kind, ns, testId, TokenFields = SingleTo
|
||||
setKindInfo((r.kinds || []).find((k) => k.kind === kind) || null)
|
||||
setCapabilities(r.capabilities || [])
|
||||
}).catch(() => {})
|
||||
}, [kind])
|
||||
}, [kind, uiLanguage])
|
||||
// The permission texts come from the host in the language of the page: ask again when it changes.
|
||||
const languageSeen = useRef(uiLanguage)
|
||||
useEffect(() => {
|
||||
if (languageSeen.current === uiLanguage) return
|
||||
languageSeen.current = uiLanguage
|
||||
setAccessVersion((v) => v + 1)
|
||||
}, [uiLanguage])
|
||||
useEffect(() => { agentsApi.list().then((r) => setAgents(r.agents || [])).catch(() => {}) }, [])
|
||||
|
||||
// A link with ?binding=<id> (from the Tools list of an agent) opens that
|
||||
|
||||
@@ -2,9 +2,18 @@
|
||||
// (sdk.core.fetch). The bindings, the sealed token and the sender log live in
|
||||
// core; the add-on only carries the messages.
|
||||
import { getSdk } from '../../host-compat/src/runtime.js'
|
||||
import { localizeAccess, localizeCapabilities } from './connector.js'
|
||||
|
||||
const enc = encodeURIComponent
|
||||
|
||||
// The language of the page. The three calls that return text for the owner
|
||||
// (kinds, access, preview) name it, so the host answers in it: the language
|
||||
// of the interface may differ from the language of the browser.
|
||||
let language = ''
|
||||
// English is the host's own text, so it is not asked for.
|
||||
export function setApiLanguage(tag) { language = typeof tag === 'string' && !/^en(-|$)/i.test(tag) ? tag : '' }
|
||||
const localized = (path) => (language ? `${path}${path.includes('?') ? '&' : '?'}lang=${enc(language)}` : path)
|
||||
|
||||
async function call(path, init = {}) {
|
||||
const response = await getSdk().core.fetch(path, { headers: { 'Content-Type': 'application/json' }, ...init })
|
||||
if (!response.ok) {
|
||||
@@ -29,7 +38,7 @@ export const agentsApi = { list: () => call('/api/chats/agents') }
|
||||
|
||||
export const connectorsApi = {
|
||||
list: () => call('/api/chats/connectors'),
|
||||
kinds: () => call('/api/chats/connectors/kinds'),
|
||||
kinds: () => call(localized('/api/chats/connectors/kinds')).then((r) => (r ? { ...r, capabilities: localizeCapabilities(r.capabilities) } : r)),
|
||||
create: (body) => send('POST', '/api/chats/connectors', body),
|
||||
update: (id, body) => send('PUT', `/api/chats/connectors/${enc(id)}`, body),
|
||||
remove: (id) => send('DELETE', `/api/chats/connectors/${enc(id)}`),
|
||||
@@ -42,8 +51,8 @@ export const connectorsApi = {
|
||||
// to the bot as a direct message from that account.
|
||||
pair: (id) => send('POST', `/api/chats/connectors/${enc(id)}/pairing`, {}),
|
||||
// What each kind of sender may do on a binding now, and the latest decisions.
|
||||
access: (id) => call(`/api/chats/connectors/${enc(id)}/access`),
|
||||
access: (id) => call(localized(`/api/chats/connectors/${enc(id)}/access`)).then(localizeAccess),
|
||||
// What the form as it is now will do, before it is saved. The id is "new" for a connection that is not saved yet.
|
||||
preview: (id, body, signal) => send('POST', `/api/chats/connectors/${enc(id)}/access/preview`, body, signal),
|
||||
preview: (id, body, signal) => send('POST', localized(`/api/chats/connectors/${enc(id)}/access/preview`), body, signal).then(localizeAccess),
|
||||
unpair: (id, sender) => send('DELETE', `/api/chats/connectors/${enc(id)}/paired/${enc(sender)}`),
|
||||
}
|
||||
@@ -6,6 +6,25 @@ export const REPLY_MODES = ['off', 'progress', 'stream']
|
||||
export const CONVERSATIONS = ['sender', 'chat', 'window', 'thread']
|
||||
// The language of the sentences the bot writes itself (auto follows the sender).
|
||||
export const REPLY_LANGUAGES = ['auto', 'en', 'it', 'de', 'es', 'fr', 'pt-BR']
|
||||
// The host answers with English text and, when the request names a language it
|
||||
// translates, localized_* fields beside it, found by code. These two fold the
|
||||
// localized fields into the plain ones, so the rest of the page reads one name.
|
||||
// A field the host did not translate keeps its English text.
|
||||
export function localizeCapabilities(list) {
|
||||
return (list || []).map((c) => ({ ...c, title: c.localized_title || c.title, description: c.localized_description || c.description }))
|
||||
}
|
||||
|
||||
export function localizeAccess(view) {
|
||||
if (!view || !Array.isArray(view.subjects)) return view
|
||||
return {
|
||||
...view,
|
||||
subjects: view.subjects.map((subject) => ({
|
||||
...subject,
|
||||
decisions: (subject.decisions || []).map((d) => (d.localized_message ? { ...d, message: d.localized_message } : d)),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export const MAX_WINDOW_MINUTES = 7 * 24 * 60
|
||||
|
||||
// How the triggers that the agent proposes in a chat are approved, and the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { approvalForm, approvalPayload, approvalProblem, bytesToMib, canSave, connectorForm, connectorPayload, emptyConnector, REPLY_LANGUAGES, fileDirection, fileLimitProblem, formatBytes, mibToBytes, modeText, previewLimit, senderLine } from '../../../internal/connector-ui/src/connector.js'
|
||||
import { localizeAccess, localizeCapabilities, approvalForm, approvalPayload, approvalProblem, bytesToMib, canSave, connectorForm, connectorPayload, emptyConnector, REPLY_LANGUAGES, fileDirection, fileLimitProblem, formatBytes, mibToBytes, modeText, previewLimit, senderLine } from '../../../internal/connector-ui/src/connector.js'
|
||||
|
||||
test('chat connectors: the token goes only when it was typed', () => {
|
||||
const create = connectorPayload({ ...emptyConnector(), name: ' Bot ', token: ' 1:abc ', agent_id: 'a1', allow: ['11', ' 11', '@ada_l', ''] }, 'telegram')
|
||||
@@ -212,3 +212,22 @@ test('chat connectors: the shared words label every reply language and say what
|
||||
const src = readFileSync(new URL('../../../internal/connector-ui/src/AnswerFields.jsx', import.meta.url), 'utf8')
|
||||
assert.ok(src.includes('data-testid="connector-reply-language"'))
|
||||
})
|
||||
|
||||
test('chat connectors: the host text in the page language replaces the English one, field by field', () => {
|
||||
const caps = localizeCapabilities([
|
||||
{ name: 'a', title: 'Save', description: 'D', language: 'it', localized_title: 'Salva' },
|
||||
{ name: 'b', title: 'Files', description: 'F' },
|
||||
])
|
||||
assert.deepEqual(caps.map((c) => [c.title, c.description]), [['Salva', 'D'], ['Files', 'F']])
|
||||
const view = { binding: 'b', subjects: [{ id: 'listed', decisions: [
|
||||
{ capability: 'a', code: 'x', message: 'English', localized_message: 'Italiano' },
|
||||
{ capability: 'b', code: 'y', message: 'English only' },
|
||||
{ capability: 'c', allowed: true },
|
||||
] }] }
|
||||
const out = localizeAccess(view)
|
||||
assert.deepEqual(out.subjects[0].decisions.map((d) => d.message), ['Italiano', 'English only', undefined])
|
||||
assert.equal(out.subjects[0].decisions[0].code, 'x')
|
||||
assert.equal(view.subjects[0].decisions[0].message, 'English', 'the input is not changed')
|
||||
assert.equal(localizeAccess(null), null)
|
||||
assert.deepEqual(localizeCapabilities(undefined), [])
|
||||
})
|
||||
@@ -2,6 +2,7 @@ package studio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mudler/LocalAI/core/services/studio/connectortext"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
@@ -178,7 +179,7 @@ func (h *Handlers) ListConnectors(c echo.Context) error {
|
||||
// @Summary List the connector kinds you can bind.
|
||||
// @Description The messaging platforms that a running connector add-on serves, with the add-on page that adds and edits their bindings. Empty when no connector add-on is installed. A kind whose manifest translates its text carries titles and descriptions by language and, when the request names a language it translates (lang, else Accept-Language), language, localized_title and localized_description with English as the fallback for each field; title and description stay English.
|
||||
// @Tags chats
|
||||
// @Param lang query string false "Language tag to resolve the kind text for, such as it or pt-BR. Wins over Accept-Language. An unknown language gives the English text."
|
||||
// @Param lang query string false "Language tag to resolve the kind text and the capability text for, such as it or pt-BR. Wins over Accept-Language. An unknown language gives the English text."
|
||||
// @Success 200 {object} schema.ChatsConnectorKindList "The kinds"
|
||||
// @Router /api/chats/connectors/kinds [get]
|
||||
func (h *Handlers) ConnectorKinds(c echo.Context) error {
|
||||
@@ -198,8 +199,9 @@ func (h *Handlers) ConnectorKinds(c echo.Context) error {
|
||||
translateKind(&view, k.Translations, explicit, accept)
|
||||
out.Kinds = append(out.Kinds, view)
|
||||
}
|
||||
lang := connectortext.Negotiate(explicit, accept)
|
||||
for _, ci := range contract.ConnectorCapabilities() {
|
||||
out.Capabilities = append(out.Capabilities, schema.ChatsConnectorCapability(ci))
|
||||
out.Capabilities = append(out.Capabilities, capabilityOut(ci, lang))
|
||||
}
|
||||
return c.JSON(http.StatusOK, out)
|
||||
}
|
||||
@@ -416,6 +418,7 @@ func (h *Handlers) StartConnectorPairing(c echo.Context) error {
|
||||
// @Description Runs the permission check the host uses for each turn, for a sender on the allowlist by ID or name (trusted with every capability that is not sensitive), a sender let in only through a listed channel, a paired sender and a sender of each role the kind declares, and lists the latest decisions. A settings page of the add-on can show it.
|
||||
// @Tags chats
|
||||
// @Param id path string true "the binding's id"
|
||||
// @Param lang query string false "Language tag for localized_message, such as it or pt-BR. Wins over Accept-Language. English or an unknown language adds no localized field."
|
||||
// @Success 200 {object} schema.ChatsConnectorAccess "The effective permissions"
|
||||
// @Router /api/chats/connectors/{id}/access [get]
|
||||
func (h *Handlers) ConnectorAccess(c echo.Context) error {
|
||||
@@ -427,16 +430,21 @@ func (h *Handlers) ConnectorAccess(c echo.Context) error {
|
||||
if err != nil {
|
||||
return failConnector(c, err)
|
||||
}
|
||||
return c.JSON(http.StatusOK, accessOut(v))
|
||||
return c.JSON(http.StatusOK, accessOut(v, connectortext.Negotiate(c.QueryParam("lang"), c.Request().Header.Get("Accept-Language"))))
|
||||
}
|
||||
|
||||
func accessOut(v studiosvc.AccessView) schema.ChatsConnectorAccess {
|
||||
out := schema.ChatsConnectorAccess{Binding: v.Binding, Subjects: []schema.ChatsConnectorAccessSubject{}, Recent: []schema.ChatsConnectorAccessEvent{}, Preview: v.Preview}
|
||||
func accessOut(v studiosvc.AccessView, lang string) schema.ChatsConnectorAccess {
|
||||
out := schema.ChatsConnectorAccess{Binding: v.Binding, Subjects: []schema.ChatsConnectorAccessSubject{}, Recent: []schema.ChatsConnectorAccessEvent{}, Preview: v.Preview, Language: lang}
|
||||
for _, s := range v.Subjects {
|
||||
o := schema.ChatsConnectorAccessSubject{ID: s.ID, Title: s.Title, Decisions: []schema.ChatsConnectorAccessDecision{}}
|
||||
for _, d := range s.Decisions {
|
||||
od := schema.ChatsConnectorAccessDecision{Capability: d.Capability, Allowed: d.Allowed, Via: d.Via, Reason: d.Reason,
|
||||
Code: d.Code, BlockedBy: d.BlockedBy, Message: d.Message, ThenCheck: d.ThenCheck}
|
||||
if lang != "" && d.Code != "" {
|
||||
if m, ok := connectortext.Block(lang, d.Code, d.Capability); ok {
|
||||
od.LocalizedMessage = m
|
||||
}
|
||||
}
|
||||
if d.Fix != nil {
|
||||
od.Fix = &schema.ChatsConnectorAccessFix{Kind: d.Fix.Kind, Target: d.Fix.Target, Field: d.Fix.Field}
|
||||
}
|
||||
@@ -456,6 +464,7 @@ func accessOut(v studiosvc.AccessView) schema.ChatsConnectorAccess {
|
||||
// @Description Runs the same permission chain as the access view on the state in the request: the server setting, the Tools switch of the agent, the agent permission, the permission of the connection and the trust of the sender, in that order, and reports the first problem of each capability with a code, a plain message, one fix and the links not yet checked. Fields that are absent keep the saved value. Use the id "new" with a kind for a connection that is not saved yet. It writes nothing, records no decision, and neither takes nor returns the token. Same sign-in and Connectors feature as the other connector routes.
|
||||
// @Tags chats
|
||||
// @Param id path string true "the binding's id, or new"
|
||||
// @Param lang query string false "Language tag for localized_message, such as it or pt-BR. Wins over Accept-Language. English or an unknown language adds no localized field."
|
||||
// @Param request body schema.ChatsConnectorAccessPreviewRequest true "the form state to check"
|
||||
// @Success 200 {object} schema.ChatsConnectorAccess "The effective permissions of that state"
|
||||
// @Failure 400 {object} schema.ErrorResponse "The request is not valid"
|
||||
@@ -474,7 +483,7 @@ func (h *Handlers) ConnectorAccessPreview(c echo.Context) error {
|
||||
if err != nil {
|
||||
return failConnector(c, err)
|
||||
}
|
||||
return c.JSON(http.StatusOK, accessOut(v))
|
||||
return c.JSON(http.StatusOK, accessOut(v, connectortext.Negotiate(c.QueryParam("lang"), c.Request().Header.Get("Accept-Language"))))
|
||||
}
|
||||
|
||||
// RemoveConnectorPaired ends the pairing of a sender.
|
||||
@@ -497,6 +506,19 @@ func (h *Handlers) RemoveConnectorPaired(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, connectorOut(i))
|
||||
}
|
||||
|
||||
// capabilityOut adds the title and description in the negotiated language
|
||||
// beside the English ones.
|
||||
func capabilityOut(ci contract.CapabilityInfo, lang string) schema.ChatsConnectorCapability {
|
||||
out := schema.ChatsConnectorCapability{Name: ci.Name, Title: ci.Title, Description: ci.Description, Sensitive: ci.Sensitive, Usual: ci.Usual}
|
||||
if lang == "" {
|
||||
return out
|
||||
}
|
||||
if t, d, ok := connectortext.Capability(lang, ci.Name); ok {
|
||||
out.Language, out.LocalizedTitle, out.LocalizedDescription = lang, t, d
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func optionsOut(in []contract.ConnectorOption) []schema.ChatsConnectorOption {
|
||||
var out []schema.ChatsConnectorOption
|
||||
for _, o := range in {
|
||||
|
||||
@@ -3,6 +3,7 @@ package studio
|
||||
|
||||
import (
|
||||
"github.com/mudler/LocalAI/core/schema"
|
||||
studiosvc "github.com/mudler/LocalAI/core/services/studio"
|
||||
"github.com/mudler/LocalAI/pkg/addons/contract"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -44,3 +45,35 @@ var _ = Describe("translated connector kinds", func() {
|
||||
Expect(k).To(Equal(kind()))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("translated capability and refusal text", func() {
|
||||
ci := contract.CapabilityInfo{Name: contract.CapMemoryWrite, Title: "Save to memory", Description: "The agent may remember."}
|
||||
It("keeps the English fields and adds the negotiated language beside them", func() {
|
||||
out := capabilityOut(ci, "it")
|
||||
Expect(out.Title).To(Equal("Save to memory"))
|
||||
Expect(out.Language).To(Equal("it"))
|
||||
Expect(out.LocalizedTitle).To(Equal("Salva in memoria"))
|
||||
Expect(out.LocalizedDescription).NotTo(BeEmpty())
|
||||
})
|
||||
It("adds nothing for English", func() {
|
||||
out := capabilityOut(ci, "")
|
||||
Expect(out.Language).To(BeEmpty())
|
||||
Expect(out.LocalizedTitle).To(BeEmpty())
|
||||
})
|
||||
It("adds a localized message by code and leaves the code and the English message", func() {
|
||||
v := studiosvc.AccessView{Binding: "b", Subjects: []studiosvc.AccessSubjectView{{ID: "listed", Decisions: []studiosvc.Decision{
|
||||
{Capability: contract.CapFilesUpload, Code: studiosvc.CodeConnectionPermissionOff, Message: "English text"},
|
||||
{Capability: contract.CapFilesUpload, Allowed: true},
|
||||
{Capability: contract.CapFilesUpload, Reason: "a rule of the feature"},
|
||||
}}}}
|
||||
out := accessOut(v, "fr")
|
||||
Expect(out.Language).To(Equal("fr"))
|
||||
d := out.Subjects[0].Decisions
|
||||
Expect(d[0].Code).To(Equal("connection_permission_off"))
|
||||
Expect(d[0].Message).To(Equal("English text"))
|
||||
Expect(d[0].LocalizedMessage).To(ContainSubstring("recevoir des fichiers"))
|
||||
Expect(d[1].LocalizedMessage).To(BeEmpty())
|
||||
Expect(d[2].LocalizedMessage).To(BeEmpty())
|
||||
Expect(accessOut(v, "").Subjects[0].Decisions[0].LocalizedMessage).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
+5
-4
@@ -89,7 +89,8 @@ export function previewFor(body, roles = []) {
|
||||
}
|
||||
|
||||
export function connectorApi(page, initial = [], kind = 'telegram', senders = [NEWCOMER], files = ONE_MIB, { capabilities = CAPABILITIES, kindExtra = {}, access = null, preview = null, previewDelay = 0, previewStatus = 200 } = {}) {
|
||||
const s = { items: structuredClone(initial), calls: [], senders: structuredClone(senders), previews: [] }
|
||||
const s = { items: structuredClone(initial), calls: [], senders: structuredClone(senders), previews: [], urls: [] }
|
||||
page.on('request', r => { if (r.url().includes('/api/chats/connectors/')) s.urls.push(r.url()) })
|
||||
const rec = (method, url, body) => s.calls.push({ method, url: new URL(url).pathname, body })
|
||||
return {
|
||||
s,
|
||||
@@ -100,14 +101,14 @@ export function connectorApi(page, initial = [], kind = 'telegram', senders = [N
|
||||
s.senders = s.senders.map(x => ({ ...x, allowed: true }))
|
||||
return r.fulfill({ json: s.items[0] })
|
||||
})
|
||||
await page.route(/\/api\/chats\/connectors\/[^/?]+\/access\/preview$/, async r => {
|
||||
await page.route(/\/api\/chats\/connectors\/[^/?]+\/access\/preview(\?.*)?$/, async r => {
|
||||
const body = r.request().postDataJSON()
|
||||
s.previews.push({ id: decodeURIComponent(new URL(r.request().url()).pathname.split('/').at(-3)), body })
|
||||
if (previewDelay) await new Promise(ok => setTimeout(ok, previewDelay))
|
||||
if (previewStatus !== 200) return r.fulfill({ status: previewStatus, json: { error: { message: 'The check is not available right now.' } } })
|
||||
return r.fulfill({ json: typeof preview === 'function' ? preview(body) : preview || previewFor(body, kindExtra.roles || []) })
|
||||
})
|
||||
await page.route(/\/api\/chats\/connectors\/[^/?]+\/access$/, r => r.fulfill({ json: access || { binding: 'b1', subjects: [], recent: [] } }))
|
||||
await page.route(/\/api\/chats\/connectors\/[^/?]+\/access(\?.*)?$/, r => r.fulfill({ json: access || { binding: 'b1', subjects: [], recent: [] } }))
|
||||
await page.route(/\/api\/chats\/connectors\/[^/?]+\/pairing$/, r => {
|
||||
rec('POST', r.request().url(), null)
|
||||
return r.fulfill({ json: { code: 'LA-K7M2QX9P', expires: new Date(Date.now() + 10 * 60 * 1000).toISOString() } })
|
||||
@@ -146,7 +147,7 @@ export function connectorApi(page, initial = [], kind = 'telegram', senders = [N
|
||||
await page.route(/\/api\/chats(\?.*)?$/, r => r.fulfill({ json: { conversations: [], persistent: true, files: true } }))
|
||||
await page.route('**/api/chats/notifications*', r => r.fulfill({ json: { notifications: [], unread: 0 } }))
|
||||
// Registered last, so it answers before the route of one binding.
|
||||
await page.route(/\/api\/chats\/connectors\/kinds$/, r => r.fulfill({ json: { kinds: [{ addon: kind, kind, title: kind, files, ...kindExtra }], capabilities } }))
|
||||
await page.route(/\/api\/chats\/connectors\/kinds(\?.*)?$/, r => r.fulfill({ json: { kinds: [{ addon: kind, kind, title: kind, files, ...kindExtra }], capabilities } }))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -428,6 +428,29 @@ test.describe('Connector permissions', () => {
|
||||
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled()
|
||||
})
|
||||
|
||||
test('in an Italian page the host is asked for Italian and its localized text is shown, with English as the fallback', async ({ page }) => {
|
||||
await page.addInitScript(() => localStorage.setItem('localai-language', 'it'))
|
||||
const capabilities = CAPABILITIES.map(c => (c.name === 'memory.write' ? { ...c, language: 'it', localized_title: 'Salva in memoria', localized_description: 'L\'agente può ricordare.' } : c))
|
||||
const preview = body => {
|
||||
const v = previewFor(body, [])
|
||||
for (const subject of v.subjects) {
|
||||
for (const d of subject.decisions) if (d.capability === 'memory.write') Object.assign(d, { allowed: false, code: 'connection_permission_off', blocked_by: 'connection', message: 'This connection does not allow the bot to save notes to memory.', localized_message: 'Questa connessione non permette al bot di salvare note in memoria.' })
|
||||
}
|
||||
return v
|
||||
}
|
||||
const a = connectorApi(page, [{ ...LEGACY, agent_id: 'a1', caps: ['memory.write'] }], 'slack', [], undefined, { kindExtra: ROLES_KIND, access: ACCESS, capabilities, preview })
|
||||
await a.install()
|
||||
await page.goto(PAGE)
|
||||
await page.getByRole('button', { name: 'Modifica' }).click()
|
||||
const row = page.getByTestId('connector-can-memory.write')
|
||||
await expect(row).toContainText('Salva in memoria')
|
||||
await expect(row.getByTestId('connector-can-why')).toHaveText('Questa connessione non permette al bot di salvare note in memoria.')
|
||||
// A capability the host did not translate keeps its English title.
|
||||
await expect(page.getByTestId('connector-can-files.upload')).toContainText('Receive files')
|
||||
expect(a.s.urls.some(u => u.endsWith('/api/chats/connectors/kinds?lang=it'))).toBe(true)
|
||||
expect(a.s.urls.some(u => u.endsWith('/access/preview?lang=it'))).toBe(true)
|
||||
})
|
||||
|
||||
test('a blocker held by the server settings links to Settings for an administrator and says to ask one otherwise', async ({ page }) => {
|
||||
const blocked = body => {
|
||||
const v = previewFor(body, [])
|
||||
|
||||
@@ -404,6 +404,19 @@ var _ = Describe("Chats connectors endpoints", func() {
|
||||
Expect(blocked).To(And(HaveKeyWithValue("allowed", false), HaveKeyWithValue("code", "connection_permission_off"),
|
||||
HaveKeyWithValue("blocked_by", "connection"), HaveKey("message"), HaveKey("reason"),
|
||||
HaveKeyWithValue("fix", HaveKeyWithValue("kind", "open_connection")), HaveKeyWithValue("then_check", ContainElement("sender"))))
|
||||
// The same refusal in the language of the request: the code and the
|
||||
// English message stay, a localized_message is added by code.
|
||||
rec = s.do("alice", http.MethodGet, "/api/chats/connectors/"+id+"/access?lang=it", nil)
|
||||
Expect(rec.Code).To(Equal(http.StatusOK), rec.Body.String())
|
||||
it := decision(decodeJSON(rec), "listed", "triggers.propose")
|
||||
Expect(it).To(And(HaveKeyWithValue("code", "connection_permission_off"), HaveKeyWithValue("message", blocked["message"]),
|
||||
HaveKeyWithValue("localized_message", ContainSubstring("promemoria"))))
|
||||
Expect(decodeJSON(rec)).To(HaveKeyWithValue("language", "it"))
|
||||
Expect(blocked).NotTo(HaveKey("localized_message"))
|
||||
rec = s.do("alice", http.MethodGet, "/api/chats/connectors/kinds?lang=de", nil)
|
||||
Expect(rec.Code).To(Equal(http.StatusOK), rec.Body.String())
|
||||
Expect(decodeJSON(rec)["capabilities"]).To(ContainElement(And(HaveKeyWithValue("name", "memory.write"), HaveKeyWithValue("title", "Save to memory"),
|
||||
HaveKeyWithValue("language", "de"), HaveKeyWithValue("localized_title", "Im Gedächtnis speichern"))))
|
||||
rec = s.do("alice", http.MethodPost, preview, map[string]any{"caps": []string{"triggers.propose"}, "sender": "listed"})
|
||||
Expect(rec.Code).To(Equal(http.StatusOK), rec.Body.String())
|
||||
v := decodeJSON(rec)
|
||||
|
||||
@@ -1432,6 +1432,11 @@ type ChatsConnectorAccessDecision struct {
|
||||
BlockedBy string `json:"blocked_by,omitempty"`
|
||||
// Message is one plain sentence for the owner. It names no setting.
|
||||
Message string `json:"message,omitempty"`
|
||||
// LocalizedMessage is message in the language of the request (see
|
||||
// ChatsConnectorAccess.Language), found by code. Absent when the
|
||||
// language is English or the code has no translation; message stays
|
||||
// English and code stays unchanged.
|
||||
LocalizedMessage string `json:"localized_message,omitempty"`
|
||||
// Fix points at the one place that solves it. The server sends no URL.
|
||||
Fix *ChatsConnectorAccessFix `json:"fix,omitempty"`
|
||||
// ThenCheck lists the links after the first problem that were not
|
||||
@@ -1483,6 +1488,10 @@ type ChatsConnectorAccess struct {
|
||||
Recent []ChatsConnectorAccessEvent `json:"recent"`
|
||||
// Preview is true when the view is for a form that is not saved.
|
||||
Preview bool `json:"preview,omitempty"`
|
||||
// Language is the language of the localized_message fields, negotiated
|
||||
// from the lang query parameter, then Accept-Language. Absent for
|
||||
// English.
|
||||
Language string `json:"language,omitempty"`
|
||||
}
|
||||
|
||||
// ChatsConnectorAccessPreviewRequest is the state of a connection form that
|
||||
@@ -1526,6 +1535,13 @@ type ChatsConnectorCapability struct {
|
||||
Sensitive bool `json:"sensitive"`
|
||||
// Usual capabilities are what a one-click preset turns on.
|
||||
Usual bool `json:"usual"`
|
||||
// Language is the language of localized_title and localized_description:
|
||||
// the one the request asked for (lang, else Accept-Language) among the
|
||||
// translated ones. Absent for English and for a language with no
|
||||
// translation; title and description stay English.
|
||||
Language string `json:"language,omitempty"`
|
||||
LocalizedTitle string `json:"localized_title,omitempty"`
|
||||
LocalizedDescription string `json:"localized_description,omitempty"`
|
||||
}
|
||||
|
||||
// ChatsConnectorPermission is a capability that a kind lists. Sensitive ones
|
||||
|
||||
@@ -261,3 +261,7 @@ func chainRank(code string) int {
|
||||
}
|
||||
return slices.Index(chain, row.By)
|
||||
}
|
||||
|
||||
// BlockOwnerMessage is the English sentence the owner reads for a code: the
|
||||
// source of the translations that connectortext holds by the same code.
|
||||
func BlockOwnerMessage(code, capability string) string { return blockMessage(code, capability) }
|
||||
@@ -0,0 +1,105 @@
|
||||
// Package connectortext holds the texts that the connection settings page
|
||||
// shows the owner for a capability or a refusal, in the languages of the web
|
||||
// UI. English is the source and stays in the code that decides (the capability
|
||||
// list of pkg/addons/contract and the block table of the studio service); this
|
||||
// package holds only the other languages, by code, so the API can answer in the
|
||||
// language the caller asked for while the code of each item stays the same.
|
||||
//
|
||||
// The text that goes to the sender of a chat or to the model is not here: the
|
||||
// host speaks to a sender through hostmsg, and the model reads English.
|
||||
package connectortext
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mudler/LocalAI/pkg/addons/contract"
|
||||
)
|
||||
|
||||
// Languages lists the translated languages. English is the source and has no
|
||||
// file.
|
||||
var Languages = []string{"it", "de", "es", "fr", "id", "ko", "pt-BR", "zh-CN"}
|
||||
|
||||
//go:embed locales/*.json
|
||||
var localeFS embed.FS
|
||||
|
||||
var (
|
||||
loadOnce sync.Once
|
||||
catalog map[string]map[string]string
|
||||
)
|
||||
|
||||
func load() {
|
||||
catalog = map[string]map[string]string{}
|
||||
for _, l := range Languages {
|
||||
raw, err := localeFS.ReadFile("locales/" + l + ".json")
|
||||
if err != nil {
|
||||
panic("connectortext: missing locale " + l)
|
||||
}
|
||||
m := map[string]string{}
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
panic("connectortext: bad locale " + l + ": " + err.Error())
|
||||
}
|
||||
catalog[l] = m
|
||||
}
|
||||
}
|
||||
|
||||
func lookup(lang, key string) (string, bool) {
|
||||
loadOnce.Do(load)
|
||||
s, ok := catalog[lang][key]
|
||||
return s, ok && s != ""
|
||||
}
|
||||
|
||||
// Negotiate picks the language for a request: the explicit tag (a lang query
|
||||
// parameter) wins, then Accept-Language. It returns "" for English, for an
|
||||
// unknown language and for no preference; the caller then keeps the English
|
||||
// fields.
|
||||
func Negotiate(explicit, acceptLanguage string) string {
|
||||
return contract.NegotiateLanguage(explicit, acceptLanguage, Languages)
|
||||
}
|
||||
|
||||
// Key builders, shared with the tests.
|
||||
func CapabilityKey(name, field string) string { return "cap." + name + "." + field }
|
||||
func BlockKey(code string) string { return "block." + code }
|
||||
|
||||
// Capability returns the title and description of a capability in a language.
|
||||
// It reports false when the language or the capability has no text.
|
||||
func Capability(lang, name string) (title, description string, ok bool) {
|
||||
t, ok1 := lookup(lang, CapabilityKey(name, "title"))
|
||||
d, ok2 := lookup(lang, CapabilityKey(name, "description"))
|
||||
return t, d, ok1 && ok2
|
||||
}
|
||||
|
||||
// Action returns the verb phrase of a capability in a language ("set
|
||||
// reminders"), or false.
|
||||
func Action(lang, capability string) (string, bool) {
|
||||
return lookup(lang, CapabilityKey(capability, "action"))
|
||||
}
|
||||
|
||||
// Block returns the owner's sentence for a refusal code in a language, with
|
||||
// {action} filled in for the capability. It reports false when the language
|
||||
// lacks the text; the caller then keeps the English sentence.
|
||||
func Block(lang, code, capability string) (string, bool) {
|
||||
tpl, ok := lookup(lang, BlockKey(code))
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
action, ok := Action(lang, capability)
|
||||
if !ok {
|
||||
action, _ = lookup(lang, "action.unknown")
|
||||
}
|
||||
return strings.ReplaceAll(tpl, "{action}", action), true
|
||||
}
|
||||
|
||||
// KeysOf lists the keys of a language, for the tests that guard the files.
|
||||
func KeysOf(lang string) []string {
|
||||
loadOnce.Do(load)
|
||||
out := make([]string, 0, len(catalog[lang]))
|
||||
for k := range catalog[lang] {
|
||||
out = append(out, k)
|
||||
}
|
||||
slices.Sort(out)
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package connectortext_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mudler/LocalAI/core/services/studio"
|
||||
"github.com/mudler/LocalAI/core/services/studio/connectortext"
|
||||
"github.com/mudler/LocalAI/pkg/addons/contract"
|
||||
)
|
||||
|
||||
// Every capability and every refusal code has a text in every language. A new
|
||||
// code or capability fails here until all the languages carry it.
|
||||
func TestEveryCodeIsInEveryLanguage(t *testing.T) {
|
||||
if len(connectortext.Languages) != 8 {
|
||||
t.Fatalf("want 8 languages, got %v", connectortext.Languages)
|
||||
}
|
||||
for _, lang := range connectortext.Languages {
|
||||
for _, name := range contract.ConnectorCapabilityNames() {
|
||||
title, desc, ok := connectortext.Capability(lang, name)
|
||||
if !ok || strings.TrimSpace(title) == "" || strings.TrimSpace(desc) == "" {
|
||||
t.Errorf("%s: capability %q has no title or description", lang, name)
|
||||
}
|
||||
if a, ok := connectortext.Action(lang, name); !ok || strings.TrimSpace(a) == "" {
|
||||
t.Errorf("%s: capability %q has no action", lang, name)
|
||||
}
|
||||
}
|
||||
for _, code := range studio.BlockCodes() {
|
||||
got, ok := connectortext.Block(lang, code, contract.CapMemoryWrite)
|
||||
if !ok || strings.TrimSpace(got) == "" {
|
||||
t.Errorf("%s: block code %q has no text", lang, code)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(got, "{") {
|
||||
t.Errorf("%s: block code %q leaves a placeholder: %q", lang, code, got)
|
||||
}
|
||||
}
|
||||
if _, ok := connectortext.Block(lang, "no_such_code", ""); ok {
|
||||
t.Errorf("%s: an unknown code must have no text", lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The translated sentence has {action} exactly where the English one has it,
|
||||
// so a sentence never loses the verb or shows the raw name.
|
||||
func TestPlaceholdersMatchEnglish(t *testing.T) {
|
||||
for _, code := range studio.BlockCodes() {
|
||||
english := studio.BlockOwnerMessage(code, contract.CapMemoryWrite)
|
||||
if english == "" {
|
||||
t.Errorf("code %q has no English message", code)
|
||||
continue
|
||||
}
|
||||
wantAction := strings.Contains(english, "save notes to memory")
|
||||
for _, lang := range connectortext.Languages {
|
||||
filled, _ := connectortext.Block(lang, code, contract.CapMemoryWrite)
|
||||
action, _ := connectortext.Action(lang, contract.CapMemoryWrite)
|
||||
if got := strings.Contains(filled, action); got != wantAction {
|
||||
t.Errorf("%s: code %q uses the action = %v, English uses it = %v", lang, code, got, wantAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoLocaleHasAnExtraKey(t *testing.T) {
|
||||
// Locale keys outside the closed lists would be dead text.
|
||||
known := map[string]bool{"action.unknown": true}
|
||||
for _, name := range contract.ConnectorCapabilityNames() {
|
||||
for _, f := range []string{"title", "description", "action"} {
|
||||
known[connectortext.CapabilityKey(name, f)] = true
|
||||
}
|
||||
}
|
||||
for _, code := range studio.BlockCodes() {
|
||||
known[connectortext.BlockKey(code)] = true
|
||||
}
|
||||
for _, lang := range connectortext.Languages {
|
||||
for _, k := range connectortext.KeysOf(lang) {
|
||||
if !known[k] {
|
||||
t.Errorf("%s: key %q matches no capability or code", lang, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNegotiate(t *testing.T) {
|
||||
cases := []struct{ explicit, accept, want string }{
|
||||
{"it", "", "it"},
|
||||
{"pt", "", "pt-BR"},
|
||||
{"zh", "", "zh-CN"},
|
||||
{"zh-Hans", "", "zh-CN"},
|
||||
{"", "ko-KR,ko;q=0.9,en;q=0.5", "ko"},
|
||||
{"it", "de", "it"},
|
||||
{"en", "de", ""},
|
||||
{"xx", "de", ""},
|
||||
{"", "", ""},
|
||||
{"", "en-US,it;q=0.5", ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := connectortext.Negotiate(c.explicit, c.accept); got != c.want {
|
||||
t.Errorf("Negotiate(%q, %q) = %q, want %q", c.explicit, c.accept, got, c.want)
|
||||
}
|
||||
}
|
||||
if !slices.Contains(connectortext.Languages, "id") {
|
||||
t.Error("id is missing")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"cap.proposals.confirm.title": "Änderungen im Chat bestätigen",
|
||||
"cap.proposals.confirm.description": "Ein Absender kann einen Trigger oder eine vom Agenten vorgeschlagene Änderung (eine Gedächtnisnotiz, die Agentenregeln, einen Skill, eine Seite, das Dashboard oder einen Text für das Wissen) bestätigen, indem er im selben Chat mit Ja oder Nein antwortet. Jede Änderung außer einer Gedächtnisnotiz braucht einen gekoppelten Absender.",
|
||||
"cap.proposals.confirm.action": "Vorschläge bestätigen",
|
||||
"cap.memory.write.title": "Im Gedächtnis speichern",
|
||||
"cap.memory.write.description": "Der Agent darf sich merken, was ein Absender ihn zu merken bittet.",
|
||||
"cap.memory.write.action": "Notizen im Gedächtnis speichern",
|
||||
"cap.files.upload.title": "Dateien empfangen",
|
||||
"cap.files.upload.description": "Der Agent darf Dateien lesen, die Absender an eine Nachricht anhängen.",
|
||||
"cap.files.upload.action": "Dateien empfangen",
|
||||
"cap.triggers.propose.title": "Trigger vorschlagen",
|
||||
"cap.triggers.propose.description": "Der Agent darf einen Zeit- oder Ereignis-Trigger vorschlagen, wenn ein Absender darum bittet.",
|
||||
"cap.triggers.propose.action": "Erinnerungen und geplante Aufgaben einrichten",
|
||||
"cap.triggers.run.title": "Ereignis-Trigger auslösen",
|
||||
"cap.triggers.run.description": "Eine Nachricht eines Absenders darf den Lauf eines Ereignis-Triggers starten.",
|
||||
"cap.triggers.run.action": "geplante Aufgaben ausführen",
|
||||
"cap.talkback.channels.title": "In freigegebene Kanäle schreiben",
|
||||
"cap.talkback.channels.description": "Der Agent darf auf Wunsch eines Absenders in die unten freigegebenen Kanäle schreiben, zusätzlich zum Chat, in dem er antwortet.",
|
||||
"cap.talkback.channels.action": "in andere Kanäle schreiben",
|
||||
"cap.talkback.new_channel.title": "In einen neuen Kanal schreiben",
|
||||
"cap.talkback.new_channel.description": "Der Agent darf in einen Kanal schreiben, der noch nicht freigegeben ist.",
|
||||
"cap.talkback.new_channel.action": "Gespräche in neuen Kanälen beginnen",
|
||||
"cap.profile.write.title": "Agentenprofil ändern",
|
||||
"cap.profile.write.description": "Der Agent darf sein eigenes Profil ändern.",
|
||||
"cap.profile.write.action": "die Regeln des Agenten ändern",
|
||||
"cap.skills.write.title": "Skills schreiben",
|
||||
"cap.skills.write.description": "Der Agent darf Skills schreiben oder ändern.",
|
||||
"cap.skills.write.action": "Skills speichern",
|
||||
"cap.pages.write.title": "Seiten schreiben",
|
||||
"cap.pages.write.description": "Der Agent darf Seiten schreiben oder ändern.",
|
||||
"cap.pages.write.action": "Seiten veröffentlichen",
|
||||
"cap.dashboard.write.title": "Das Dashboard schreiben",
|
||||
"cap.dashboard.write.description": "Der Agent darf das Dashboard ändern.",
|
||||
"cap.dashboard.write.action": "das Dashboard ändern",
|
||||
"cap.knowledge.write.title": "Zum Wissen hinzufügen",
|
||||
"cap.knowledge.write.description": "Der Agent darf einen Text für eine Sammlung des Wissens vorschlagen. Der Besitzer bestätigt jeden einzelnen.",
|
||||
"cap.knowledge.write.action": "ins Wissen schreiben",
|
||||
"cap.tools.write.title": "Dinge außerhalb von LocalAI ändern",
|
||||
"cap.tools.write.description": "Ein Add-on-Werkzeug darf mit der Zugangsberechtigung der Verbindung etwas ändern, etwa einen Kommentar zu einem Issue.",
|
||||
"cap.tools.write.action": "mit Add-on-Werkzeugen Dinge in anderen Diensten ändern",
|
||||
"action.unknown": "das tun",
|
||||
"block.no_agent": "Diese Verbindung hat keinen Agenten, daher kann der Bot noch nicht antworten oder handeln.",
|
||||
"block.unknown_capability": "Dieser Server kennt diese Berechtigung nicht.",
|
||||
"block.server_setting_off": "Ein Administrator hat dies für den ganzen Server ausgeschaltet, daher kann der Bot nicht {action}.",
|
||||
"block.tools_switch_off": "Die Werkzeuge, die der Bot dafür braucht, sind für den Agenten ausgeschaltet (Aktion: {action}). Schalte sie im Agenten ein.",
|
||||
"block.agent_permission_off": "Der Agent darf nicht {action}. Schalte die passende Berechtigung im Agenten ein.",
|
||||
"block.agent_unavailable": "Der Agent dieser Verbindung kann gerade nicht gelesen werden. Prüfe, ob er noch existiert.",
|
||||
"block.connection_permission_off": "Diese Verbindung erlaubt dem Bot nicht, {action}. Hake die passende Berechtigung in der Verbindung an.",
|
||||
"block.sender_not_listed": "Diese Person steht nicht auf der Liste der Personen, denen der Bot antwortet. Füge sie zu den erlaubten Personen hinzu.",
|
||||
"block.sender_needs_pairing": "Diese Person muss mit dem Bot gekoppelt sein, bevor der Bot für sie {action} kann.",
|
||||
"block.channel_not_trusted": "Personen in einem Kanal gelten nicht einzeln als vertrauenswürdig, daher kann der Bot für sie nicht {action}. Füge die Person zu den erlaubten Personen hinzu.",
|
||||
"block.role_not_granted": "Die Rolle dieser Person erlaubt dem Bot nicht, {action}. Erlaube es für diese Rolle in der Verbindung.",
|
||||
"block.kind_not_running": "Die App dieser Verbindung läuft nicht, daher können Rollen nicht geprüft werden. Starte die App.",
|
||||
"block.context_rule": "Der Bot kann an diesem Ort nicht {action}.",
|
||||
"block.knowledge_none": "In der Liste des Agenten steht keine Sammlung, daher kann der Bot nirgends {action}. Füge dem Agenten eine Sammlung hinzu.",
|
||||
"block.knowledge_no_embedding": "Das Embedding-Modell für das Wissen ist nicht installiert, daher kann der Bot nicht {action}. Installiere es unter Modelle."
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"cap.proposals.confirm.title": "Confirmar cambios desde el chat",
|
||||
"cap.proposals.confirm.description": "Quien escribe puede confirmar un disparador o un cambio que el agente propuso (una nota de memoria, las reglas del agente, una skill, una página, el panel o un texto para el Conocimiento) respondiendo sí o no en el mismo chat. Todo cambio salvo una nota de memoria necesita a una persona vinculada.",
|
||||
"cap.proposals.confirm.action": "confirmar propuestas",
|
||||
"cap.memory.write.title": "Guardar en la memoria",
|
||||
"cap.memory.write.description": "El agente puede recordar lo que quien escribe le pide que recuerde.",
|
||||
"cap.memory.write.action": "guardar notas en la memoria",
|
||||
"cap.files.upload.title": "Recibir archivos",
|
||||
"cap.files.upload.description": "El agente puede leer los archivos que quien escribe adjunta a un mensaje.",
|
||||
"cap.files.upload.action": "recibir archivos",
|
||||
"cap.triggers.propose.title": "Proponer disparadores",
|
||||
"cap.triggers.propose.description": "El agente puede proponer un disparador de tiempo o de evento cuando quien escribe lo pide.",
|
||||
"cap.triggers.propose.action": "crear recordatorios y tareas programadas",
|
||||
"cap.triggers.run.title": "Activar disparadores de evento",
|
||||
"cap.triggers.run.description": "El mensaje de quien escribe puede iniciar la ejecución de un disparador de evento.",
|
||||
"cap.triggers.run.action": "ejecutar tareas programadas",
|
||||
"cap.talkback.channels.title": "Publicar en canales aprobados",
|
||||
"cap.talkback.channels.description": "El agente puede escribir en los canales aprobados de abajo cuando quien escribe lo pide, además del chat en el que responde.",
|
||||
"cap.talkback.channels.action": "escribir en otros canales",
|
||||
"cap.talkback.new_channel.title": "Publicar en un canal nuevo",
|
||||
"cap.talkback.new_channel.description": "El agente puede escribir en un canal que aún no está aprobado.",
|
||||
"cap.talkback.new_channel.action": "iniciar conversaciones en canales nuevos",
|
||||
"cap.profile.write.title": "Cambiar el perfil del agente",
|
||||
"cap.profile.write.description": "El agente puede cambiar su propio perfil.",
|
||||
"cap.profile.write.action": "cambiar las reglas del agente",
|
||||
"cap.skills.write.title": "Escribir skills",
|
||||
"cap.skills.write.description": "El agente puede escribir o cambiar skills.",
|
||||
"cap.skills.write.action": "guardar skills",
|
||||
"cap.pages.write.title": "Escribir páginas",
|
||||
"cap.pages.write.description": "El agente puede escribir o cambiar páginas.",
|
||||
"cap.pages.write.action": "publicar páginas",
|
||||
"cap.dashboard.write.title": "Escribir el panel",
|
||||
"cap.dashboard.write.description": "El agente puede cambiar el panel.",
|
||||
"cap.dashboard.write.action": "cambiar el panel",
|
||||
"cap.knowledge.write.title": "Añadir al Conocimiento",
|
||||
"cap.knowledge.write.description": "El agente puede proponer un texto para una colección del Conocimiento. El propietario confirma cada uno.",
|
||||
"cap.knowledge.write.action": "escribir en el Conocimiento",
|
||||
"cap.tools.write.title": "Cambiar cosas fuera de LocalAI",
|
||||
"cap.tools.write.description": "Una herramienta de una app puede cambiar algo con la credencial de la vinculación, como un comentario en una incidencia.",
|
||||
"cap.tools.write.action": "cambiar cosas en otros servicios con herramientas de apps",
|
||||
"action.unknown": "hacerlo",
|
||||
"block.no_agent": "Esta conexión no tiene agente, así que el bot aún no puede responder ni actuar.",
|
||||
"block.unknown_capability": "Este servidor no conoce ese permiso.",
|
||||
"block.server_setting_off": "Un administrador lo desactivó para todo el servidor, así que el bot no puede {action}.",
|
||||
"block.tools_switch_off": "Las herramientas que el bot necesita para {action} están desactivadas para el agente. Actívalas en el agente.",
|
||||
"block.agent_permission_off": "El agente no tiene permiso para {action}. Activa el permiso correspondiente en el agente.",
|
||||
"block.agent_unavailable": "No se puede leer el agente de esta conexión ahora mismo. Comprueba que todavía exista.",
|
||||
"block.connection_permission_off": "Esta conexión no permite que el bot pueda {action}. Marca el permiso correspondiente en la conexión.",
|
||||
"block.sender_not_listed": "Esta persona no está en la lista de personas a las que responde el bot. Añádela a las personas permitidas.",
|
||||
"block.sender_needs_pairing": "Esta persona debe estar vinculada con el bot antes de que el bot pueda {action} para ella.",
|
||||
"block.channel_not_trusted": "Las personas de un canal no son de confianza una por una, así que el bot no puede {action} para ellas. Añade a la persona a las personas permitidas.",
|
||||
"block.role_not_granted": "El rol de esta persona no permite que el bot pueda {action}. Permítelo para ese rol en la conexión.",
|
||||
"block.kind_not_running": "La app de esta conexión no se está ejecutando, así que no se pueden comprobar los roles. Inicia la app.",
|
||||
"block.context_rule": "El bot no puede {action} en este lugar.",
|
||||
"block.knowledge_none": "Ninguna colección está en la lista del agente, así que el bot no tiene dónde {action}. Añade una colección al agente.",
|
||||
"block.knowledge_no_embedding": "El modelo de embeddings del Conocimiento no está instalado, así que el bot no puede {action}. Instálalo en Modelos."
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"cap.proposals.confirm.title": "Confirmer les modifications depuis le chat",
|
||||
"cap.proposals.confirm.description": "Un expéditeur peut confirmer un déclencheur ou une modification proposée par l'agent (une note de mémoire, les règles de l'agent, une skill, une page, le tableau de bord ou un texte pour les Connaissances) en répondant oui ou non dans le même chat. Toute modification autre qu'une note de mémoire demande un expéditeur appairé.",
|
||||
"cap.proposals.confirm.action": "confirmer des propositions",
|
||||
"cap.memory.write.title": "Enregistrer en mémoire",
|
||||
"cap.memory.write.description": "L'agent peut retenir ce qu'un expéditeur lui demande de retenir.",
|
||||
"cap.memory.write.action": "enregistrer des notes en mémoire",
|
||||
"cap.files.upload.title": "Recevoir des fichiers",
|
||||
"cap.files.upload.description": "L'agent peut lire les fichiers que les expéditeurs joignent à un message.",
|
||||
"cap.files.upload.action": "recevoir des fichiers",
|
||||
"cap.triggers.propose.title": "Proposer des déclencheurs",
|
||||
"cap.triggers.propose.description": "L'agent peut proposer un déclencheur horaire ou d'événement lorsqu'un expéditeur le demande.",
|
||||
"cap.triggers.propose.action": "créer des rappels et des tâches planifiées",
|
||||
"cap.triggers.run.title": "Activer les déclencheurs d'événement",
|
||||
"cap.triggers.run.description": "Le message d'un expéditeur peut lancer l'exécution d'un déclencheur d'événement.",
|
||||
"cap.triggers.run.action": "exécuter des tâches planifiées",
|
||||
"cap.talkback.channels.title": "Écrire dans les canaux approuvés",
|
||||
"cap.talkback.channels.description": "L'agent peut écrire dans les canaux approuvés ci-dessous lorsqu'un expéditeur le demande, en plus du chat où il répond.",
|
||||
"cap.talkback.channels.action": "écrire dans d'autres canaux",
|
||||
"cap.talkback.new_channel.title": "Écrire dans un nouveau canal",
|
||||
"cap.talkback.new_channel.description": "L'agent peut écrire dans un canal qui n'est pas encore approuvé.",
|
||||
"cap.talkback.new_channel.action": "démarrer des conversations dans de nouveaux canaux",
|
||||
"cap.profile.write.title": "Modifier le profil de l'agent",
|
||||
"cap.profile.write.description": "L'agent peut modifier son propre profil.",
|
||||
"cap.profile.write.action": "modifier les règles de l'agent",
|
||||
"cap.skills.write.title": "Écrire des skills",
|
||||
"cap.skills.write.description": "L'agent peut écrire ou modifier des skills.",
|
||||
"cap.skills.write.action": "enregistrer des skills",
|
||||
"cap.pages.write.title": "Écrire des pages",
|
||||
"cap.pages.write.description": "L'agent peut écrire ou modifier des pages.",
|
||||
"cap.pages.write.action": "publier des pages",
|
||||
"cap.dashboard.write.title": "Écrire le tableau de bord",
|
||||
"cap.dashboard.write.description": "L'agent peut modifier le tableau de bord.",
|
||||
"cap.dashboard.write.action": "modifier le tableau de bord",
|
||||
"cap.knowledge.write.title": "Ajouter aux Connaissances",
|
||||
"cap.knowledge.write.description": "L'agent peut proposer un texte pour une collection des Connaissances. Le propriétaire confirme chacun.",
|
||||
"cap.knowledge.write.action": "écrire dans les Connaissances",
|
||||
"cap.tools.write.title": "Modifier des choses hors de LocalAI",
|
||||
"cap.tools.write.description": "Un outil d'une extension peut modifier quelque chose avec l'identifiant de la liaison, comme un commentaire sur un ticket.",
|
||||
"cap.tools.write.action": "modifier des choses dans d'autres services avec des outils d'extension",
|
||||
"action.unknown": "faire cela",
|
||||
"block.no_agent": "Cette connexion n'a pas d'agent, donc le bot ne peut pas encore répondre ni agir.",
|
||||
"block.unknown_capability": "Ce serveur ne connaît pas cette autorisation.",
|
||||
"block.server_setting_off": "Un administrateur l'a désactivé pour tout le serveur, donc le bot ne peut pas {action}.",
|
||||
"block.tools_switch_off": "Les outils dont le bot a besoin pour {action} sont désactivés pour l'agent. Activez-les dans l'agent.",
|
||||
"block.agent_permission_off": "L'agent n'a pas le droit de {action}. Activez l'autorisation correspondante dans l'agent.",
|
||||
"block.agent_unavailable": "L'agent de cette connexion est illisible pour le moment. Vérifiez qu'il existe toujours.",
|
||||
"block.connection_permission_off": "Cette connexion ne permet pas au bot de {action}. Cochez l'autorisation correspondante dans la connexion.",
|
||||
"block.sender_not_listed": "Cette personne ne figure pas dans la liste des personnes auxquelles le bot répond. Ajoutez-la aux personnes autorisées.",
|
||||
"block.sender_needs_pairing": "Cette personne doit être appairée avec le bot avant que le bot puisse {action} pour elle.",
|
||||
"block.channel_not_trusted": "Les personnes d'un canal ne sont pas approuvées une à une, donc le bot ne peut pas {action} pour elles. Ajoutez la personne aux personnes autorisées.",
|
||||
"block.role_not_granted": "Le rôle de cette personne ne permet pas au bot de {action}. Autorisez-le pour ce rôle dans la connexion.",
|
||||
"block.kind_not_running": "L'application de cette connexion ne tourne pas, donc les rôles ne peuvent pas être vérifiés. Démarrez l'application.",
|
||||
"block.context_rule": "Le bot ne peut pas {action} à cet endroit.",
|
||||
"block.knowledge_none": "Aucune collection n'est dans la liste de l'agent, donc le bot n'a nulle part où {action}. Ajoutez une collection à l'agent.",
|
||||
"block.knowledge_no_embedding": "Le modèle d'embedding des Connaissances n'est pas installé, donc le bot ne peut pas {action}. Installez-le dans Modèles."
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"cap.proposals.confirm.title": "Konfirmasi perubahan dari obrolan",
|
||||
"cap.proposals.confirm.description": "Pengirim dapat mengonfirmasi pemicu atau perubahan yang diusulkan agen (catatan memori, aturan agen, skill, halaman, dasbor, atau teks untuk Pengetahuan) dengan menjawab ya atau tidak di obrolan yang sama. Setiap perubahan selain catatan memori memerlukan pengirim yang sudah dipasangkan.",
|
||||
"cap.proposals.confirm.action": "mengonfirmasi usulan",
|
||||
"cap.memory.write.title": "Simpan ke memori",
|
||||
"cap.memory.write.description": "Agen boleh mengingat apa yang diminta pengirim untuk diingat.",
|
||||
"cap.memory.write.action": "menyimpan catatan ke memori",
|
||||
"cap.files.upload.title": "Terima berkas",
|
||||
"cap.files.upload.description": "Agen boleh membaca berkas yang dilampirkan pengirim pada pesan.",
|
||||
"cap.files.upload.action": "menerima berkas",
|
||||
"cap.triggers.propose.title": "Usulkan pemicu",
|
||||
"cap.triggers.propose.description": "Agen boleh mengusulkan pemicu waktu atau pemicu peristiwa saat pengirim memintanya.",
|
||||
"cap.triggers.propose.action": "membuat pengingat dan tugas terjadwal",
|
||||
"cap.triggers.run.title": "Jalankan pemicu peristiwa",
|
||||
"cap.triggers.run.description": "Pesan dari pengirim boleh memulai satu eksekusi pemicu peristiwa.",
|
||||
"cap.triggers.run.action": "menjalankan tugas terjadwal",
|
||||
"cap.talkback.channels.title": "Tulis ke kanal yang disetujui",
|
||||
"cap.talkback.channels.description": "Agen boleh menulis ke kanal yang disetujui di bawah ini saat pengirim memintanya, selain di obrolan tempat ia menjawab.",
|
||||
"cap.talkback.channels.action": "menulis ke kanal lain",
|
||||
"cap.talkback.new_channel.title": "Tulis ke kanal baru",
|
||||
"cap.talkback.new_channel.description": "Agen boleh menulis ke kanal yang belum disetujui.",
|
||||
"cap.talkback.new_channel.action": "memulai percakapan di kanal baru",
|
||||
"cap.profile.write.title": "Ubah profil agen",
|
||||
"cap.profile.write.description": "Agen boleh mengubah profilnya sendiri.",
|
||||
"cap.profile.write.action": "mengubah aturan agen",
|
||||
"cap.skills.write.title": "Tulis skill",
|
||||
"cap.skills.write.description": "Agen boleh menulis atau mengubah skill.",
|
||||
"cap.skills.write.action": "menyimpan skill",
|
||||
"cap.pages.write.title": "Tulis halaman",
|
||||
"cap.pages.write.description": "Agen boleh menulis atau mengubah halaman.",
|
||||
"cap.pages.write.action": "menerbitkan halaman",
|
||||
"cap.dashboard.write.title": "Tulis dasbor",
|
||||
"cap.dashboard.write.description": "Agen boleh mengubah dasbor.",
|
||||
"cap.dashboard.write.action": "mengubah dasbor",
|
||||
"cap.knowledge.write.title": "Tambah ke Pengetahuan",
|
||||
"cap.knowledge.write.description": "Agen boleh mengusulkan teks untuk sebuah koleksi Pengetahuan. Pemilik mengonfirmasi tiap teks.",
|
||||
"cap.knowledge.write.action": "menulis ke Pengetahuan",
|
||||
"cap.tools.write.title": "Ubah hal di luar LocalAI",
|
||||
"cap.tools.write.description": "Alat dari add-on boleh mengubah sesuatu dengan kredensial pengikatan, misalnya komentar pada sebuah issue.",
|
||||
"cap.tools.write.action": "mengubah hal di layanan lain dengan alat add-on",
|
||||
"action.unknown": "melakukan ini",
|
||||
"block.no_agent": "Koneksi ini tidak punya agen, jadi bot belum bisa menjawab atau bertindak.",
|
||||
"block.unknown_capability": "Server ini tidak mengenal izin itu.",
|
||||
"block.server_setting_off": "Administrator mematikannya untuk seluruh server, jadi bot tidak dapat {action}.",
|
||||
"block.tools_switch_off": "Alat yang dibutuhkan bot untuk {action} dimatikan untuk agen. Nyalakan di agen.",
|
||||
"block.agent_permission_off": "Agen tidak diizinkan untuk {action}. Nyalakan izin yang sesuai di agen.",
|
||||
"block.agent_unavailable": "Agen dari koneksi ini tidak dapat dibaca saat ini. Periksa apakah agen masih ada.",
|
||||
"block.connection_permission_off": "Koneksi ini tidak mengizinkan bot untuk {action}. Centang izin yang sesuai di koneksi.",
|
||||
"block.sender_not_listed": "Orang ini tidak ada dalam daftar orang yang dijawab bot. Tambahkan ke orang yang diizinkan.",
|
||||
"block.sender_needs_pairing": "Orang ini harus dipasangkan dengan bot sebelum bot dapat {action} untuknya.",
|
||||
"block.channel_not_trusted": "Orang di sebuah kanal tidak dipercaya satu per satu, jadi bot tidak dapat {action} untuk mereka. Tambahkan orang itu ke orang yang diizinkan.",
|
||||
"block.role_not_granted": "Peran orang ini tidak mengizinkan bot untuk {action}. Izinkan untuk peran itu di koneksi.",
|
||||
"block.kind_not_running": "Aplikasi untuk koneksi ini tidak berjalan, jadi peran tidak dapat diperiksa. Jalankan aplikasinya.",
|
||||
"block.context_rule": "Bot tidak dapat {action} di tempat ini.",
|
||||
"block.knowledge_none": "Tidak ada koleksi dalam daftar agen, jadi bot tidak punya tempat untuk {action}. Tambahkan koleksi ke agen.",
|
||||
"block.knowledge_no_embedding": "Model embedding untuk Pengetahuan belum dipasang, jadi bot tidak dapat {action}. Pasang di Model."
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"cap.proposals.confirm.title": "Conferma le modifiche dalla chat",
|
||||
"cap.proposals.confirm.description": "Chi scrive può confermare un trigger o una modifica proposta dall'agente (una nota di memoria, le regole dell'agente, una skill, una pagina, la dashboard o un testo per la Conoscenza) rispondendo sì o no nella stessa chat. Ogni modifica tranne la nota di memoria richiede una persona abbinata.",
|
||||
"cap.proposals.confirm.action": "confermare le proposte",
|
||||
"cap.memory.write.title": "Salva in memoria",
|
||||
"cap.memory.write.description": "L'agente può ricordare ciò che chi scrive gli chiede di ricordare.",
|
||||
"cap.memory.write.action": "salvare note in memoria",
|
||||
"cap.files.upload.title": "Ricevi file",
|
||||
"cap.files.upload.description": "L'agente può leggere i file che chi scrive allega a un messaggio.",
|
||||
"cap.files.upload.action": "ricevere file",
|
||||
"cap.triggers.propose.title": "Proponi trigger",
|
||||
"cap.triggers.propose.description": "L'agente può proporre un trigger a tempo o a evento quando chi scrive lo chiede.",
|
||||
"cap.triggers.propose.action": "impostare promemoria e attività pianificate",
|
||||
"cap.triggers.run.title": "Attiva trigger a evento",
|
||||
"cap.triggers.run.description": "Un messaggio di chi scrive può avviare l'esecuzione di un trigger a evento.",
|
||||
"cap.triggers.run.action": "eseguire attività pianificate",
|
||||
"cap.talkback.channels.title": "Scrivi nei canali approvati",
|
||||
"cap.talkback.channels.description": "L'agente può scrivere nei canali approvati qui sotto, quando chi scrive lo chiede, oltre che nella chat in cui risponde.",
|
||||
"cap.talkback.channels.action": "scrivere in altri canali",
|
||||
"cap.talkback.new_channel.title": "Scrivi in un nuovo canale",
|
||||
"cap.talkback.new_channel.description": "L'agente può scrivere in un canale non ancora approvato.",
|
||||
"cap.talkback.new_channel.action": "avviare conversazioni in nuovi canali",
|
||||
"cap.profile.write.title": "Modifica il profilo dell'agente",
|
||||
"cap.profile.write.description": "L'agente può modificare il proprio profilo.",
|
||||
"cap.profile.write.action": "modificare le regole dell'agente",
|
||||
"cap.skills.write.title": "Scrivi skill",
|
||||
"cap.skills.write.description": "L'agente può scrivere o modificare le skill.",
|
||||
"cap.skills.write.action": "salvare skill",
|
||||
"cap.pages.write.title": "Scrivi pagine",
|
||||
"cap.pages.write.description": "L'agente può scrivere o modificare le pagine.",
|
||||
"cap.pages.write.action": "pubblicare pagine",
|
||||
"cap.dashboard.write.title": "Scrivi la dashboard",
|
||||
"cap.dashboard.write.description": "L'agente può modificare la dashboard.",
|
||||
"cap.dashboard.write.action": "modificare la dashboard",
|
||||
"cap.knowledge.write.title": "Aggiungi alla Conoscenza",
|
||||
"cap.knowledge.write.description": "L'agente può proporre un testo per una raccolta della Conoscenza. Il proprietario conferma ogni testo.",
|
||||
"cap.knowledge.write.action": "scrivere nella Conoscenza",
|
||||
"cap.tools.write.title": "Modifica cose fuori da LocalAI",
|
||||
"cap.tools.write.description": "Uno strumento di un'app può modificare qualcosa con la credenziale del collegamento, come un commento a una issue.",
|
||||
"cap.tools.write.action": "modificare cose in altri servizi con gli strumenti delle app",
|
||||
"action.unknown": "farlo",
|
||||
"block.no_agent": "Questa connessione non ha un agente, quindi il bot non può ancora rispondere né agire.",
|
||||
"block.unknown_capability": "Questo server non conosce quel permesso.",
|
||||
"block.server_setting_off": "Un amministratore l'ha disattivato per tutto il server, quindi il bot non può {action}.",
|
||||
"block.tools_switch_off": "Gli strumenti di cui il bot ha bisogno per {action} sono disattivati per l'agente. Attivali nell'agente.",
|
||||
"block.agent_permission_off": "L'agente non ha il permesso di {action}. Attiva il permesso corrispondente nell'agente.",
|
||||
"block.agent_unavailable": "In questo momento non è possibile leggere l'agente di questa connessione. Controlla che esista ancora.",
|
||||
"block.connection_permission_off": "Questa connessione non permette al bot di {action}. Spunta il permesso corrispondente nella connessione.",
|
||||
"block.sender_not_listed": "Questa persona non è nell'elenco di chi il bot ascolta. Aggiungila alle persone consentite.",
|
||||
"block.sender_needs_pairing": "Questa persona deve essere abbinata al bot prima che il bot possa {action} per lei.",
|
||||
"block.channel_not_trusted": "Le persone in un canale non sono considerate affidabili una per una, quindi il bot non può {action} per loro. Aggiungi la persona alle persone consentite.",
|
||||
"block.role_not_granted": "Il ruolo di questa persona non permette al bot di {action}. Consentilo per quel ruolo nella connessione.",
|
||||
"block.kind_not_running": "L'app di questa connessione non è in esecuzione, quindi non si possono controllare i ruoli. Avvia l'app.",
|
||||
"block.context_rule": "Il bot non può {action} in questo posto.",
|
||||
"block.knowledge_none": "Nessuna raccolta è nell'elenco dell'agente, quindi il bot non ha dove {action}. Aggiungi una raccolta all'agente.",
|
||||
"block.knowledge_no_embedding": "Il modello di embedding per la Conoscenza non è installato, quindi il bot non può {action}. Installalo in Modelli."
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"cap.proposals.confirm.title": "채팅에서 변경 사항 확인",
|
||||
"cap.proposals.confirm.description": "보낸 사람은 같은 채팅에서 예 또는 아니요로 답하여 에이전트가 제안한 트리거나 변경 사항(메모리 노트, 에이전트 규칙, 스킬, 페이지, 대시보드 또는 지식 텍스트)을 확인할 수 있습니다. 메모리 노트를 제외한 모든 변경에는 페어링된 보낸 사람이 필요합니다.",
|
||||
"cap.proposals.confirm.action": "제안 확인",
|
||||
"cap.memory.write.title": "메모리에 저장",
|
||||
"cap.memory.write.description": "에이전트는 보낸 사람이 기억해 달라고 요청한 내용을 기억할 수 있습니다.",
|
||||
"cap.memory.write.action": "메모리에 노트 저장",
|
||||
"cap.files.upload.title": "파일 받기",
|
||||
"cap.files.upload.description": "에이전트는 보낸 사람이 메시지에 첨부한 파일을 읽을 수 있습니다.",
|
||||
"cap.files.upload.action": "파일 받기",
|
||||
"cap.triggers.propose.title": "트리거 제안",
|
||||
"cap.triggers.propose.description": "보낸 사람이 요청하면 에이전트가 타이머 또는 이벤트 트리거를 제안할 수 있습니다.",
|
||||
"cap.triggers.propose.action": "알림 및 예약 작업 설정",
|
||||
"cap.triggers.run.title": "이벤트 트리거 실행",
|
||||
"cap.triggers.run.description": "보낸 사람의 메시지가 이벤트 트리거의 실행을 시작할 수 있습니다.",
|
||||
"cap.triggers.run.action": "예약 작업 실행",
|
||||
"cap.talkback.channels.title": "승인된 채널에 쓰기",
|
||||
"cap.talkback.channels.description": "보낸 사람이 요청하면 에이전트는 답변하는 채팅 외에 아래의 승인된 채널에도 글을 쓸 수 있습니다.",
|
||||
"cap.talkback.channels.action": "다른 채널에 글 쓰기",
|
||||
"cap.talkback.new_channel.title": "새 채널에 쓰기",
|
||||
"cap.talkback.new_channel.description": "에이전트는 아직 승인되지 않은 채널에 글을 쓸 수 있습니다.",
|
||||
"cap.talkback.new_channel.action": "새 채널에서 대화 시작",
|
||||
"cap.profile.write.title": "에이전트 프로필 변경",
|
||||
"cap.profile.write.description": "에이전트는 자신의 프로필을 변경할 수 있습니다.",
|
||||
"cap.profile.write.action": "에이전트 규칙 변경",
|
||||
"cap.skills.write.title": "스킬 작성",
|
||||
"cap.skills.write.description": "에이전트는 스킬을 작성하거나 변경할 수 있습니다.",
|
||||
"cap.skills.write.action": "스킬 저장",
|
||||
"cap.pages.write.title": "페이지 작성",
|
||||
"cap.pages.write.description": "에이전트는 페이지를 작성하거나 변경할 수 있습니다.",
|
||||
"cap.pages.write.action": "페이지 게시",
|
||||
"cap.dashboard.write.title": "대시보드 작성",
|
||||
"cap.dashboard.write.description": "에이전트는 대시보드를 변경할 수 있습니다.",
|
||||
"cap.dashboard.write.action": "대시보드 변경",
|
||||
"cap.knowledge.write.title": "지식에 추가",
|
||||
"cap.knowledge.write.description": "에이전트는 지식의 컬렉션에 넣을 텍스트를 제안할 수 있습니다. 소유자가 각 텍스트를 확인합니다.",
|
||||
"cap.knowledge.write.action": "지식에 쓰기",
|
||||
"cap.tools.write.title": "LocalAI 밖의 항목 변경",
|
||||
"cap.tools.write.description": "애드온 도구는 연결의 자격 증명으로 무언가를 변경할 수 있습니다. 예를 들어 이슈에 댓글을 남기는 일입니다.",
|
||||
"cap.tools.write.action": "애드온 도구로 다른 서비스의 항목 변경",
|
||||
"action.unknown": "이 작업",
|
||||
"block.no_agent": "이 연결에는 에이전트가 없어서 봇이 아직 답하거나 동작할 수 없습니다.",
|
||||
"block.unknown_capability": "이 서버는 해당 권한을 알지 못합니다.",
|
||||
"block.server_setting_off": "관리자가 서버 전체에서 이 기능을 껐기 때문에 봇이 다음 작업을 할 수 없습니다: {action}.",
|
||||
"block.tools_switch_off": "봇이 다음 작업에 필요한 도구가 에이전트에서 꺼져 있습니다: {action}. 에이전트에서 켜 주세요.",
|
||||
"block.agent_permission_off": "에이전트에게 다음 작업의 권한이 없습니다: {action}. 에이전트에서 해당 권한을 켜 주세요.",
|
||||
"block.agent_unavailable": "지금은 이 연결의 에이전트를 읽을 수 없습니다. 에이전트가 아직 있는지 확인하세요.",
|
||||
"block.connection_permission_off": "이 연결은 봇이 다음 작업을 하도록 허용하지 않습니다: {action}. 연결에서 해당 권한을 선택해 주세요.",
|
||||
"block.sender_not_listed": "이 사람은 봇이 응답하는 사람 목록에 없습니다. 허용된 사람에 추가해 주세요.",
|
||||
"block.sender_needs_pairing": "봇이 이 사람을 위해 다음 작업을 하려면 먼저 이 사람이 봇과 페어링되어야 합니다: {action}.",
|
||||
"block.channel_not_trusted": "채널에 있는 사람은 한 명씩 신뢰되지 않으므로 봇이 그들을 위해 다음 작업을 할 수 없습니다: {action}. 이 사람을 허용된 사람에 추가해 주세요.",
|
||||
"block.role_not_granted": "이 사람의 역할로는 봇이 다음 작업을 할 수 없습니다: {action}. 연결에서 해당 역할에 허용해 주세요.",
|
||||
"block.kind_not_running": "이 연결의 앱이 실행 중이 아니어서 역할을 확인할 수 없습니다. 앱을 시작해 주세요.",
|
||||
"block.context_rule": "봇은 이 위치에서 다음 작업을 할 수 없습니다: {action}.",
|
||||
"block.knowledge_none": "에이전트의 목록에 컬렉션이 없어서 봇이 다음 작업을 할 곳이 없습니다: {action}. 에이전트에 컬렉션을 추가해 주세요.",
|
||||
"block.knowledge_no_embedding": "지식용 임베딩 모델이 설치되어 있지 않아 봇이 다음 작업을 할 수 없습니다: {action}. 모델에서 설치해 주세요."
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"cap.proposals.confirm.title": "Confirmar alterações pelo chat",
|
||||
"cap.proposals.confirm.description": "Quem envia pode confirmar um gatilho ou uma alteração que o agente propôs (uma nota de memória, as regras do agente, uma skill, uma página, o painel ou um texto para o Conhecimento) respondendo sim ou não no mesmo chat. Toda alteração, exceto uma nota de memória, exige uma pessoa pareada.",
|
||||
"cap.proposals.confirm.action": "confirmar propostas",
|
||||
"cap.memory.write.title": "Salvar na memória",
|
||||
"cap.memory.write.description": "O agente pode lembrar o que quem envia pede para ele lembrar.",
|
||||
"cap.memory.write.action": "salvar notas na memória",
|
||||
"cap.files.upload.title": "Receber arquivos",
|
||||
"cap.files.upload.description": "O agente pode ler os arquivos que quem envia anexa a uma mensagem.",
|
||||
"cap.files.upload.action": "receber arquivos",
|
||||
"cap.triggers.propose.title": "Propor gatilhos",
|
||||
"cap.triggers.propose.description": "O agente pode propor um gatilho de horário ou de evento quando quem envia pede.",
|
||||
"cap.triggers.propose.action": "criar lembretes e tarefas agendadas",
|
||||
"cap.triggers.run.title": "Disparar gatilhos de evento",
|
||||
"cap.triggers.run.description": "Uma mensagem de quem envia pode iniciar a execução de um gatilho de evento.",
|
||||
"cap.triggers.run.action": "executar tarefas agendadas",
|
||||
"cap.talkback.channels.title": "Escrever em canais aprovados",
|
||||
"cap.talkback.channels.description": "O agente pode escrever nos canais aprovados abaixo quando quem envia pede, além do chat em que responde.",
|
||||
"cap.talkback.channels.action": "escrever em outros canais",
|
||||
"cap.talkback.new_channel.title": "Escrever em um canal novo",
|
||||
"cap.talkback.new_channel.description": "O agente pode escrever em um canal que ainda não foi aprovado.",
|
||||
"cap.talkback.new_channel.action": "iniciar conversas em canais novos",
|
||||
"cap.profile.write.title": "Alterar o perfil do agente",
|
||||
"cap.profile.write.description": "O agente pode alterar o próprio perfil.",
|
||||
"cap.profile.write.action": "alterar as regras do agente",
|
||||
"cap.skills.write.title": "Escrever skills",
|
||||
"cap.skills.write.description": "O agente pode escrever ou alterar skills.",
|
||||
"cap.skills.write.action": "salvar skills",
|
||||
"cap.pages.write.title": "Escrever páginas",
|
||||
"cap.pages.write.description": "O agente pode escrever ou alterar páginas.",
|
||||
"cap.pages.write.action": "publicar páginas",
|
||||
"cap.dashboard.write.title": "Escrever o painel",
|
||||
"cap.dashboard.write.description": "O agente pode alterar o painel.",
|
||||
"cap.dashboard.write.action": "alterar o painel",
|
||||
"cap.knowledge.write.title": "Adicionar ao Conhecimento",
|
||||
"cap.knowledge.write.description": "O agente pode propor um texto para uma coleção do Conhecimento. O proprietário confirma cada um.",
|
||||
"cap.knowledge.write.action": "escrever no Conhecimento",
|
||||
"cap.tools.write.title": "Alterar coisas fora do LocalAI",
|
||||
"cap.tools.write.description": "Uma ferramenta de um complemento pode alterar algo com a credencial da vinculação, como um comentário em uma issue.",
|
||||
"cap.tools.write.action": "alterar coisas em outros serviços com ferramentas de complementos",
|
||||
"action.unknown": "fazer isso",
|
||||
"block.no_agent": "Esta conexão não tem agente, então o bot ainda não pode responder nem agir.",
|
||||
"block.unknown_capability": "Este servidor não conhece essa permissão.",
|
||||
"block.server_setting_off": "Um administrador desativou isso para todo o servidor, então o bot não pode {action}.",
|
||||
"block.tools_switch_off": "As ferramentas de que o bot precisa para {action} estão desativadas para o agente. Ative-as no agente.",
|
||||
"block.agent_permission_off": "O agente não tem permissão para {action}. Ative a permissão correspondente no agente.",
|
||||
"block.agent_unavailable": "Não é possível ler o agente desta conexão agora. Verifique se ele ainda existe.",
|
||||
"block.connection_permission_off": "Esta conexão não permite que o bot possa {action}. Marque a permissão correspondente na conexão.",
|
||||
"block.sender_not_listed": "Esta pessoa não está na lista de quem o bot atende. Adicione-a às pessoas permitidas.",
|
||||
"block.sender_needs_pairing": "Esta pessoa precisa estar pareada com o bot antes de o bot poder {action} para ela.",
|
||||
"block.channel_not_trusted": "As pessoas de um canal não são confiáveis uma a uma, então o bot não pode {action} para elas. Adicione a pessoa às pessoas permitidas.",
|
||||
"block.role_not_granted": "O papel desta pessoa não permite que o bot possa {action}. Permita para esse papel na conexão.",
|
||||
"block.kind_not_running": "O app desta conexão não está em execução, então os papéis não podem ser verificados. Inicie o app.",
|
||||
"block.context_rule": "O bot não pode {action} neste lugar.",
|
||||
"block.knowledge_none": "Nenhuma coleção está na lista do agente, então o bot não tem onde {action}. Adicione uma coleção ao agente.",
|
||||
"block.knowledge_no_embedding": "O modelo de embeddings do Conhecimento não está instalado, então o bot não pode {action}. Instale-o em Modelos."
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"cap.proposals.confirm.title": "在聊天中确认更改",
|
||||
"cap.proposals.confirm.description": "发送者可以在同一聊天中回复“是”或“否”,来确认代理提出的触发器或更改(记忆笔记、代理规则、技能、页面、仪表板或知识库文本)。除记忆笔记外,所有更改都需要已配对的发送者。",
|
||||
"cap.proposals.confirm.action": "确认提议",
|
||||
"cap.memory.write.title": "保存到记忆",
|
||||
"cap.memory.write.description": "代理可以记住发送者要求它记住的内容。",
|
||||
"cap.memory.write.action": "把笔记保存到记忆",
|
||||
"cap.files.upload.title": "接收文件",
|
||||
"cap.files.upload.description": "代理可以读取发送者附在消息中的文件。",
|
||||
"cap.files.upload.action": "接收文件",
|
||||
"cap.triggers.propose.title": "提议触发器",
|
||||
"cap.triggers.propose.description": "发送者提出要求时,代理可以提议定时触发器或事件触发器。",
|
||||
"cap.triggers.propose.action": "设置提醒和定时任务",
|
||||
"cap.triggers.run.title": "触发事件触发器",
|
||||
"cap.triggers.run.description": "发送者的一条消息可以启动一次事件触发器的运行。",
|
||||
"cap.triggers.run.action": "运行定时任务",
|
||||
"cap.talkback.channels.title": "向已批准的频道发帖",
|
||||
"cap.talkback.channels.description": "发送者提出要求时,代理除了在其回复所在的聊天外,还可以向下列已批准的频道发送消息。",
|
||||
"cap.talkback.channels.action": "向其他频道发送消息",
|
||||
"cap.talkback.new_channel.title": "向新频道发帖",
|
||||
"cap.talkback.new_channel.description": "代理可以向尚未批准的频道发送消息。",
|
||||
"cap.talkback.new_channel.action": "在新频道中发起对话",
|
||||
"cap.profile.write.title": "更改代理配置",
|
||||
"cap.profile.write.description": "代理可以更改自己的配置。",
|
||||
"cap.profile.write.action": "更改代理的规则",
|
||||
"cap.skills.write.title": "编写技能",
|
||||
"cap.skills.write.description": "代理可以编写或更改技能。",
|
||||
"cap.skills.write.action": "保存技能",
|
||||
"cap.pages.write.title": "编写页面",
|
||||
"cap.pages.write.description": "代理可以编写或更改页面。",
|
||||
"cap.pages.write.action": "发布页面",
|
||||
"cap.dashboard.write.title": "编写仪表板",
|
||||
"cap.dashboard.write.description": "代理可以更改仪表板。",
|
||||
"cap.dashboard.write.action": "更改仪表板",
|
||||
"cap.knowledge.write.title": "添加到知识库",
|
||||
"cap.knowledge.write.description": "代理可以为知识库的某个集合提议一段文本。每一段都由所有者确认。",
|
||||
"cap.knowledge.write.action": "写入知识库",
|
||||
"cap.tools.write.title": "更改 LocalAI 之外的内容",
|
||||
"cap.tools.write.description": "附加组件的工具可以使用绑定的凭据更改某些内容,例如在问题下发表评论。",
|
||||
"cap.tools.write.action": "使用附加组件工具更改其他服务中的内容",
|
||||
"action.unknown": "这样做",
|
||||
"block.no_agent": "此连接没有代理,因此机器人暂时无法回复或执行操作。",
|
||||
"block.unknown_capability": "此服务器不认识该权限。",
|
||||
"block.server_setting_off": "管理员已在整个服务器上关闭此功能,因此机器人无法{action}。",
|
||||
"block.tools_switch_off": "机器人{action}所需的工具已为该代理关闭。请在代理中打开它们。",
|
||||
"block.agent_permission_off": "该代理无权{action}。请在代理中打开相应的权限。",
|
||||
"block.agent_unavailable": "目前无法读取此连接的代理。请检查它是否仍然存在。",
|
||||
"block.connection_permission_off": "此连接不允许机器人{action}。请在连接中勾选相应的权限。",
|
||||
"block.sender_not_listed": "此人不在机器人回复的人员名单中。请把他们添加到允许的人员。",
|
||||
"block.sender_needs_pairing": "此人需要先与机器人配对,机器人才能为其{action}。",
|
||||
"block.channel_not_trusted": "频道中的人不会被逐一信任,因此机器人无法为他们{action}。请把此人添加到允许的人员。",
|
||||
"block.role_not_granted": "此人的角色不允许机器人{action}。请在连接中为该角色允许此操作。",
|
||||
"block.kind_not_running": "此连接的应用未运行,因此无法检查角色。请启动该应用。",
|
||||
"block.context_rule": "机器人不能在此处{action}。",
|
||||
"block.knowledge_none": "代理的列表中没有任何集合,因此机器人无处可{action}。请为代理添加一个集合。",
|
||||
"block.knowledge_no_embedding": "知识库的嵌入模型尚未安装,因此机器人无法{action}。请在“模型”中安装。"
|
||||
}
|
||||
@@ -901,6 +901,26 @@ sentence of every code, so `message` always reads as a whole sentence, for examp
|
||||
"People in a channel are not trusted one by one, so the bot cannot save notes to memory
|
||||
for them. Add the person to the allowed people."
|
||||
|
||||
### Text in the language of the page
|
||||
|
||||
The access view, the preview and the kinds call (which lists the capabilities) return
|
||||
English text and, beside it, the same text in the language of the request. The language
|
||||
is the `lang` query parameter (for example `it` or `pt-BR`), else the first language of
|
||||
`Accept-Language` that the host translates, else English. The host translates Italian,
|
||||
German, Spanish, French, Indonesian, Korean, Brazilian Portuguese and Simplified
|
||||
Chinese.
|
||||
|
||||
- Each decision keeps `code` and `message` and gains `localized_message`, found by
|
||||
`code`. The view gains `language`.
|
||||
- Each capability keeps `title` and `description` and gains `language`,
|
||||
`localized_title` and `localized_description`.
|
||||
|
||||
The `localized_*` fields are absent for English, for a language the host does not
|
||||
translate, and for a refusal that a rule of the feature wrote itself (it has no code).
|
||||
A page shows the localized field when it is there and the English one otherwise. The
|
||||
note for the model and the words a sender reads in the chat do not use these fields:
|
||||
the model reads English, and the sender reads the reply language of the connection.
|
||||
|
||||
### Preview before saving
|
||||
|
||||
`POST /api/chats/connectors/<id>/access/preview` answers like the access view for a form
|
||||
|
||||
+31
-1
@@ -5798,7 +5798,7 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Language tag to resolve the kind text for, such as it or pt-BR. Wins over Accept-Language. An unknown language gives the English text.",
|
||||
"description": "Language tag to resolve the kind text and the capability text for, such as it or pt-BR. Wins over Accept-Language. An unknown language gives the English text.",
|
||||
"name": "lang",
|
||||
"in": "query"
|
||||
}
|
||||
@@ -5912,6 +5912,12 @@ const docTemplate = `{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Language tag for localized_message, such as it or pt-BR. Wins over Accept-Language. English or an unknown language adds no localized field.",
|
||||
"name": "lang",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -5939,6 +5945,12 @@ const docTemplate = `{
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Language tag for localized_message, such as it or pt-BR. Wins over Accept-Language. English or an unknown language adds no localized field.",
|
||||
"name": "lang",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"description": "the form state to check",
|
||||
"name": "request",
|
||||
@@ -21974,6 +21986,10 @@ const docTemplate = `{
|
||||
"binding": {
|
||||
"type": "string"
|
||||
},
|
||||
"language": {
|
||||
"description": "Language is the language of the localized_message fields, negotiated\nfrom the lang query parameter, then Accept-Language. Absent for\nEnglish.",
|
||||
"type": "string"
|
||||
},
|
||||
"preview": {
|
||||
"description": "Preview is true when the view is for a form that is not saved.",
|
||||
"type": "boolean"
|
||||
@@ -22017,6 +22033,10 @@ const docTemplate = `{
|
||||
}
|
||||
]
|
||||
},
|
||||
"localized_message": {
|
||||
"description": "LocalizedMessage is message in the language of the request (see\nChatsConnectorAccess.Language), found by code. Absent when the\nlanguage is English or the code has no translation; message stays\nEnglish and code stays unchanged.",
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"description": "Message is one plain sentence for the owner. It names no setting.",
|
||||
"type": "string"
|
||||
@@ -22177,6 +22197,16 @@ const docTemplate = `{
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"language": {
|
||||
"description": "Language is the language of localized_title and localized_description:\nthe one the request asked for (lang, else Accept-Language) among the\ntranslated ones. Absent for English and for a language with no\ntranslation; title and description stay English.",
|
||||
"type": "string"
|
||||
},
|
||||
"localized_description": {
|
||||
"type": "string"
|
||||
},
|
||||
"localized_title": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
+31
-1
@@ -5795,7 +5795,7 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Language tag to resolve the kind text for, such as it or pt-BR. Wins over Accept-Language. An unknown language gives the English text.",
|
||||
"description": "Language tag to resolve the kind text and the capability text for, such as it or pt-BR. Wins over Accept-Language. An unknown language gives the English text.",
|
||||
"name": "lang",
|
||||
"in": "query"
|
||||
}
|
||||
@@ -5909,6 +5909,12 @@
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Language tag for localized_message, such as it or pt-BR. Wins over Accept-Language. English or an unknown language adds no localized field.",
|
||||
"name": "lang",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -5936,6 +5942,12 @@
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Language tag for localized_message, such as it or pt-BR. Wins over Accept-Language. English or an unknown language adds no localized field.",
|
||||
"name": "lang",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"description": "the form state to check",
|
||||
"name": "request",
|
||||
@@ -21971,6 +21983,10 @@
|
||||
"binding": {
|
||||
"type": "string"
|
||||
},
|
||||
"language": {
|
||||
"description": "Language is the language of the localized_message fields, negotiated\nfrom the lang query parameter, then Accept-Language. Absent for\nEnglish.",
|
||||
"type": "string"
|
||||
},
|
||||
"preview": {
|
||||
"description": "Preview is true when the view is for a form that is not saved.",
|
||||
"type": "boolean"
|
||||
@@ -22014,6 +22030,10 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"localized_message": {
|
||||
"description": "LocalizedMessage is message in the language of the request (see\nChatsConnectorAccess.Language), found by code. Absent when the\nlanguage is English or the code has no translation; message stays\nEnglish and code stays unchanged.",
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"description": "Message is one plain sentence for the owner. It names no setting.",
|
||||
"type": "string"
|
||||
@@ -22174,6 +22194,16 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"language": {
|
||||
"description": "Language is the language of localized_title and localized_description:\nthe one the request asked for (lang, else Accept-Language) among the\ntranslated ones. Absent for English and for a language with no\ntranslation; title and description stay English.",
|
||||
"type": "string"
|
||||
},
|
||||
"localized_description": {
|
||||
"type": "string"
|
||||
},
|
||||
"localized_title": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
+37
-2
@@ -4785,6 +4785,12 @@ definitions:
|
||||
properties:
|
||||
binding:
|
||||
type: string
|
||||
language:
|
||||
description: |-
|
||||
Language is the language of the localized_message fields, negotiated
|
||||
from the lang query parameter, then Accept-Language. Absent for
|
||||
English.
|
||||
type: string
|
||||
preview:
|
||||
description: Preview is true when the view is for a form that is not saved.
|
||||
type: boolean
|
||||
@@ -4822,6 +4828,13 @@ definitions:
|
||||
- $ref: '#/definitions/schema.ChatsConnectorAccessFix'
|
||||
description: Fix points at the one place that solves it. The server sends
|
||||
no URL.
|
||||
localized_message:
|
||||
description: |-
|
||||
LocalizedMessage is message in the language of the request (see
|
||||
ChatsConnectorAccess.Language), found by code. Absent when the
|
||||
language is English or the code has no translation; message stays
|
||||
English and code stays unchanged.
|
||||
type: string
|
||||
message:
|
||||
description: Message is one plain sentence for the owner. It names no setting.
|
||||
type: string
|
||||
@@ -4942,6 +4955,17 @@ definitions:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
language:
|
||||
description: |-
|
||||
Language is the language of localized_title and localized_description:
|
||||
the one the request asked for (lang, else Accept-Language) among the
|
||||
translated ones. Absent for English and for a language with no
|
||||
translation; title and description stay English.
|
||||
type: string
|
||||
localized_description:
|
||||
type: string
|
||||
localized_title:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
sensitive:
|
||||
@@ -17450,6 +17474,11 @@ paths:
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Language tag for localized_message, such as it or pt-BR. Wins
|
||||
over Accept-Language. English or an unknown language adds no localized field.
|
||||
in: query
|
||||
name: lang
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: The effective permissions
|
||||
@@ -17475,6 +17504,11 @@ paths:
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Language tag for localized_message, such as it or pt-BR. Wins
|
||||
over Accept-Language. English or an unknown language adds no localized field.
|
||||
in: query
|
||||
name: lang
|
||||
type: string
|
||||
- description: the form state to check
|
||||
in: body
|
||||
name: request
|
||||
@@ -17638,8 +17672,9 @@ paths:
|
||||
(lang, else Accept-Language), language, localized_title and localized_description
|
||||
with English as the fallback for each field; title and description stay English.
|
||||
parameters:
|
||||
- description: Language tag to resolve the kind text for, such as it or pt-BR.
|
||||
Wins over Accept-Language. An unknown language gives the English text.
|
||||
- description: Language tag to resolve the kind text and the capability text
|
||||
for, such as it or pt-BR. Wins over Accept-Language. An unknown language
|
||||
gives the English text.
|
||||
in: query
|
||||
name: lang
|
||||
type: string
|
||||
|
||||
Reference in new issue
Block a user