diff --git a/addons/discord/manifest.json b/addons/discord/manifest.json
index 162e4337a3..d4706d1530 100644
--- a/addons/discord/manifest.json
+++ b/addons/discord/manifest.json
@@ -50,8 +50,38 @@
"pages": ["bots"],
"mainPage": "bots",
"icon": "ui/icon.png",
- "title": "Discord",
- "description": "Bind a Discord bot to one of your saved agents, so people can talk to it from Discord."
+ "title": {
+ "en": "Discord",
+ "it": "Discord",
+ "de": "Discord",
+ "es": "Discord",
+ "fr": "Discord",
+ "pt-BR": "Discord",
+ "zh-CN": "Discord",
+ "ko": "Discord",
+ "id": "Discord"
+ },
+ "description": {
+ "en": "Bind a Discord bot to one of your saved agents, so people can talk to it from Discord.",
+ "it": "Collega un bot Discord a uno dei tuoi agenti salvati, così le persone possono parlarci da Discord.",
+ "de": "Verbinden Sie einen Discord-Bot mit einem Ihrer gespeicherten Agenten, damit andere über Discord mit ihm sprechen können.",
+ "es": "Vincula un bot de Discord a uno de tus agentes guardados para que la gente pueda hablar con él desde Discord.",
+ "fr": "Associez un bot Discord à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Discord.",
+ "pt-BR": "Vincule um bot do Discord a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Discord.",
+ "zh-CN": "将 Discord 机器人绑定到你保存的某个智能体,让大家可以在 Discord 中与它对话。",
+ "ko": "저장한 에이전트 중 하나에 Discord 봇을 연결하여 사람들이 Discord에서 대화할 수 있게 합니다.",
+ "id": "Hubungkan bot Discord ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Discord."
+ }
}
+ },
+ "i18n": {
+ "it": {"kinds": {"discord": {"title": "Discord", "description": "Parla con uno dei tuoi agenti salvati da Discord, nei messaggi diretti e dove il bot viene menzionato in un canale o in un thread."}}},
+ "de": {"kinds": {"discord": {"title": "Discord", "description": "Sprechen Sie über Discord mit einem Ihrer gespeicherten Agenten, in Direktnachrichten und dort, wo der Bot in einem Kanal oder Thread erwähnt wird."}}},
+ "es": {"kinds": {"discord": {"title": "Discord", "description": "Habla con uno de tus agentes guardados desde Discord, en mensajes directos y donde se mencione al bot en un canal o hilo."}}},
+ "fr": {"kinds": {"discord": {"title": "Discord", "description": "Parlez à l'un de vos agents enregistrés depuis Discord, en messages privés et là où le bot est mentionné dans un salon ou un fil."}}},
+ "pt-BR": {"kinds": {"discord": {"title": "Discord", "description": "Converse com um dos seus agentes salvos pelo Discord, em mensagens diretas e onde o bot for mencionado em um canal ou tópico."}}},
+ "zh-CN": {"kinds": {"discord": {"title": "Discord", "description": "在 Discord 中与你保存的某个智能体对话,支持私信,也支持在频道或子区中提及机器人。"}}},
+ "ko": {"kinds": {"discord": {"title": "Discord", "description": "Discord에서 저장한 에이전트 중 하나와 대화합니다. 다이렉트 메시지와, 채널이나 스레드에서 봇이 멘션된 곳에서 사용할 수 있습니다."}}},
+ "id": {"kinds": {"discord": {"title": "Discord", "description": "Berbicara dengan salah satu agen tersimpan Anda dari Discord, lewat pesan langsung dan di tempat bot disebut dalam kanal atau utas."}}}
}
}
diff --git a/addons/face/manifest.json b/addons/face/manifest.json
index 2d9167d338..49acab825c 100644
--- a/addons/face/manifest.json
+++ b/addons/face/manifest.json
@@ -19,8 +19,28 @@
"modelActions": [{"capability": "FLAG_FACE_RECOGNITION", "page": "recognition"}],
"mainPage": "recognition",
"icon": "ui/icon.png",
- "title": "Face",
- "description": "Find out whose face is in a photo, or whether two photos show the same person."
+ "title": {
+ "en": "Face",
+ "it": "Volto",
+ "de": "Gesicht",
+ "es": "Rostro",
+ "fr": "Visage",
+ "pt-BR": "Rosto",
+ "zh-CN": "人脸",
+ "ko": "얼굴",
+ "id": "Wajah"
+ },
+ "description": {
+ "en": "Find out whose face is in a photo, or whether two photos show the same person.",
+ "it": "Scopri di chi è il volto in una foto, o se due foto mostrano la stessa persona.",
+ "de": "Finden Sie heraus, wessen Gesicht auf einem Foto zu sehen ist oder ob zwei Fotos dieselbe Person zeigen.",
+ "es": "Descubre de quién es el rostro de una foto, o si dos fotos muestran a la misma persona.",
+ "fr": "Découvrez à qui appartient le visage sur une photo, ou si deux photos montrent la même personne.",
+ "pt-BR": "Descubra de quem é o rosto em uma foto, ou se duas fotos mostram a mesma pessoa.",
+ "zh-CN": "找出照片中的人是谁,或判断两张照片是否是同一个人。",
+ "ko": "사진 속 얼굴이 누구인지, 또는 두 사진이 같은 사람인지 확인합니다.",
+ "id": "Cari tahu wajah siapa yang ada di sebuah foto, atau apakah dua foto menunjukkan orang yang sama."
+ }
}
}
}
diff --git a/addons/fine-tuning/manifest.json b/addons/fine-tuning/manifest.json
index 94233ee376..836d1e6d78 100644
--- a/addons/fine-tuning/manifest.json
+++ b/addons/fine-tuning/manifest.json
@@ -287,8 +287,28 @@
"navigation": [],
"mainPage": "jobs",
"icon": "ui/icon.png",
- "title": "Fine-tuning",
- "description": "Train an adapter on your own data, then export it as a model you can chat with."
+ "title": {
+ "en": "Fine-tuning",
+ "it": "Fine-tuning",
+ "de": "Fine-Tuning",
+ "es": "Ajuste fino",
+ "fr": "Fine-tuning",
+ "pt-BR": "Ajuste fino",
+ "zh-CN": "微调",
+ "ko": "파인튜닝",
+ "id": "Fine-tuning"
+ },
+ "description": {
+ "en": "Train an adapter on your own data, then export it as a model you can chat with.",
+ "it": "Addestra un adapter sui tuoi dati, poi esportalo come modello con cui puoi chattare.",
+ "de": "Trainieren Sie einen Adapter mit Ihren eigenen Daten und exportieren Sie ihn dann als Modell, mit dem Sie chatten können.",
+ "es": "Entrena un adaptador con tus propios datos y expórtalo como un modelo con el que puedes chatear.",
+ "fr": "Entraînez un adaptateur sur vos propres données, puis exportez-le sous forme de modèle avec lequel vous pouvez discuter.",
+ "pt-BR": "Treine um adaptador com seus próprios dados e depois exporte-o como um modelo com o qual você pode conversar.",
+ "zh-CN": "用你自己的数据训练适配器,然后将其导出为可以聊天的模型。",
+ "ko": "내 데이터로 어댑터를 학습한 다음, 채팅할 수 있는 모델로 내보냅니다.",
+ "id": "Latih adapter dengan data Anda sendiri, lalu ekspor sebagai model yang dapat diajak mengobrol."
+ }
}
}
}
diff --git a/addons/github/manifest.json b/addons/github/manifest.json
index d8cbd62fb3..a2189e7129 100644
--- a/addons/github/manifest.json
+++ b/addons/github/manifest.json
@@ -621,8 +621,38 @@
{"kind": "approval-card", "tool": "review-comment-pr", "component": "ProposalCard"}
],
"icon": "ui/icon.png",
- "title": "GitHub",
- "description": "Let your agents read the code, issues and pull requests of the repositories you choose, and write to them if you allow it."
+ "title": {
+ "en": "GitHub",
+ "it": "GitHub",
+ "de": "GitHub",
+ "es": "GitHub",
+ "fr": "GitHub",
+ "pt-BR": "GitHub",
+ "zh-CN": "GitHub",
+ "ko": "GitHub",
+ "id": "GitHub"
+ },
+ "description": {
+ "en": "Let your agents read the code, issues and pull requests of the repositories you choose, and write to them if you allow it.",
+ "it": "Lascia che i tuoi agenti leggano il codice, le issue e le pull request dei repository che scegli, e ci scrivano se lo permetti.",
+ "de": "Lassen Sie Ihre Agenten den Code, die Issues und die Pull Requests der von Ihnen gewählten Repositorys lesen und, wenn Sie es erlauben, darin schreiben.",
+ "es": "Permite que tus agentes lean el código, las issues y los pull requests de los repositorios que elijas y escriban en ellos si lo autorizas.",
+ "fr": "Laissez vos agents lire le code, les issues et les pull requests des dépôts que vous choisissez, et y écrire si vous l'autorisez.",
+ "pt-BR": "Deixe seus agentes lerem o código, as issues e os pull requests dos repositórios que você escolher, e escreverem neles se você permitir.",
+ "zh-CN": "让你的智能体读取你所选仓库的代码、Issue 和 Pull Request,并在你允许时向其中写入。",
+ "ko": "에이전트가 선택한 저장소의 코드, 이슈, 풀 리퀘스트를 읽고, 허용하면 그곳에 쓸 수 있게 합니다.",
+ "id": "Izinkan agen Anda membaca kode, isu, dan pull request dari repositori yang Anda pilih, dan menulis di sana jika Anda mengizinkan."
+ }
}
+ },
+ "i18n": {
+ "it": {"kinds": {"github": {"title": "GitHub", "description": "Lascia che i tuoi agenti leggano i repository che scegli e commentino issue e pull request se lo permetti, con un token di accesso granulare."}}},
+ "de": {"kinds": {"github": {"title": "GitHub", "description": "Lassen Sie Ihre Agenten die von Ihnen gewählten Repositorys lesen und, wenn Sie es erlauben, Issues und Pull Requests kommentieren, mit einem fein abgestuften Zugriffstoken."}}},
+ "es": {"kinds": {"github": {"title": "GitHub", "description": "Permite que tus agentes lean los repositorios que elijas y comenten en issues y pull requests si lo autorizas, con un token de acceso de permisos específicos."}}},
+ "fr": {"kinds": {"github": {"title": "GitHub", "description": "Laissez vos agents lire les dépôts que vous choisissez et commenter les issues et les pull requests si vous l'autorisez, avec un jeton d'accès à granularité fine."}}},
+ "pt-BR": {"kinds": {"github": {"title": "GitHub", "description": "Deixe seus agentes lerem os repositórios que você escolher e comentarem em issues e pull requests se você permitir, com um token de acesso refinado."}}},
+ "zh-CN": {"kinds": {"github": {"title": "GitHub", "description": "让你的智能体读取你所选的仓库,并在你允许时评论 Issue 和 Pull Request,使用细粒度访问令牌。"}}},
+ "ko": {"kinds": {"github": {"title": "GitHub", "description": "에이전트가 선택한 저장소를 읽고, 허용하면 이슈와 풀 리퀘스트에 댓글을 달 수 있게 합니다. 세분화된 액세스 토큰을 사용합니다."}}},
+ "id": {"kinds": {"github": {"title": "GitHub", "description": "Izinkan agen Anda membaca repositori yang Anda pilih, dan mengomentari isu serta pull request jika Anda mengizinkan, dengan token akses terperinci."}}}
}
}
diff --git a/addons/home-assistant/manifest.json b/addons/home-assistant/manifest.json
index 54e423379c..08669e6600 100644
--- a/addons/home-assistant/manifest.json
+++ b/addons/home-assistant/manifest.json
@@ -481,8 +481,38 @@
}
],
"icon": "ui/icon.png",
- "title": "Home Assistant",
- "description": "Let your chats and agents read your smart home and, if you allow it, switch lights and other things, with limits you set."
+ "title": {
+ "en": "Home Assistant",
+ "it": "Home Assistant",
+ "de": "Home Assistant",
+ "es": "Home Assistant",
+ "fr": "Home Assistant",
+ "pt-BR": "Home Assistant",
+ "zh-CN": "Home Assistant",
+ "ko": "Home Assistant",
+ "id": "Home Assistant"
+ },
+ "description": {
+ "en": "Let your chats and agents read your smart home and, if you allow it, switch lights and other things, with limits you set.",
+ "it": "Lascia che le tue chat e i tuoi agenti leggano la tua casa intelligente e, se lo permetti, accendano le luci e altri dispositivi, entro i limiti che imposti.",
+ "de": "Lassen Sie Ihre Chats und Agenten Ihr Smart Home auslesen und, wenn Sie es erlauben, Lichter und andere Geräte schalten, innerhalb der von Ihnen gesetzten Grenzen.",
+ "es": "Permite que tus chats y agentes lean tu hogar inteligente y, si lo autorizas, enciendan luces y controlen otros dispositivos, dentro de los límites que establezcas.",
+ "fr": "Laissez vos conversations et vos agents lire votre maison connectée et, si vous l'autorisez, allumer les lumières et commander d'autres appareils, dans les limites que vous fixez.",
+ "pt-BR": "Deixe seus chats e agentes lerem sua casa inteligente e, se você permitir, ligarem luzes e controlarem outros dispositivos, dentro dos limites que você define.",
+ "zh-CN": "让你的聊天和智能体读取你的智能家居,并在你允许时开关灯光等设备,权限范围由你设定。",
+ "ko": "채팅과 에이전트가 스마트홈을 읽고, 허용하면 조명 등 기기를 켜고 끌 수 있게 합니다. 한도는 직접 정합니다.",
+ "id": "Izinkan obrolan dan agen Anda membaca rumah pintar Anda dan, jika Anda mengizinkan, menyalakan lampu serta perangkat lain, dengan batas yang Anda tentukan."
+ }
}
+ },
+ "i18n": {
+ "it": {"kinds": {"home-assistant": {"title": "Home Assistant", "description": "Lascia che le tue chat e i tuoi agenti leggano la tua casa intelligente e comandino i dispositivi se lo permetti, con un token di accesso a lunga durata."}}},
+ "de": {"kinds": {"home-assistant": {"title": "Home Assistant", "description": "Lassen Sie Ihre Chats und Agenten Ihr Smart Home auslesen und, wenn Sie es erlauben, Geräte schalten, mit einem langlebigen Zugriffstoken."}}},
+ "es": {"kinds": {"home-assistant": {"title": "Home Assistant", "description": "Permite que tus chats y agentes lean tu hogar inteligente y controlen dispositivos si lo autorizas, con un token de acceso de larga duración."}}},
+ "fr": {"kinds": {"home-assistant": {"title": "Home Assistant", "description": "Laissez vos conversations et vos agents lire votre maison connectée et commander des appareils si vous l'autorisez, avec un jeton d'accès longue durée."}}},
+ "pt-BR": {"kinds": {"home-assistant": {"title": "Home Assistant", "description": "Deixe seus chats e agentes lerem sua casa inteligente e controlarem dispositivos se você permitir, com um token de acesso de longa duração."}}},
+ "zh-CN": {"kinds": {"home-assistant": {"title": "Home Assistant", "description": "让你的聊天和智能体读取你的智能家居,并在你允许时控制设备,使用长期访问令牌。"}}},
+ "ko": {"kinds": {"home-assistant": {"title": "Home Assistant", "description": "채팅과 에이전트가 스마트홈을 읽고, 허용하면 기기를 제어할 수 있게 합니다. 장기 액세스 토큰을 사용합니다."}}},
+ "id": {"kinds": {"home-assistant": {"title": "Home Assistant", "description": "Izinkan obrolan dan agen Anda membaca rumah pintar Anda dan mengendalikan perangkat jika Anda mengizinkan, dengan token akses berumur panjang."}}}
}
}
diff --git a/addons/jellyfin/manifest.json b/addons/jellyfin/manifest.json
index 3258140658..93bc7b814c 100644
--- a/addons/jellyfin/manifest.json
+++ b/addons/jellyfin/manifest.json
@@ -688,8 +688,38 @@
}
],
"icon": "ui/icon.png",
- "title": "Jellyfin",
- "description": "Let your agents search your Jellyfin library, see what is playing, and remote control players if you allow it."
+ "title": {
+ "en": "Jellyfin",
+ "it": "Jellyfin",
+ "de": "Jellyfin",
+ "es": "Jellyfin",
+ "fr": "Jellyfin",
+ "pt-BR": "Jellyfin",
+ "zh-CN": "Jellyfin",
+ "ko": "Jellyfin",
+ "id": "Jellyfin"
+ },
+ "description": {
+ "en": "Let your agents search your Jellyfin library, see what is playing, and remote control players if you allow it.",
+ "it": "Lascia che i tuoi agenti cerchino nella tua libreria Jellyfin, vedano cosa è in riproduzione e controllino i player a distanza se lo permetti.",
+ "de": "Lassen Sie Ihre Agenten Ihre Jellyfin-Bibliothek durchsuchen, sehen, was gerade läuft, und, wenn Sie es erlauben, Wiedergabegeräte fernsteuern.",
+ "es": "Permite que tus agentes busquen en tu biblioteca de Jellyfin, vean qué se está reproduciendo y, si lo autorizas, controlen los reproductores de forma remota.",
+ "fr": "Laissez vos agents chercher dans votre bibliothèque Jellyfin, voir ce qui est en cours de lecture et, si vous l'autorisez, contrôler les lecteurs à distance.",
+ "pt-BR": "Deixe seus agentes pesquisarem na sua biblioteca do Jellyfin, verem o que está tocando e, se você permitir, controlarem os players remotamente.",
+ "zh-CN": "让你的智能体搜索你的 Jellyfin 媒体库,查看正在播放的内容,并在你允许时远程控制播放器。",
+ "ko": "에이전트가 Jellyfin 라이브러리를 검색하고 재생 중인 항목을 확인하며, 허용하면 플레이어를 원격으로 제어할 수 있게 합니다.",
+ "id": "Izinkan agen Anda mencari di pustaka Jellyfin Anda, melihat apa yang sedang diputar, dan mengendalikan pemutar dari jarak jauh jika Anda mengizinkan."
+ }
}
+ },
+ "i18n": {
+ "it": {"kinds": {"jellyfin": {"title": "Jellyfin", "description": "Lascia che i tuoi agenti consultino la tua libreria multimediale, vedano cosa è in riproduzione e controllino i player a distanza se lo permetti, con una chiave API del tuo server Jellyfin."}}},
+ "de": {"kinds": {"jellyfin": {"title": "Jellyfin", "description": "Lassen Sie Ihre Agenten Ihre Medienbibliothek durchsuchen, sehen, was gerade läuft, und, wenn Sie es erlauben, Wiedergabegeräte fernsteuern, mit einem API-Schlüssel Ihres Jellyfin-Servers."}}},
+ "es": {"kinds": {"jellyfin": {"title": "Jellyfin", "description": "Permite que tus agentes consulten tu biblioteca multimedia, vean qué se reproduce y, si lo autorizas, controlen los reproductores de forma remota, con una clave de API de tu servidor Jellyfin."}}},
+ "fr": {"kinds": {"jellyfin": {"title": "Jellyfin", "description": "Laissez vos agents consulter votre médiathèque, voir ce qui est en cours de lecture et, si vous l'autorisez, contrôler les lecteurs à distance, avec une clé API de votre serveur Jellyfin."}}},
+ "pt-BR": {"kinds": {"jellyfin": {"title": "Jellyfin", "description": "Deixe seus agentes consultarem sua biblioteca de mídia, verem o que está tocando e, se você permitir, controlarem os players remotamente, com uma chave de API do seu servidor Jellyfin."}}},
+ "zh-CN": {"kinds": {"jellyfin": {"title": "Jellyfin", "description": "让你的智能体查询你的媒体库,查看正在播放的内容,并在你允许时远程控制播放器,使用来自你的 Jellyfin 服务器的 API 密钥。"}}},
+ "ko": {"kinds": {"jellyfin": {"title": "Jellyfin", "description": "에이전트가 미디어 라이브러리를 조회하고 재생 중인 항목을 확인하며, 허용하면 플레이어를 원격으로 제어할 수 있게 합니다. Jellyfin 서버의 API 키를 사용합니다."}}},
+ "id": {"kinds": {"jellyfin": {"title": "Jellyfin", "description": "Izinkan agen Anda mencari di pustaka media Anda, melihat apa yang sedang diputar, dan mengendalikan pemutar dari jarak jauh jika Anda mengizinkan, dengan kunci API dari server Jellyfin Anda."}}}
}
}
diff --git a/addons/memory/manifest.json b/addons/memory/manifest.json
index c5b68e5a6d..5f3fcec7e8 100644
--- a/addons/memory/manifest.json
+++ b/addons/memory/manifest.json
@@ -29,8 +29,28 @@
"pages": ["memory"],
"mainPage": "memory",
"icon": "ui/icon.png",
- "title": "Short memory",
- "description": "Your chats keep short notes about you and read them before they answer. By default a new note is saved only after you confirm it; you can choose to have it saved at once with an Undo, or never."
+ "title": {
+ "en": "Short memory",
+ "it": "Memoria breve",
+ "de": "Kurzzeitgedächtnis",
+ "es": "Memoria breve",
+ "fr": "Mémoire courte",
+ "pt-BR": "Memória curta",
+ "zh-CN": "简短记忆",
+ "ko": "짧은 기억",
+ "id": "Memori singkat"
+ },
+ "description": {
+ "en": "Your chats keep short notes about you and read them before they answer. By default a new note is saved only after you confirm it; you can choose to have it saved at once with an Undo, or never.",
+ "it": "Le tue chat conservano brevi note su di te e le leggono prima di rispondere. Di norma una nuova nota viene salvata solo dopo la tua conferma; puoi scegliere di salvarla subito con un Annulla, oppure mai.",
+ "de": "Ihre Chats führen kurze Notizen über Sie und lesen sie, bevor sie antworten. Standardmäßig wird eine neue Notiz erst nach Ihrer Bestätigung gespeichert; Sie können sie auch sofort mit Rückgängig-Option speichern lassen oder nie.",
+ "es": "Tus chats guardan notas breves sobre ti y las leen antes de responder. De forma predeterminada, una nota nueva se guarda solo después de que la confirmes; puedes elegir que se guarde al instante con la opción de deshacer, o que nunca se guarde.",
+ "fr": "Vos conversations gardent de courtes notes sur vous et les lisent avant de répondre. Par défaut, une nouvelle note n'est enregistrée qu'après votre confirmation ; vous pouvez choisir de l'enregistrer immédiatement avec une option Annuler, ou jamais.",
+ "pt-BR": "Seus chats guardam notas curtas sobre você e as leem antes de responder. Por padrão, uma nova nota só é salva depois que você a confirma; você pode escolher salvá-la na hora, com a opção de desfazer, ou nunca.",
+ "zh-CN": "你的聊天会记下关于你的简短笔记,并在回答前读取它们。默认情况下,新笔记须经你确认后才会保存;你也可以选择立即保存并提供撤销,或从不保存。",
+ "ko": "채팅이 나에 대한 짧은 메모를 보관하고, 답하기 전에 읽습니다. 기본적으로 새 메모는 확인한 후에만 저장되며, 실행 취소와 함께 바로 저장하거나 저장하지 않도록 선택할 수 있습니다.",
+ "id": "Obrolan Anda menyimpan catatan singkat tentang Anda dan membacanya sebelum menjawab. Secara bawaan, catatan baru disimpan hanya setelah Anda mengonfirmasinya; Anda dapat memilih agar langsung disimpan dengan opsi Urungkan, atau tidak pernah."
+ }
}
}
}
diff --git a/addons/office/manifest.json b/addons/office/manifest.json
index e29a09ff4b..2858ed6d1f 100644
--- a/addons/office/manifest.json
+++ b/addons/office/manifest.json
@@ -504,7 +504,17 @@
"css": [
"ui/office.css"
],
- "description": "Lightweight basic Word, Excel, PowerPoint and PDF file work from your chats, without requiring Sandbox.",
+ "description": {
+ "en": "Lightweight basic Word, Excel, PowerPoint and PDF file work from your chats, without requiring Sandbox.",
+ "it": "Lavoro leggero e di base su file Word, Excel, PowerPoint e PDF dalle tue chat, senza richiedere Sandbox.",
+ "de": "Einfache Arbeit mit Word-, Excel-, PowerPoint- und PDF-Dateien direkt aus Ihren Chats, ohne dass Sandbox nötig ist.",
+ "es": "Trabajo ligero y básico con archivos de Word, Excel, PowerPoint y PDF desde tus chats, sin necesidad de Sandbox.",
+ "fr": "Travail léger et de base sur des fichiers Word, Excel, PowerPoint et PDF depuis vos conversations, sans avoir besoin de Sandbox.",
+ "pt-BR": "Trabalho leve e básico com arquivos Word, Excel, PowerPoint e PDF a partir dos seus chats, sem precisar do Sandbox.",
+ "zh-CN": "在聊天中轻量地处理基础的 Word、Excel、PowerPoint 和 PDF 文件,无需 Sandbox。",
+ "ko": "Sandbox 없이도 채팅에서 Word, Excel, PowerPoint, PDF 파일의 기본 작업을 가볍게 처리합니다.",
+ "id": "Pekerjaan ringan dan dasar dengan berkas Word, Excel, PowerPoint, dan PDF dari obrolan Anda, tanpa memerlukan Sandbox."
+ },
"entry": "ui/office.js",
"icon": "ui/icon.png",
"mainPage": "office",
@@ -527,7 +537,17 @@
"slot": "files.preview"
}
],
- "title": "Simple Office"
+ "title": {
+ "en": "Simple Office",
+ "it": "Office semplice",
+ "de": "Einfaches Office",
+ "es": "Office sencillo",
+ "fr": "Office simple",
+ "pt-BR": "Office simples",
+ "zh-CN": "简易 Office",
+ "ko": "간단한 Office",
+ "id": "Office Sederhana"
+ }
}
},
"dataFormat": {
diff --git a/addons/quantization/manifest.json b/addons/quantization/manifest.json
index 7ca6fb0f64..b4d87286be 100644
--- a/addons/quantization/manifest.json
+++ b/addons/quantization/manifest.json
@@ -163,8 +163,28 @@
"navigation": [],
"mainPage": "jobs",
"icon": "ui/icon.png",
- "title": "Quantization",
- "description": "Shrink a model to a smaller quantized file, then import it so you can chat with it."
+ "title": {
+ "en": "Quantization",
+ "it": "Quantizzazione",
+ "de": "Quantisierung",
+ "es": "Cuantización",
+ "fr": "Quantification",
+ "pt-BR": "Quantização",
+ "zh-CN": "量化",
+ "ko": "양자화",
+ "id": "Kuantisasi"
+ },
+ "description": {
+ "en": "Shrink a model to a smaller quantized file, then import it so you can chat with it.",
+ "it": "Riduci un modello in un file quantizzato più piccolo, poi importalo per poterci chattare.",
+ "de": "Verkleinern Sie ein Modell zu einer kleineren quantisierten Datei und importieren Sie sie dann, um mit ihr zu chatten.",
+ "es": "Reduce un modelo a un archivo cuantizado más pequeño y luego impórtalo para poder chatear con él.",
+ "fr": "Réduisez un modèle à un fichier quantifié plus petit, puis importez-le pour pouvoir discuter avec lui.",
+ "pt-BR": "Reduza um modelo a um arquivo quantizado menor e depois importe-o para poder conversar com ele.",
+ "zh-CN": "把模型压缩为更小的量化文件,再导入它,即可与之聊天。",
+ "ko": "모델을 더 작은 양자화 파일로 줄인 다음 가져와서 채팅할 수 있습니다.",
+ "id": "Perkecil model menjadi berkas terkuantisasi yang lebih kecil, lalu impor agar Anda dapat mengobrol dengannya."
+ }
}
}
}
diff --git a/addons/sandbox/manifest.json b/addons/sandbox/manifest.json
index b8a8470842..4f09ab6adf 100644
--- a/addons/sandbox/manifest.json
+++ b/addons/sandbox/manifest.json
@@ -137,7 +137,17 @@
"css": [
"ui/sandbox.css"
],
- "description": "Let your chats run commands and code in an isolated sandbox, with your files mounted.",
+ "description": {
+ "en": "Let your chats run commands and code in an isolated sandbox, with your files mounted.",
+ "it": "Lascia che le tue chat eseguano comandi e codice in una sandbox isolata, con i tuoi file montati.",
+ "de": "Lassen Sie Ihre Chats Befehle und Code in einer isolierten Sandbox ausführen, mit eingebundenen Dateien.",
+ "es": "Permite que tus chats ejecuten comandos y código en un sandbox aislado, con tus archivos montados.",
+ "fr": "Laissez vos conversations exécuter des commandes et du code dans une sandbox isolée, avec vos fichiers montés.",
+ "pt-BR": "Deixe seus chats executarem comandos e código em um sandbox isolado, com seus arquivos montados.",
+ "zh-CN": "让你的聊天在隔离的沙箱中运行命令和代码,并挂载你的文件。",
+ "ko": "채팅이 격리된 샌드박스에서 명령과 코드를 실행할 수 있게 하며, 내 파일이 마운트됩니다.",
+ "id": "Izinkan obrolan Anda menjalankan perintah dan kode di sandbox terisolasi, dengan berkas Anda terpasang."
+ },
"entry": "ui/sandbox.js",
"icon": "ui/icon.png",
"mainPage": "sandbox",
@@ -155,7 +165,17 @@
"slot": "settings.sections"
}
],
- "title": "Sandbox"
+ "title": {
+ "en": "Sandbox",
+ "it": "Sandbox",
+ "de": "Sandbox",
+ "es": "Sandbox",
+ "fr": "Sandbox",
+ "pt-BR": "Sandbox",
+ "zh-CN": "沙箱",
+ "ko": "샌드박스",
+ "id": "Sandbox"
+ }
},
"skillDefinitions": [
{
diff --git a/addons/slack/manifest.json b/addons/slack/manifest.json
index 7effd61047..5bb386a18d 100644
--- a/addons/slack/manifest.json
+++ b/addons/slack/manifest.json
@@ -50,8 +50,38 @@
"pages": ["apps"],
"mainPage": "apps",
"icon": "ui/icon.png",
- "title": "Slack",
- "description": "Bind a Slack app to one of your saved agents, so people can talk to it from Slack."
+ "title": {
+ "en": "Slack",
+ "it": "Slack",
+ "de": "Slack",
+ "es": "Slack",
+ "fr": "Slack",
+ "pt-BR": "Slack",
+ "zh-CN": "Slack",
+ "ko": "Slack",
+ "id": "Slack"
+ },
+ "description": {
+ "en": "Bind a Slack app to one of your saved agents, so people can talk to it from Slack.",
+ "it": "Collega un'app Slack a uno dei tuoi agenti salvati, così le persone possono parlarci da Slack.",
+ "de": "Verbinden Sie eine Slack-App mit einem Ihrer gespeicherten Agenten, damit andere über Slack mit ihm sprechen können.",
+ "es": "Vincula una aplicación de Slack a uno de tus agentes guardados para que la gente pueda hablar con él desde Slack.",
+ "fr": "Associez une application Slack à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Slack.",
+ "pt-BR": "Vincule um app do Slack a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Slack.",
+ "zh-CN": "将 Slack 应用绑定到你保存的某个智能体,让大家可以在 Slack 中与它对话。",
+ "ko": "저장한 에이전트 중 하나에 Slack 앱을 연결하여 사람들이 Slack에서 대화할 수 있게 합니다.",
+ "id": "Hubungkan aplikasi Slack ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Slack."
+ }
}
+ },
+ "i18n": {
+ "it": {"kinds": {"slack": {"title": "Slack", "description": "Parla con uno dei tuoi agenti salvati da Slack, nei messaggi diretti e nei thread dei canali."}}},
+ "de": {"kinds": {"slack": {"title": "Slack", "description": "Sprechen Sie über Slack mit einem Ihrer gespeicherten Agenten, in Direktnachrichten und in Kanal-Threads."}}},
+ "es": {"kinds": {"slack": {"title": "Slack", "description": "Habla con uno de tus agentes guardados desde Slack, en mensajes directos y en hilos de canales."}}},
+ "fr": {"kinds": {"slack": {"title": "Slack", "description": "Parlez à l'un de vos agents enregistrés depuis Slack, en messages privés et dans les fils des canaux."}}},
+ "pt-BR": {"kinds": {"slack": {"title": "Slack", "description": "Converse com um dos seus agentes salvos pelo Slack, em mensagens diretas e em threads de canais."}}},
+ "zh-CN": {"kinds": {"slack": {"title": "Slack", "description": "在 Slack 中与你保存的某个智能体对话,支持私信和频道线程。"}}},
+ "ko": {"kinds": {"slack": {"title": "Slack", "description": "Slack에서 저장한 에이전트 중 하나와 대화합니다. 다이렉트 메시지와 채널 스레드에서 사용할 수 있습니다."}}},
+ "id": {"kinds": {"slack": {"title": "Slack", "description": "Berbicara dengan salah satu agen tersimpan Anda dari Slack, lewat pesan langsung dan di utas kanal."}}}
}
}
diff --git a/addons/studio/manifest.json b/addons/studio/manifest.json
index 030cd09ad6..2929f4cad8 100644
--- a/addons/studio/manifest.json
+++ b/addons/studio/manifest.json
@@ -20,8 +20,28 @@
"mainPage": "studio",
"homeCommands": [{ "id": "open", "alias": "studio", "icon": "palette" }],
"icon": "ui/icon.png",
- "title": "Studio",
- "description": "Make images, video, 3D models, speech, sound and transformed audio with the models you have installed, and keep what you make."
+ "title": {
+ "en": "Studio",
+ "it": "Studio",
+ "de": "Studio",
+ "es": "Studio",
+ "fr": "Studio",
+ "pt-BR": "Studio",
+ "zh-CN": "Studio",
+ "ko": "스튜디오",
+ "id": "Studio"
+ },
+ "description": {
+ "en": "Make images, video, 3D models, speech, sound and transformed audio with the models you have installed, and keep what you make.",
+ "it": "Crea immagini, video, modelli 3D, voce, suoni e audio trasformato con i modelli che hai installato, e conserva ciò che crei.",
+ "de": "Erstellen Sie Bilder, Videos, 3D-Modelle, Sprache, Klänge und umgewandeltes Audio mit den installierten Modellen und behalten Sie, was Sie erstellen.",
+ "es": "Crea imágenes, video, modelos 3D, voz, sonido y audio transformado con los modelos que tienes instalados, y conserva lo que crees.",
+ "fr": "Créez des images, des vidéos, des modèles 3D, de la voix, des sons et de l'audio transformé avec les modèles que vous avez installés, et conservez ce que vous créez.",
+ "pt-BR": "Crie imagens, vídeo, modelos 3D, fala, som e áudio transformado com os modelos que você instalou, e guarde o que criar.",
+ "zh-CN": "用你已安装的模型生成图像、视频、3D 模型、语音、声音和转换后的音频,并保存你的作品。",
+ "ko": "설치한 모델로 이미지, 비디오, 3D 모델, 음성, 소리, 변환된 오디오를 만들고 결과물을 보관합니다.",
+ "id": "Buat gambar, video, model 3D, ucapan, bunyi, dan audio hasil transformasi dengan model yang telah Anda pasang, dan simpan hasil karya Anda."
+ }
}
}
}
diff --git a/addons/tables/manifest.json b/addons/tables/manifest.json
index 7106eb88a7..92fe4e0a2a 100644
--- a/addons/tables/manifest.json
+++ b/addons/tables/manifest.json
@@ -603,16 +603,18 @@
"id": "Tabel",
"ko": "\ud14c\uc774\ube14",
"pt-BR": "Tabelas",
+ "fr": "Tables",
"zh-CN": "\u6570\u636e\u8868"
},
"description": {
"en": "Databases of tables you own or share: typed rows, filters, schema and sharing.",
- "it": "Database di tabelle tuoi o condivisi: righe tipizzate, filtri, schema e condivisione.",
+ "it": "Database di tabelle tue o condivise: righe tipizzate, filtri, schema e condivisione.",
"de": "Datenbanken mit Tabellen, die Ihnen geh\u00f6ren oder geteilt wurden: typisierte Zeilen, Filter, Schema und Freigabe.",
"es": "Bases de datos de tablas propias o compartidas: filas tipadas, filtros, esquema y uso compartido.",
"id": "Basis data tabel milik Anda atau yang dibagikan: baris bertipe, filter, skema, dan berbagi.",
"ko": "\ub0b4\uac00 \uc18c\uc720\ud558\uac70\ub098 \uacf5\uc720\ubc1b\uc740 \ud14c\uc774\ube14 \ub370\uc774\ud130\ubca0\uc774\uc2a4: \ud615\uc2dd\uc774 \uc788\ub294 \ud589, \ud544\ud130, \uc2a4\ud0a4\ub9c8, \uacf5\uc720.",
"pt-BR": "Bancos de dados de tabelas pr\u00f3prios ou compartilhados: linhas tipadas, filtros, esquema e compartilhamento.",
+ "fr": "Bases de donn\u00e9es de tables que vous poss\u00e9dez ou partagez : lignes typ\u00e9es, filtres, sch\u00e9ma et partage.",
"zh-CN": "\u4f60\u62e5\u6709\u6216\u88ab\u5171\u4eab\u7684\u6570\u636e\u8868\u6570\u636e\u5e93\uff1a\u5e26\u7c7b\u578b\u7684\u884c\u3001\u7b5b\u9009\u3001\u7ed3\u6784\u548c\u5171\u4eab\u3002"
},
"chatActions": [
diff --git a/addons/talk/manifest.json b/addons/talk/manifest.json
index dca2226c31..c22660c1b5 100644
--- a/addons/talk/manifest.json
+++ b/addons/talk/manifest.json
@@ -19,8 +19,28 @@
"modelActions": [{"capability": "FLAG_TRANSCRIPT", "page": "talk"}],
"mainPage": "talk",
"icon": "ui/icon.png",
- "title": "Talk",
- "description": "Have a spoken conversation with a model: your voice goes in, the answer is spoken back."
+ "title": {
+ "en": "Talk",
+ "it": "Conversazione vocale",
+ "de": "Sprachchat",
+ "es": "Conversación de voz",
+ "fr": "Vocal",
+ "pt-BR": "Conversa por voz",
+ "zh-CN": "语音对话",
+ "ko": "음성 대화",
+ "id": "Obrolan suara"
+ },
+ "description": {
+ "en": "Have a spoken conversation with a model: your voice goes in, the answer is spoken back.",
+ "it": "Conversa a voce con un modello: la tua voce entra, la risposta viene pronunciata.",
+ "de": "Führen Sie ein gesprochenes Gespräch mit einem Modell: Ihre Stimme geht hinein, die Antwort wird gesprochen.",
+ "es": "Mantén una conversación hablada con un modelo: tu voz entra y la respuesta se pronuncia en voz alta.",
+ "fr": "Dialoguez à voix haute avec un modèle : votre voix entre, la réponse est prononcée.",
+ "pt-BR": "Tenha uma conversa falada com um modelo: sua voz entra e a resposta é falada de volta.",
+ "zh-CN": "与模型进行语音对话:你说话输入,回答以语音读出。",
+ "ko": "모델과 음성으로 대화합니다. 내 목소리가 입력되고 답변은 음성으로 들려줍니다.",
+ "id": "Lakukan percakapan lisan dengan model: suara Anda masuk, jawabannya diucapkan kembali."
+ }
}
}
}
diff --git a/addons/telegram/manifest.json b/addons/telegram/manifest.json
index 1a682aaa8a..aa3c75c509 100644
--- a/addons/telegram/manifest.json
+++ b/addons/telegram/manifest.json
@@ -38,8 +38,38 @@
"pages": ["bots"],
"mainPage": "bots",
"icon": "ui/icon.png",
- "title": "Telegram",
- "description": "Bind a Telegram bot to one of your saved agents, so people can talk to it from Telegram."
+ "title": {
+ "en": "Telegram",
+ "it": "Telegram",
+ "de": "Telegram",
+ "es": "Telegram",
+ "fr": "Telegram",
+ "pt-BR": "Telegram",
+ "zh-CN": "Telegram",
+ "ko": "Telegram",
+ "id": "Telegram"
+ },
+ "description": {
+ "en": "Bind a Telegram bot to one of your saved agents, so people can talk to it from Telegram.",
+ "it": "Collega un bot Telegram a uno dei tuoi agenti salvati, così le persone possono parlarci da Telegram.",
+ "de": "Verbinden Sie einen Telegram-Bot mit einem Ihrer gespeicherten Agenten, damit andere über Telegram mit ihm sprechen können.",
+ "es": "Vincula un bot de Telegram a uno de tus agentes guardados para que la gente pueda hablar con él desde Telegram.",
+ "fr": "Associez un bot Telegram à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Telegram.",
+ "pt-BR": "Vincule um bot do Telegram a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Telegram.",
+ "zh-CN": "将 Telegram 机器人绑定到你保存的某个智能体,让大家可以在 Telegram 中与它对话。",
+ "ko": "저장한 에이전트 중 하나에 Telegram 봇을 연결하여 사람들이 Telegram에서 대화할 수 있게 합니다.",
+ "id": "Hubungkan bot Telegram ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Telegram."
+ }
}
+ },
+ "i18n": {
+ "it": {"kinds": {"telegram": {"title": "Telegram", "description": "Parla con uno dei tuoi agenti salvati da un bot Telegram."}}},
+ "de": {"kinds": {"telegram": {"title": "Telegram", "description": "Sprechen Sie über einen Telegram-Bot mit einem Ihrer gespeicherten Agenten."}}},
+ "es": {"kinds": {"telegram": {"title": "Telegram", "description": "Habla con uno de tus agentes guardados desde un bot de Telegram."}}},
+ "fr": {"kinds": {"telegram": {"title": "Telegram", "description": "Parlez à l'un de vos agents enregistrés depuis un bot Telegram."}}},
+ "pt-BR": {"kinds": {"telegram": {"title": "Telegram", "description": "Converse com um dos seus agentes salvos por um bot do Telegram."}}},
+ "zh-CN": {"kinds": {"telegram": {"title": "Telegram", "description": "通过 Telegram 机器人与你保存的某个智能体对话。"}}},
+ "ko": {"kinds": {"telegram": {"title": "Telegram", "description": "Telegram 봇으로 저장한 에이전트 중 하나와 대화합니다."}}},
+ "id": {"kinds": {"telegram": {"title": "Telegram", "description": "Berbicara dengan salah satu agen tersimpan Anda dari bot Telegram."}}}
}
}
diff --git a/addons/voice/manifest.json b/addons/voice/manifest.json
index c00ea2c037..bfe67159fc 100644
--- a/addons/voice/manifest.json
+++ b/addons/voice/manifest.json
@@ -19,8 +19,28 @@
"modelActions": [{"capability": "FLAG_SPEAKER_RECOGNITION", "page": "recognition"}],
"mainPage": "voice",
"icon": "ui/icon.png",
- "title": "Voice",
- "description": "Recognise who is speaking, keep the voices that text-to-speech can use, and speak text with them."
+ "title": {
+ "en": "Voice",
+ "it": "Voce",
+ "de": "Stimme",
+ "es": "Voz",
+ "fr": "Voix",
+ "pt-BR": "Voz",
+ "zh-CN": "声音",
+ "ko": "음성",
+ "id": "Suara"
+ },
+ "description": {
+ "en": "Recognise who is speaking, keep the voices that text-to-speech can use, and speak text with them.",
+ "it": "Riconosci chi sta parlando, conserva le voci che la sintesi vocale può usare e leggi testi con quelle voci.",
+ "de": "Erkennen Sie, wer spricht, verwalten Sie die Stimmen, die die Sprachausgabe verwenden kann, und sprechen Sie Text mit ihnen.",
+ "es": "Reconoce quién está hablando, guarda las voces que puede usar la síntesis de voz y di textos con ellas.",
+ "fr": "Reconnaissez qui parle, gardez les voix que la synthèse vocale peut utiliser et prononcez du texte avec elles.",
+ "pt-BR": "Reconheça quem está falando, guarde as vozes que a síntese de fala pode usar e fale textos com elas.",
+ "zh-CN": "识别说话的人,管理语音合成可用的声音,并用这些声音朗读文本。",
+ "ko": "말하는 사람을 인식하고, 음성 합성에서 사용할 수 있는 목소리를 보관하며, 그 목소리로 텍스트를 읽습니다.",
+ "id": "Kenali siapa yang berbicara, simpan suara yang dapat digunakan text-to-speech, dan ucapkan teks dengan suara tersebut."
+ }
}
}
}
diff --git a/addons/web/manifest.json b/addons/web/manifest.json
index 702c0c14fc..ec8a1b6d0d 100644
--- a/addons/web/manifest.json
+++ b/addons/web/manifest.json
@@ -202,7 +202,17 @@
"css": [
"ui/web.css"
],
- "description": "Search the web from your chats with Brave Search, Tavily or Google, and read PDF files on the web.",
+ "description": {
+ "en": "Search the web from your chats with Brave Search, Tavily or Google, and read PDF files on the web.",
+ "it": "Cerca sul web dalle tue chat con Brave Search, Tavily o Google, e leggi i file PDF sul web.",
+ "de": "Durchsuchen Sie das Web aus Ihren Chats mit Brave Search, Tavily oder Google und lesen Sie PDF-Dateien im Web.",
+ "es": "Busca en la web desde tus chats con Brave Search, Tavily o Google, y lee archivos PDF de la web.",
+ "fr": "Cherchez sur le web depuis vos conversations avec Brave Search, Tavily ou Google, et lisez des fichiers PDF sur le web.",
+ "pt-BR": "Pesquise na web a partir dos seus chats com Brave Search, Tavily ou Google, e leia arquivos PDF na web.",
+ "zh-CN": "在聊天中用 Brave Search、Tavily 或 Google 搜索网页,并读取网络上的 PDF 文件。",
+ "ko": "채팅에서 Brave Search, Tavily 또는 Google로 웹을 검색하고 웹의 PDF 파일을 읽습니다.",
+ "id": "Cari di web dari obrolan Anda dengan Brave Search, Tavily, atau Google, dan baca berkas PDF di web."
+ },
"entry": "ui/web.js",
"icon": "ui/icon.png",
"mainPage": "web",
@@ -220,7 +230,17 @@
"slot": "settings.sections"
}
],
- "title": "Web"
+ "title": {
+ "en": "Web",
+ "it": "Web",
+ "de": "Web",
+ "es": "Web",
+ "fr": "Web",
+ "pt-BR": "Web",
+ "zh-CN": "网页",
+ "ko": "웹",
+ "id": "Web"
+ }
}
},
"dataFormat": {
diff --git a/core/http/endpoints/addons/apps.go b/core/http/endpoints/addons/apps.go
index 2ae72dca56..26c31f0be3 100644
--- a/core/http/endpoints/addons/apps.go
+++ b/core/http/endpoints/addons/apps.go
@@ -20,9 +20,11 @@ var appID = regexp.MustCompile(`^[a-z][a-z0-9-]{0,63}$`)
// ListApps lists the addons the caller may use as apps.
// @Summary List the addon apps the caller may use
-// @Description Returns the installed addons that are enabled, for every authenticated caller who holds the addons feature. The owner decides what a caller sees; hiding an entry is never the control, because every call the entry leads to is authorized again. Each app has its title, description, icon URL, version and installed generation, a process state (running, starting, failed or stopped; the state is an observation, separate from the enabled flag), the path of its main page when its manifest declares a valid one, and a bounded tool summary. A headless addon is listed without a main page. Administrators additionally see installed addons that are disabled and, per app, the start and stop controls and the management link. An app carries update update-pending, separate from state, only when the owner's cached update check says an update is available for the installed package; the list never evaluates a check and never reaches a library.
+// @Description Returns the installed addons that are enabled, for every authenticated caller who holds the addons feature. The owner decides what a caller sees; hiding an entry is never the control, because every call the entry leads to is authorized again. Each app has its title, description, icon URL, version and installed generation, a process state (running, starting, failed or stopped; the state is an observation, separate from the enabled flag), the path of its main page when its manifest declares a valid one, and a bounded tool summary. A headless addon is listed without a main page. Administrators additionally see installed addons that are disabled and, per app, the start and stop controls and the management link. An app carries update update-pending, separate from state, only when the owner's cached update check says an update is available for the installed package; the list never evaluates a check and never reaches a library. Text that the manifest translates (ui.title and ui.description language maps) is returned in all languages in titles and descriptions. When the request names a language, each app that translates it also carries language, localizedTitle and localizedDescription, resolved per field with English as the fallback; title and description never change.
// @Tags Addons
// @Produce json
+// @Param lang query string false "Language tag to resolve the text for, such as it or pt-BR. Wins over Accept-Language. An unknown language gives the English text."
+// @Param Accept-Language header string false "Used when lang is absent. Quality values are honored; English or no match adds no localized field."
// @Success 200 {object} apps.List "Apps visible to the caller"
// @Failure 401 {object} AddonErrorResponse "Named authentication required"
// @Failure 403 {object} AddonErrorResponse "Feature or owner policy denied the request"
@@ -46,6 +48,10 @@ func ListApps(g *gateway.Gateway, db *gorm.DB, opts ...Option) echo.HandlerFunc
if list.Apps == nil {
list.Apps = []apps.App{}
}
+ explicit, accept := c.QueryParam("lang"), c.Request().Header.Get("Accept-Language")
+ for i := range list.Apps {
+ list.Apps[i] = list.Apps[i].Localize(explicit, accept)
+ }
return c.JSON(200, list)
}
}
diff --git a/core/http/endpoints/addons/apps_test.go b/core/http/endpoints/addons/apps_test.go
index 5e3143ae3a..9b335c0bc6 100644
--- a/core/http/endpoints/addons/apps_test.go
+++ b/core/http/endpoints/addons/apps_test.go
@@ -205,6 +205,35 @@ var _ = Describe("apps routes", Label("apps"), func() {
})
})
+ It("adds the text in the negotiated language beside the English fields and changes none of them", func() {
+ s.each(func(name string, e http.Handler) {
+ ask := func(path string, header string) map[string]any {
+ r := send(e, "GET", path, "", func(req *http.Request) {
+ bearerOf(s.tokens["carol"])(req)
+ if header != "" {
+ req.Header.Set("Accept-Language", header)
+ }
+ })
+ Expect(r.code).To(Equal(200), name)
+ return appsOf(r)["notes"]
+ }
+ plain := ask("/api/addons/apps", "")
+ Expect(plain).NotTo(HaveKey("language"), name+": no language asked, nothing added")
+ Expect(plain).NotTo(HaveKey("localizedTitle"))
+ for _, c := range [][2]string{{"/api/addons/apps?lang=it", ""}, {"/api/addons/apps", "it-IT,it;q=0.9,en;q=0.5"}, {"/api/addons/apps?lang=it", "de"}} {
+ path, header := c[0], c[1]
+ notes := ask(path, header)
+ Expect(notes).To(HaveKeyWithValue("title", "Notes"), name+" "+path+" "+header)
+ Expect(notes).To(HaveKeyWithValue("description", "Keep notes."))
+ Expect(notes).To(HaveKeyWithValue("language", "it"))
+ Expect(notes).To(HaveKeyWithValue("localizedTitle", "Appunti"))
+ Expect(notes).To(HaveKeyWithValue("localizedDescription", "Keep notes."), "the description has no Italian text: English per field")
+ }
+ for path, header := range map[string]string{"/api/addons/apps?lang=de": "", "/api/addons/apps?lang=en": "it", "/api/addons/apps?lang=%3Cx%3E": "it", "/api/addons/apps": "de, fr;q=0.5"} {
+ Expect(ask(path, header)).NotTo(HaveKey("language"), name+" "+path+" "+header)
+ }
+ })
+ })
It("lets only administrators start and stop, through the lifecycle manager, and denies a stale generation", func() {
ctx := context.Background()
desired := func(id string) bool {
@@ -389,7 +418,7 @@ var _ = Describe("apps routes outcomes and documentation", Label("apps"), func()
Expect(json.Unmarshal(b, &m)).To(Succeed())
return m
}
- full := apps.App{ID: "a", Name: "a", Credentialed: true, ContextFiles: []contract.ContextFile{{Scope: "user", Path: "Memory/index.md"}}, UserSettings: []contract.UserSetting{{Key: "save-mode", Values: []string{"ask", "never"}, Default: "ask", Role: "memory.save-mode"}}, Network: &apps.NetworkView{Kind: contract.NetworkKindEgressCheck, Title: "N", Presets: []contract.NetworkPreset{{ID: "off", Title: "Closed"}}}, Title: "t", Titles: map[string]string{"en": "t"}, Description: "d", Descriptions: map[string]string{"en": "d"}, Icon: "i", Version: "1", InstalledAt: "2026-10-30T09:15:00Z", Generation: 1, Enabled: true, State: "running", Update: "update-pending", Operation: &apps.Operation{Kind: "upgrade", Phase: "downloading", Since: "2026-11-04T17:18:00Z"}, MainPage: "/m", HasUI: true, LaunchMode: "always", Tools: []string{"x"}, ToolCount: 1, Shadows: []string{"bash"}, ToolDefinitions: []contract.ToolDefinition{{Name: "x", Description: "d", Kind: "read", InputSchema: json.RawMessage(`{"type":"object"}`), UIOnly: true, Approval: contract.ApprovalAlways, Shadows: "bash", Execution: &contract.ExecutionDescriptor{Class: "commands", Title: "T", Note: "n", Skills: &contract.ExecutionSkills{ProvidesRuntimeFor: []string{"documents"}}}}}, Skills: []contract.SkillDefinition{{Name: "xlsx", Shadows: "xlsx", Slot: &contract.SlotDefinition{Kind: contract.SlotKindDocuments}}}, SkillShadows: []string{"xlsx"}, UI: &apps.UIView{Entry: "/e", CSS: []string{"/c"}, SDK: contract.VersionRange{Min: 1, Max: 1}, Actions: []apps.ChatActionView{{ID: "a", Label: "A", Labels: map[string]string{"en": "A"}, Icon: "star", PackagedIcon: true, Placement: "composer", Tool: "t", Component: "C"}}, Slots: []apps.SlotView{{ID: "s", Slot: "files.preview", Component: "C", Extensions: []string{"docx"}}}, Renderers: []contract.Renderer{{Kind: "approval-card", Tool: "t", Component: "C"}}, Pages: []string{"p"}, Navigation: []string{"p"}, HomeCommands: []apps.HomeCommandView{{ID: "h", Page: "p", Label: "H", Icon: "star"}}, ModelActions: []contract.ModelAction{{Capability: "FLAG_IMAGE", Page: "p"}}, ChatActions: []string{"a"}, Controls: []string{"network-policy"}}, Admin: &apps.AdminView{CanStart: true, CanStop: true, ManagementURL: "/x", HostOperations: &hostops.RequestSummary{Required: []string{"r"}}}}
+ full := apps.App{ID: "a", Name: "a", Credentialed: true, ContextFiles: []contract.ContextFile{{Scope: "user", Path: "Memory/index.md"}}, UserSettings: []contract.UserSetting{{Key: "save-mode", Values: []string{"ask", "never"}, Default: "ask", Role: "memory.save-mode"}}, Network: &apps.NetworkView{Kind: contract.NetworkKindEgressCheck, Title: "N", Presets: []contract.NetworkPreset{{ID: "off", Title: "Closed"}}}, Title: "t", Titles: map[string]string{"en": "t"}, Description: "d", Descriptions: map[string]string{"en": "d"}, Language: "it", LocalizedTitle: "t", LocalizedDescription: "d", Icon: "i", Version: "1", InstalledAt: "2026-10-30T09:15:00Z", Generation: 1, Enabled: true, State: "running", Update: "update-pending", Operation: &apps.Operation{Kind: "upgrade", Phase: "downloading", Since: "2026-11-04T17:18:00Z"}, MainPage: "/m", HasUI: true, LaunchMode: "always", Tools: []string{"x"}, ToolCount: 1, Shadows: []string{"bash"}, ToolDefinitions: []contract.ToolDefinition{{Name: "x", Description: "d", Kind: "read", InputSchema: json.RawMessage(`{"type":"object"}`), UIOnly: true, Approval: contract.ApprovalAlways, Shadows: "bash", Execution: &contract.ExecutionDescriptor{Class: "commands", Title: "T", Note: "n", Skills: &contract.ExecutionSkills{ProvidesRuntimeFor: []string{"documents"}}}}}, Skills: []contract.SkillDefinition{{Name: "xlsx", Shadows: "xlsx", Slot: &contract.SlotDefinition{Kind: contract.SlotKindDocuments}}}, SkillShadows: []string{"xlsx"}, UI: &apps.UIView{Entry: "/e", CSS: []string{"/c"}, SDK: contract.VersionRange{Min: 1, Max: 1}, Actions: []apps.ChatActionView{{ID: "a", Label: "A", Labels: map[string]string{"en": "A"}, Icon: "star", PackagedIcon: true, Placement: "composer", Tool: "t", Component: "C"}}, Slots: []apps.SlotView{{ID: "s", Slot: "files.preview", Component: "C", Extensions: []string{"docx"}}}, Renderers: []contract.Renderer{{Kind: "approval-card", Tool: "t", Component: "C"}}, Pages: []string{"p"}, Navigation: []string{"p"}, HomeCommands: []apps.HomeCommandView{{ID: "h", Page: "p", Label: "H", Icon: "star"}}, ModelActions: []contract.ModelAction{{Capability: "FLAG_IMAGE", Page: "p"}}, ChatActions: []string{"a"}, Controls: []string{"network-policy"}}, Admin: &apps.AdminView{CanStart: true, CanStop: true, ManagementURL: "/x", HostOperations: &hostops.RequestSummary{Required: []string{"r"}}}}
for name, v := range map[string]any{"apps.App": full, "apps.AdminView": *full.Admin, "apps.UIView": *full.UI, "apps.ChatActionView": full.UI.Actions[0], "apps.SlotView": full.UI.Slots[0], "contract.Renderer": full.UI.Renderers[0], "contract.VersionRange": full.UI.SDK, "contract.ToolDefinition": full.ToolDefinitions[0], "contract.ExecutionDescriptor": *full.ToolDefinitions[0].Execution, "contract.ExecutionSkills": *full.ToolDefinitions[0].Execution.Skills, "contract.SkillDefinition": full.Skills[0], "apps.List": apps.List{Apps: []apps.App{full}, Stale: true, StaleReason: "owner_busy", StaleSince: "2026-11-04T17:18:30Z"}, "apps.Operation": *full.Operation} {
def, ok := doc.Definitions[name]
Expect(ok).To(BeTrue(), name)
diff --git a/core/http/endpoints/studio/connectors.go b/core/http/endpoints/studio/connectors.go
index 5e552d0e0b..935ac31f39 100644
--- a/core/http/endpoints/studio/connectors.go
+++ b/core/http/endpoints/studio/connectors.go
@@ -176,8 +176,9 @@ func (h *Handlers) ListConnectors(c echo.Context) error {
// ConnectorKinds lists the kinds of binding that installed add-ons serve.
// @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.
+// @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."
// @Success 200 {object} schema.ChatsConnectorKindList "The kinds"
// @Router /api/chats/connectors/kinds [get]
func (h *Handlers) ConnectorKinds(c echo.Context) error {
@@ -190,9 +191,12 @@ func (h *Handlers) ConnectorKinds(c echo.Context) error {
return failConnector(c, err)
}
out := schema.ChatsConnectorKindList{Kinds: make([]schema.ChatsConnectorKind, 0, len(kinds))}
+ explicit, accept := c.QueryParam("lang"), c.Request().Header.Get("Accept-Language")
for _, k := range kinds {
- out.Kinds = append(out.Kinds, schema.ChatsConnectorKind{Addon: k.AddonID, Kind: k.ID, Title: k.Title, Description: k.Description, SenderHint: k.SenderHint, Page: k.Page, AgentOptional: k.AgentOptional, ThreadHistory: k.ServesThreadHistory(), Options: optionsOut(k.Options),
- Files: filesOut(host.KindFileLimits(c.Request().Context(), k)), Roles: rolesOut(k.ConnectorKind), Permissions: permissionsOut(k.ConnectorKind)})
+ view := schema.ChatsConnectorKind{Addon: k.AddonID, Kind: k.ID, Title: k.Title, Description: k.Description, SenderHint: k.SenderHint, Page: k.Page, AgentOptional: k.AgentOptional, ThreadHistory: k.ServesThreadHistory(), Options: optionsOut(k.Options),
+ Files: filesOut(host.KindFileLimits(c.Request().Context(), k)), Roles: rolesOut(k.ConnectorKind), Permissions: permissionsOut(k.ConnectorKind)}
+ translateKind(&view, k.Translations, explicit, accept)
+ out.Kinds = append(out.Kinds, view)
}
for _, ci := range contract.ConnectorCapabilities() {
out.Capabilities = append(out.Capabilities, schema.ChatsConnectorCapability(ci))
diff --git a/core/http/endpoints/studio/connectors_i18n.go b/core/http/endpoints/studio/connectors_i18n.go
new file mode 100644
index 0000000000..cd49034a6e
--- /dev/null
+++ b/core/http/endpoints/studio/connectors_i18n.go
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: MIT
+package studio
+
+import (
+ "github.com/mudler/LocalAI/core/schema"
+ "github.com/mudler/LocalAI/pkg/addons/contract"
+)
+
+// translateKind adds the manifest's translations of a connector kind to its
+// view. The maps carry every language. When the request names a language that
+// the kind translates (the lang query parameter, else Accept-Language), the
+// resolved text goes in the localized fields, each falling back to English on
+// its own. Title and Description never change.
+func translateKind(out *schema.ChatsConnectorKind, tr map[string]contract.KindText, explicit, acceptLanguage string) {
+ if len(tr) == 0 {
+ return
+ }
+ tags := make([]string, 0, len(tr))
+ for tag, t := range tr {
+ tags = append(tags, tag)
+ if t.Title != "" {
+ if out.Titles == nil {
+ out.Titles = map[string]string{}
+ }
+ out.Titles[tag] = t.Title
+ }
+ if t.Description != "" {
+ if out.Descriptions == nil {
+ out.Descriptions = map[string]string{}
+ }
+ out.Descriptions[tag] = t.Description
+ }
+ }
+ tag := contract.NegotiateLanguage(explicit, acceptLanguage, tags)
+ if tag == "" {
+ return
+ }
+ out.Language = tag
+ out.LocalizedTitle, out.LocalizedDescription = out.Title, out.Description
+ if t := tr[tag]; t.Title != "" {
+ out.LocalizedTitle = t.Title
+ }
+ if t := tr[tag]; t.Description != "" {
+ out.LocalizedDescription = t.Description
+ }
+}
diff --git a/core/http/endpoints/studio/connectors_i18n_test.go b/core/http/endpoints/studio/connectors_i18n_test.go
new file mode 100644
index 0000000000..cb6ae34e9b
--- /dev/null
+++ b/core/http/endpoints/studio/connectors_i18n_test.go
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: MIT
+package studio
+
+import (
+ "github.com/mudler/LocalAI/core/schema"
+ "github.com/mudler/LocalAI/pkg/addons/contract"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("translated connector kinds", func() {
+ kind := func() schema.ChatsConnectorKind {
+ return schema.ChatsConnectorKind{Addon: "telegram", Kind: "telegram", Title: "Telegram", Description: "Talk to an agent."}
+ }
+ tr := map[string]contract.KindText{
+ "it": {Title: "Telegram", Description: "Parla con un agente."},
+ "pt-BR": {Title: "Telegram"},
+ }
+ It("keeps the English fields and adds the languages beside them", func() {
+ k := kind()
+ translateKind(&k, tr, "it", "")
+ Expect(k.Title).To(Equal("Telegram"))
+ Expect(k.Description).To(Equal("Talk to an agent."))
+ Expect(k.Descriptions).To(Equal(map[string]string{"it": "Parla con un agente."}))
+ Expect(k.Titles).To(HaveKeyWithValue("pt-BR", "Telegram"))
+ Expect(k.Language).To(Equal("it"))
+ Expect(k.LocalizedDescription).To(Equal("Parla con un agente."))
+ })
+ It("falls back to English per field", func() {
+ k := kind()
+ translateKind(&k, tr, "", "pt-BR")
+ Expect(k.Language).To(Equal("pt-BR"))
+ Expect(k.LocalizedDescription).To(Equal("Talk to an agent."))
+ })
+ It("adds no localized field for English, an unknown language or no translation", func() {
+ for _, c := range [][2]string{{"en", ""}, {"de", ""}, {"", ""}, {"", "de, en;q=0.5"}} {
+ k := kind()
+ translateKind(&k, tr, c[0], c[1])
+ Expect(k.Language).To(BeEmpty(), "%v", c)
+ Expect(k.LocalizedTitle).To(BeEmpty())
+ }
+ k := kind()
+ translateKind(&k, nil, "it", "it")
+ Expect(k).To(Equal(kind()))
+ })
+})
diff --git a/core/schema/chats.go b/core/schema/chats.go
index 02473a15ef..316e6867bb 100644
--- a/core/schema/chats.go
+++ b/core/schema/chats.go
@@ -1539,7 +1539,18 @@ type ChatsConnectorKind struct {
Kind string `json:"kind"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
- SenderHint string `json:"sender_hint,omitempty"`
+ // Titles and Descriptions carry the translated text of the manifest by
+ // language tag, when it has some. Title and Description stay English.
+ Titles map[string]string `json:"titles,omitempty"`
+ Descriptions map[string]string `json:"descriptions,omitempty"`
+ // Language, LocalizedTitle and LocalizedDescription are present only when
+ // the request names a language (the lang query parameter, else the
+ // Accept-Language header) that the kind translates. Each falls back to
+ // English on its own.
+ Language string `json:"language,omitempty"`
+ LocalizedTitle string `json:"localized_title,omitempty"`
+ LocalizedDescription string `json:"localized_description,omitempty"`
+ SenderHint string `json:"sender_hint,omitempty"`
// Page is the add-on page that adds and edits bindings of this kind.
Page string `json:"page,omitempty"`
// AgentOptional is true when a binding of this kind may have no agent. It
diff --git a/core/services/addons/apps/apps.go b/core/services/addons/apps/apps.go
index 8e1e9a0312..4dde6450ba 100644
--- a/core/services/addons/apps/apps.go
+++ b/core/services/addons/apps/apps.go
@@ -84,6 +84,47 @@ type StartRequest struct {
Generation uint64 `json:"generation" example:"3"`
}
+// Localize returns a copy of the app with the text in the language the
+// request negotiates: the explicit language, else the Accept-Language header,
+// else none. An app whose manifest translates nothing in that language is
+// returned as it is.
+func (a App) Localize(explicit, acceptLanguage string) App {
+ available := map[string]bool{}
+ for tag := range a.Titles {
+ available[tag] = true
+ }
+ for tag := range a.Descriptions {
+ available[tag] = true
+ }
+ tags := make([]string, 0, len(available))
+ for tag := range available {
+ tags = append(tags, tag)
+ }
+ tag := contract.NegotiateLanguage(explicit, acceptLanguage, tags)
+ if tag == "" {
+ return a
+ }
+ pick := func(m map[string]string, fallback string) string {
+ if got, ok := contract.MatchLanguage(tag, languagesOf(m)); ok && m[got] != "" {
+ return m[got]
+ }
+ return fallback
+ }
+ a.Titles, a.Descriptions = maps.Clone(a.Titles), maps.Clone(a.Descriptions)
+ a.Language = tag
+ a.LocalizedTitle = pick(a.Titles, a.Title)
+ a.LocalizedDescription = pick(a.Descriptions, a.Description)
+ return a
+}
+
+func languagesOf(m map[string]string) []string {
+ out := make([]string, 0, len(m))
+ for tag := range m {
+ out = append(out, tag)
+ }
+ return out
+}
+
// MaxTools bounds the tool names of one app.
const MaxTools = 32
@@ -103,6 +144,15 @@ type App struct {
Titles map[string]string `json:"titles,omitempty"`
Description string `json:"description,omitempty" example:"Keep notes."`
Descriptions map[string]string `json:"descriptions,omitempty"`
+ // Language, LocalizedTitle and LocalizedDescription are present only when
+ // the request named a language (the lang query parameter, else the
+ // Accept-Language header) that the manifest translates. Language is the
+ // tag that matched. Each localized field falls back to English on its own,
+ // so a title without a translation is the English title. No other field
+ // changes: Title and Description stay the default-language text.
+ Language string `json:"language,omitempty" example:"it"`
+ LocalizedTitle string `json:"localizedTitle,omitempty" example:"Appunti"`
+ LocalizedDescription string `json:"localizedDescription,omitempty" example:"Tieni degli appunti."`
// Icon is a same-origin URL of the verified immutable icon of this
// generation. It is absent when the manifest declares none.
Icon string `json:"icon,omitempty" example:"/api/addons/apps/notes/icon?generation=3"`
diff --git a/core/services/addons/apps/localize_test.go b/core/services/addons/apps/localize_test.go
new file mode 100644
index 0000000000..03a035bcd7
--- /dev/null
+++ b/core/services/addons/apps/localize_test.go
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: MIT
+package apps
+
+import (
+ "encoding/json"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("localized app text", func() {
+ app := func() App {
+ return App{ID: "tables", Title: "Tables", Titles: map[string]string{"en": "Tables", "it": "Tabelle", "pt-BR": "Tabelas"}, Description: "Databases.", Descriptions: map[string]string{"en": "Databases.", "it": "Database."}}
+ }
+ It("changes no existing field and adds the resolved text beside them", func() {
+ got := app().Localize("it", "")
+ Expect(got.Title).To(Equal("Tables"))
+ Expect(got.Description).To(Equal("Databases."))
+ Expect(got.Titles).To(Equal(app().Titles))
+ Expect(got.Language).To(Equal("it"))
+ Expect(got.LocalizedTitle).To(Equal("Tabelle"))
+ Expect(got.LocalizedDescription).To(Equal("Database."))
+ })
+ It("falls back to English for each field on its own", func() {
+ got := app().Localize("pt-BR", "")
+ Expect(got.Language).To(Equal("pt-BR"))
+ Expect(got.LocalizedTitle).To(Equal("Tabelas"))
+ Expect(got.LocalizedDescription).To(Equal("Databases."), "the description has no pt-BR text")
+ })
+ It("takes the explicit language before the header, and the header before English", func() {
+ Expect(app().Localize("pt", "it").LocalizedTitle).To(Equal("Tabelas"))
+ Expect(app().Localize("", "pt-BR,pt;q=0.9,en;q=0.5").LocalizedTitle).To(Equal("Tabelas"))
+ Expect(app().Localize("", "de, it;q=0.5").LocalizedTitle).To(Equal("Tabelle"))
+ Expect(app().Localize("", "").Language).To(BeEmpty())
+ Expect(app().Localize("xx", "it").Language).To(BeEmpty(), "an unknown explicit language is English")
+ })
+ It("adds nothing for an app without translations, and writes none of the new fields then", func() {
+ plain := App{ID: "x", Title: "X"}
+ got := plain.Localize("it", "it")
+ Expect(got).To(Equal(plain))
+ b, _ := json.Marshal(got)
+ for _, f := range []string{"language", "localizedTitle", "localizedDescription"} {
+ Expect(string(b)).NotTo(ContainSubstring(f))
+ }
+ })
+ It("does not share a map with the original", func() {
+ a := app()
+ got := a.Localize("it", "")
+ got.Titles["it"] = "changed"
+ Expect(a.Titles["it"]).To(Equal("Tabelle"))
+ })
+})
diff --git a/core/services/addons/catalog/catalog.go b/core/services/addons/catalog/catalog.go
index e7298c03a9..6b50de4542 100644
--- a/core/services/addons/catalog/catalog.go
+++ b/core/services/addons/catalog/catalog.go
@@ -4,8 +4,10 @@ package catalog
import (
"context"
"errors"
+ "maps"
"reflect"
"runtime"
+ "slices"
"sort"
"strings"
@@ -76,6 +78,12 @@ type EntryUI struct {
// part of the package manifest and reaches users from the installed addon.
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
+ // Titles and Descriptions carry the other languages of the manifest's
+ // ui.title and ui.description, keyed by BCP 47 tag, so the library can
+ // show the text in the user's language before the add-on is installed.
+ // They never hold English: Title and Description are the English fallback.
+ Titles map[string]string `json:"titles,omitempty"`
+ Descriptions map[string]string `json:"descriptions,omitempty"`
// MainPage is the route the addon opens as an app, if it has one.
MainPage string `json:"mainPage,omitempty"`
// Icon is a path inside the package. The host serves an icon only after
@@ -126,8 +134,8 @@ type Release struct {
OS string `json:"os,omitempty"`
Arch string `json:"arch,omitempty"`
Policy VerificationPolicy `json:"policy,omitzero"`
- // UI is the optional display metadata of the entry. It is comparable on
- // purpose: a release is compared with its trusted resolution at staging.
+ // UI is the optional display metadata of the entry. A release is compared
+ // with its trusted resolution at staging (see Equal).
UI EntryUI `json:"ui,omitzero"`
// Requires is the optional compatibility hint of the entry.
Requires Requirements `json:"requires,omitzero"`
@@ -175,6 +183,12 @@ type Release struct {
Mutable bool `json:"mutable,omitempty"`
}
+// IsZero reports whether no display metadata is set. The translation maps make
+// EntryUI incomparable, so callers use this and not == with the zero value.
+func (u EntryUI) IsZero() bool {
+ return u.Title == "" && u.Description == "" && u.MainPage == "" && u.Icon == "" && u.LaunchMode == "" && len(u.Titles) == 0 && len(u.Descriptions) == 0
+}
+
// Equal reports whether two releases are the same entry field by field. A
// release holds lists and a map since backends were added, so == no longer
// compiles; the add-on staging uses Equal to compare a caller's release with
@@ -271,7 +285,7 @@ func (c Catalog) Search(ctx context.Context, q Query) (Page, error) {
}
continue
}
- if strings.Contains(strings.ToLower(r.Name+" "+r.ID+" "+r.Description), strings.ToLower(q.Text)) {
+ if strings.Contains(strings.ToLower(r.searchText()), strings.ToLower(q.Text)) {
all = append(all, r)
}
}
@@ -283,6 +297,21 @@ func (c Catalog) Search(ctx context.Context, q Query) (Page, error) {
}
return p, nil
}
+
+// searchText is what a text query is matched against: the English name and
+// description, and the translated title and description of every language, so
+// a person finds an add-on by the name they see in their own language.
+func (r Release) searchText() string {
+ var b strings.Builder
+ b.WriteString(r.Name + " " + r.ID + " " + r.Description)
+ for _, tag := range slices.Sorted(maps.Keys(r.UI.Titles)) {
+ b.WriteString(" " + r.UI.Titles[tag])
+ }
+ for _, tag := range slices.Sorted(maps.Keys(r.UI.Descriptions)) {
+ b.WriteString(" " + r.UI.Descriptions[tag])
+ }
+ return b.String()
+}
func releaseKey(r Release) string {
return r.SourceID + "/" + string(r.EntryKind()) + "/" + r.ID + "/" + r.Version + "/" + r.OS + "/" + r.Arch
}
diff --git a/core/services/addons/catalog/catalog_test.go b/core/services/addons/catalog/catalog_test.go
index 7cbfea00c4..0f5a8568c7 100644
--- a/core/services/addons/catalog/catalog_test.go
+++ b/core/services/addons/catalog/catalog_test.go
@@ -328,3 +328,79 @@ var _ = Describe("catalog display metadata", func() {
Expect(e).To(HaveOccurred())
})
})
+
+var _ = Describe("catalog translations", func() {
+ translated := func(mutate func(*Release)) []Release {
+ app := release()
+ app.ID, app.Name = "notes", "Notes"
+ app.UI = EntryUI{
+ Title: "Notes", Description: "Keep notes.", MainPage: "home",
+ Titles: map[string]string{"it": "Appunti", "pt-BR": "Anotações"},
+ Descriptions: map[string]string{"it": "Tieni degli appunti.", "pt-BR": "Guarde anotações."},
+ }
+ if mutate != nil {
+ mutate(&app)
+ }
+ return []Release{app}
+ }
+ refresh := func(releases []Release) (Catalog, error) {
+ server := library(func(w http.ResponseWriter, r *http.Request) { _ = WriteIndex(w, releases) })
+ s := setup()
+ save(s, "main", server.URL)
+ _, e := s.Refresh(ctx, "main")
+ return Catalog{Sources: s, TargetOS: "linux", TargetArch: "amd64"}, e
+ }
+ It("carries the translated title and description of an entry through the library", func() {
+ c, e := refresh(translated(nil))
+ Expect(e).NotTo(HaveOccurred())
+ page, e := c.Search(ctx, Query{})
+ Expect(e).NotTo(HaveOccurred())
+ Expect(page.Releases[0].UI.Titles).To(Equal(map[string]string{"it": "Appunti", "pt-BR": "Anotações"}))
+ Expect(page.Releases[0].UI.Descriptions["it"]).To(Equal("Tieni degli appunti."))
+ r, e := c.Resolve(ctx, "main", "notes", "1.0.0")
+ Expect(e).NotTo(HaveOccurred())
+ Expect(r.UI.Titles).To(Equal(page.Releases[0].UI.Titles))
+ })
+ It("finds an entry by its translated text as well as by the English text", func() {
+ c, e := refresh(translated(nil))
+ Expect(e).NotTo(HaveOccurred())
+ for _, text := range []string{"notes", "appunti", "ANOTAÇÕES", "tieni degli"} {
+ page, e := c.Search(ctx, Query{Text: text})
+ Expect(e).NotTo(HaveOccurred())
+ Expect(page.Releases).To(HaveLen(1), text)
+ }
+ page, e := c.Search(ctx, Query{Text: "tabelle"})
+ Expect(e).NotTo(HaveOccurred())
+ Expect(page.Releases).To(BeEmpty())
+ })
+ It("keeps a plain entry free of translation fields on the wire", func() {
+ c, e := refresh(translated(func(r *Release) { r.UI.Titles, r.UI.Descriptions = nil, nil }))
+ Expect(e).NotTo(HaveOccurred())
+ page, _ := c.Search(ctx, Query{})
+ b, _ := json.Marshal(page.Releases[0])
+ Expect(string(b)).NotTo(ContainSubstring("titles"))
+ Expect(string(b)).NotTo(ContainSubstring("descriptions"))
+ })
+ It("rejects translations that the manifest would refuse", func() {
+ many := map[string]string{}
+ for i := 0; i < contract.MaxLanguages+1; i++ {
+ many[string(rune('a'+i%26))+string(rune('b'+i/26))+"-US"] = "x"
+ }
+ for name, mutate := range map[string]func(*Release){
+ "English entry": func(r *Release) { r.UI.Titles["en"] = "Notes" },
+ "bad tag": func(r *Release) { r.UI.Titles["not a tag"] = "x" },
+ "underscore tag": func(r *Release) { r.UI.Titles["pt_BR"] = "x" },
+ "tag case clash": func(r *Release) { r.UI.Titles["PT-br"] = "x" },
+ "markup": func(r *Release) { r.UI.Titles["it"] = "Appunti" },
+ "control": func(r *Release) { r.UI.Descriptions["it"] = "a\nb" },
+ "empty": func(r *Release) { r.UI.Titles["it"] = "" },
+ "long title": func(r *Release) { r.UI.Titles["it"] = strings.Repeat("t", 81) },
+ "long description": func(r *Release) { r.UI.Descriptions["it"] = strings.Repeat("d", 281) },
+ "too many": func(r *Release) { r.UI.Titles = many },
+ "bidi override": func(r *Release) { r.UI.Titles["it"] = "A\u202eB" },
+ } {
+ _, e := refresh(translated(mutate))
+ Expect(e).To(MatchError(ErrRefresh), name)
+ }
+ })
+})
diff --git a/core/services/addons/catalog/sources.go b/core/services/addons/catalog/sources.go
index fb314a5852..e31d0cd0ac 100644
--- a/core/services/addons/catalog/sources.go
+++ b/core/services/addons/catalog/sources.go
@@ -604,7 +604,7 @@ func validKindFields(r Release) bool {
if r.IsMeta() {
// A meta backend has no image, so it has no UI path or requirements
// either; its alias follows the same rule as any backend.
- return r.UI == (EntryUI{}) && r.Requires == (Requirements{}) && r.ToolCount == 0 && len(r.Tools) == 0 && (r.Alias == "" || (backendNamePattern.MatchString(r.Alias) && !strings.Contains(r.Alias, "..")))
+ return r.UI.IsZero() && r.Requires == (Requirements{}) && r.ToolCount == 0 && len(r.Tools) == 0 && (r.Alias == "" || (backendNamePattern.MatchString(r.Alias) && !strings.Contains(r.Alias, "..")))
}
if !strings.HasPrefix(r.Artifact, "oci://") || r.UI.MainPage != "" || r.UI.Icon != "" || r.UI.LaunchMode != "" || r.Requires != (Requirements{}) || r.ToolCount != 0 || len(r.Tools) > 0 {
return false
@@ -620,7 +620,26 @@ func validEntryUI(u EntryUI) bool {
(u.Description == "" || contract.ValidPlainText(u.Description, contract.MaxDescriptionChars)) &&
(u.MainPage == "" || contract.ValidName(u.MainPage)) &&
(u.Icon == "" || contract.ValidIconPath(u.Icon)) &&
- (u.LaunchMode == "" || u.LaunchMode == contract.LaunchModeAlways)
+ (u.LaunchMode == "" || u.LaunchMode == contract.LaunchModeAlways) &&
+ validTranslations(u.Titles, contract.MaxTitleChars) && validTranslations(u.Descriptions, contract.MaxDescriptionChars)
+}
+
+// validTranslations applies the rules of a manifest language map to the
+// translated text of an entry. English is the entry's own text, never a
+// translation, and a tag may appear once whatever its case.
+func validTranslations(m map[string]string, max int) bool {
+ if len(m) > contract.MaxLanguages {
+ return false
+ }
+ seen := map[string]bool{}
+ for tag, text := range m {
+ key := strings.ToLower(tag)
+ if !contract.ValidLanguageTag(tag) || key == "en" || strings.HasPrefix(key, "en-") || seen[key] || !contract.ValidPlainText(text, max) {
+ return false
+ }
+ seen[key] = true
+ }
+ return true
}
// validRequirements accepts a zero range (no claim) or a well formed one.
diff --git a/core/services/addons/gateway/connectors.go b/core/services/addons/gateway/connectors.go
index df4cb6172c..bf1e603f8d 100644
--- a/core/services/addons/gateway/connectors.go
+++ b/core/services/addons/gateway/connectors.go
@@ -70,7 +70,7 @@ func (g *Gateway) localConnectorKinds(ctx context.Context) ([]connectors.Kind, e
}
out := make([]connectors.Kind, len(kinds))
for i, k := range kinds {
- out[i] = connectors.Kind{AddonID: k.AddonID, ConnectorKind: k.ConnectorKind}
+ out[i] = connectors.Kind{AddonID: k.AddonID, ConnectorKind: k.ConnectorKind, Translations: k.Translations}
}
return out, nil
}
diff --git a/core/services/addons/manager.go b/core/services/addons/manager.go
index 4666294f4f..e510a5c6f3 100644
--- a/core/services/addons/manager.go
+++ b/core/services/addons/manager.go
@@ -1265,6 +1265,10 @@ func (m *Manager) Runtime(ctx context.Context, id string) (addonruntime.Instance
type ConnectorKind struct {
AddonID string
contract.ConnectorKind
+ // Translations is the manifest's i18n text of this kind, by language tag.
+ // It is display text only and is never part of the kind that the add-on
+ // registers.
+ Translations map[string]contract.KindText `json:"translations,omitempty"`
}
// ConnectorKinds lists the connector kinds of the add-ons that run now. An
@@ -1288,8 +1292,14 @@ func (m *Manager) ConnectorKinds(ctx context.Context) ([]ConnectorKind, error) {
if r.Capabilities.Connector == nil {
continue
}
+ var manifest contract.Manifest
+ if st, ok := m.journal.Addons[r.AddonID]; ok {
+ if receipt, ok := currentReceipt(st); ok {
+ manifest = receipt.Manifest
+ }
+ }
for _, k := range r.Capabilities.Connector.Kinds {
- out = append(out, ConnectorKind{AddonID: r.AddonID, ConnectorKind: k})
+ out = append(out, ConnectorKind{AddonID: r.AddonID, ConnectorKind: k, Translations: manifest.KindTranslations(k.ID)})
}
}
sort.Slice(out, func(i, j int) bool { return out[i].AddonID+"/"+out[i].ID < out[j].AddonID+"/"+out[j].ID })
diff --git a/core/services/studio/connectors/scope.go b/core/services/studio/connectors/scope.go
index a804b5ef35..ba6a396624 100644
--- a/core/services/studio/connectors/scope.go
+++ b/core/services/studio/connectors/scope.go
@@ -33,4 +33,7 @@ const (
type Kind struct {
AddonID string
contract.ConnectorKind
+ // Translations is the display text of the kind in other languages, from
+ // the manifest's i18n field, by language tag.
+ Translations map[string]contract.KindText `json:"translations,omitempty"`
}
diff --git a/core/services/studio/providerguard/guard_test.go b/core/services/studio/providerguard/guard_test.go
index 946cfc65df..a6e3a2cefc 100644
--- a/core/services/studio/providerguard/guard_test.go
+++ b/core/services/studio/providerguard/guard_test.go
@@ -113,7 +113,6 @@ var allowed = []entry{
// Ordinary words that equal an id or a title.
{"core/schema/chats.go", `example:"Sandbox"`, "a sample value in the API documentation"},
{locales + "de/pages.json", "Sie öffnen sich hier in einer Sandbox", "German for the isolated frame of a page: the ordinary noun"},
- {locales + "de/workspace.json", "types.sheet = Tabelle", "German for a sheet: the translated title and the noun coincide"},
{"core/application/studio_service.go", `memory`, "the in-memory kind of the store"},
{"core/application/studio_store.go", `memory`, "the in-memory kind of the store"},
{"core/services/studio/nibharness/tools.go", `memory`, "the built-in memory tool of the agent engine"},
diff --git a/core/services/studio/providerguard/names.go b/core/services/studio/providerguard/names.go
index f47d1e3a7f..ca89c1b675 100644
--- a/core/services/studio/providerguard/names.go
+++ b/core/services/studio/providerguard/names.go
@@ -105,9 +105,12 @@ func Addons() ([]Addon, error) {
if json.Unmarshal(m.Capabilities.UI.Title, &title) == nil {
add(title)
} else if json.Unmarshal(m.Capabilities.UI.Title, &titles) == nil {
- for _, v := range titles {
- add(v)
- }
+ // The English title is the name of the add-on. Its translations
+ // are the same name in the language of the reader and are often an
+ // ordinary word there (the Korean for web, the Chinese for
+ // sandbox), so they are not names to keep out of core. The
+ // translated strings live in the add-on packages.
+ add(titles["en"])
}
sort.Strings(a.Titles)
out = append(out, a)
diff --git a/docs/content/features/addons.md b/docs/content/features/addons.md
index 492ea2d93c..7f5d1373bd 100644
--- a/docs/content/features/addons.md
+++ b/docs/content/features/addons.md
@@ -1598,6 +1598,67 @@ needs none of them.
| `ui.icon` | A packaged image: `.png`, `.webp`, `.jpg` or `.jpeg`, a normalized package-relative path of at most 256 bytes, at most 256 KiB. A remote URL, a `data:` URL, an absolute path and a traversal are rejected. It cannot also be the module or a stylesheet. |
| `ui.title`, `ui.description` | Plain text: one string, or a map of BCP 47 language tag to string (at most 16 tags). At most 80 and 280 characters. Markup characters (`<`, `>`), control characters and direction overrides are rejected. |
+### Translated text
+
+The English text of a manifest is the source and the fallback. An add-on can
+carry the text that LocalAI shows to people in other languages, so the library
+list and the add-on cards read in the language of the user. Two fields do this.
+
+**The title and the description of the add-on** use the language maps of
+`ui.title` and `ui.description` (see the table above). Write the English text
+under `en`, then one entry for each language:
+
+```json
+"title": { "en": "Tables", "it": "Tabelle", "de": "Tabellen", "pt-BR": "Tabelas" },
+"description": { "en": "Databases of tables.", "it": "Database di tabelle." }
+```
+
+**The kinds of a connector add-on** use the optional top-level field `i18n`.
+It is an optional addition to `api` 2. Its shape is closed on purpose:
+
+```json
+"i18n": {
+ "it": { "kinds": { "telegram": { "title": "Telegram", "description": "Parla con uno dei tuoi agenti salvati da un bot Telegram." } } },
+ "pt-BR": { "kinds": { "telegram": { "title": "Telegram", "description": "Converse com um dos seus agentes salvos por um bot do Telegram." } } }
+}
+```
+
+| Rule | Value |
+|---|---|
+| Language tags | A BCP 47 subset: two or three letters and up to three subtags, such as `it`, `pt-BR`, `zh-CN`. At most 16 languages. A tag is unique whatever its case. `en` is refused: the English fields are the manifest. |
+| Fields | `kinds..title` and `kinds..description`. The kind must be declared in `capabilities.connector.kinds`, and a description can be translated only when the English kind has one. An unknown field fails the strict parser, as does a duplicate key. |
+| Text | Plain text, as for `ui.title`: no `<` or `>`, no control characters, no direction overrides, no leading or trailing space. A title has at most 80 characters and a description at most 280. All the translated text of one manifest has at most 12 000 characters. The whole manifest keeps its size limit. |
+| Fallback | A field that a language leaves out, and every language that the manifest does not list, shows the English text. Each field falls back on its own. |
+| Not translatable | Tool descriptions, input schemas and skills are written for the model and stay English. User setting keys and values are identifiers. The text of the add-on's own pages is part of the UI bundle and has its own strings. |
+
+A host that does not know `i18n` refuses a manifest that has it, as it refuses
+any unknown field, so a package with `i18n` needs a host that reads it. The
+language maps of `ui.title` and `ui.description` are older and every host reads them.
+The field is part of the manifest, so the package digest and its signature cover
+it: a changed translation is a new package.
+
+**What the host does with it.** The library index carries the translated title and
+description of each entry (`ui.titles` and `ui.descriptions`, by language), so the library
+lists them before the add-on is installed. The generator of the index
+(`scripts/addons/catalog.sh`) copies them from the manifest, so they are never
+edited in the index. A search matches the English and the translated text.
+
+`GET /api/addons/apps` and `GET /api/chats/connectors/kinds` return the text of every
+language in `titles` and `descriptions`. When the request names a language, each item that
+translates it also has `language` and the resolved `localizedTitle` and
+`localizedDescription` (`localized_title` and `localized_description` for connector kinds). The
+language is the `lang` query parameter; without it, the `Accept-Language` header (quality values
+are honored); without both, English and no extra field. An unknown or malformed `lang` is
+English and the header is not read behind it. `title` and `description` never change, so an
+existing client sees the same answer as before. The UI reads the maps and picks the language of
+the user itself.
+
+**Authoring.** Translate by hand, in the tone of the main locale of the language. Keep product
+names (GitHub, Slack, Telegram, Discord, Jellyfin, Home Assistant, LocalAI) as they are. Keep the
+meaning of the English text; do not add claims to it. The packaging tests require every first-party
+add-on to have the title and the description in `en`, `it`, `de`, `es`, `fr`, `id`, `ko`, `pt-BR`
+and `zh-CN`, and every connector kind to have the same fields as its English kind in each of them.
+
### Skills
An add-on can ship skills, the procedures a chat model reads before a task
diff --git a/gallery/addons/index.yaml b/gallery/addons/index.yaml
index 6cb898ee09..7ce7b264e9 100644
--- a/gallery/addons/index.yaml
+++ b/gallery/addons/index.yaml
@@ -28,6 +28,24 @@
description: "Bind a Discord bot to one of your saved agents, so people can talk to it from Discord."
mainPage: "bots"
icon: "ui/icon.png"
+ titles:
+ "de": "Discord"
+ "es": "Discord"
+ "fr": "Discord"
+ "id": "Discord"
+ "it": "Discord"
+ "ko": "Discord"
+ "pt-BR": "Discord"
+ "zh-CN": "Discord"
+ descriptions:
+ "de": "Verbinden Sie einen Discord-Bot mit einem Ihrer gespeicherten Agenten, damit andere über Discord mit ihm sprechen können."
+ "es": "Vincula un bot de Discord a uno de tus agentes guardados para que la gente pueda hablar con él desde Discord."
+ "fr": "Associez un bot Discord à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Discord."
+ "id": "Hubungkan bot Discord ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Discord."
+ "it": "Collega un bot Discord a uno dei tuoi agenti salvati, così le persone possono parlarci da Discord."
+ "ko": "저장한 에이전트 중 하나에 Discord 봇을 연결하여 사람들이 Discord에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um bot do Discord a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Discord."
+ "zh-CN": "将 Discord 机器人绑定到你保存的某个智能体,让大家可以在 Discord 中与它对话。"
- id: "discord"
name: Discord
description: "Bind a Discord bot to one of your saved agents, so people can talk to it from Discord."
@@ -46,6 +64,24 @@
description: "Bind a Discord bot to one of your saved agents, so people can talk to it from Discord."
mainPage: "bots"
icon: "ui/icon.png"
+ titles:
+ "de": "Discord"
+ "es": "Discord"
+ "fr": "Discord"
+ "id": "Discord"
+ "it": "Discord"
+ "ko": "Discord"
+ "pt-BR": "Discord"
+ "zh-CN": "Discord"
+ descriptions:
+ "de": "Verbinden Sie einen Discord-Bot mit einem Ihrer gespeicherten Agenten, damit andere über Discord mit ihm sprechen können."
+ "es": "Vincula un bot de Discord a uno de tus agentes guardados para que la gente pueda hablar con él desde Discord."
+ "fr": "Associez un bot Discord à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Discord."
+ "id": "Hubungkan bot Discord ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Discord."
+ "it": "Collega un bot Discord a uno dei tuoi agenti salvati, così le persone possono parlarci da Discord."
+ "ko": "저장한 에이전트 중 하나에 Discord 봇을 연결하여 사람들이 Discord에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um bot do Discord a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Discord."
+ "zh-CN": "将 Discord 机器人绑定到你保存的某个智能体,让大家可以在 Discord 中与它对话。"
- id: "discord"
name: Discord
description: "Bind a Discord bot to one of your saved agents, so people can talk to it from Discord."
@@ -64,6 +100,24 @@
description: "Bind a Discord bot to one of your saved agents, so people can talk to it from Discord."
mainPage: "bots"
icon: "ui/icon.png"
+ titles:
+ "de": "Discord"
+ "es": "Discord"
+ "fr": "Discord"
+ "id": "Discord"
+ "it": "Discord"
+ "ko": "Discord"
+ "pt-BR": "Discord"
+ "zh-CN": "Discord"
+ descriptions:
+ "de": "Verbinden Sie einen Discord-Bot mit einem Ihrer gespeicherten Agenten, damit andere über Discord mit ihm sprechen können."
+ "es": "Vincula un bot de Discord a uno de tus agentes guardados para que la gente pueda hablar con él desde Discord."
+ "fr": "Associez un bot Discord à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Discord."
+ "id": "Hubungkan bot Discord ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Discord."
+ "it": "Collega un bot Discord a uno dei tuoi agenti salvati, così le persone possono parlarci da Discord."
+ "ko": "저장한 에이전트 중 하나에 Discord 봇을 연결하여 사람들이 Discord에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um bot do Discord a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Discord."
+ "zh-CN": "将 Discord 机器人绑定到你保存的某个智能体,让大家可以在 Discord 中与它对话。"
- id: "discord"
name: Discord
description: "Bind a Discord bot to one of your saved agents, so people can talk to it from Discord."
@@ -82,6 +136,24 @@
description: "Bind a Discord bot to one of your saved agents, so people can talk to it from Discord."
mainPage: "bots"
icon: "ui/icon.png"
+ titles:
+ "de": "Discord"
+ "es": "Discord"
+ "fr": "Discord"
+ "id": "Discord"
+ "it": "Discord"
+ "ko": "Discord"
+ "pt-BR": "Discord"
+ "zh-CN": "Discord"
+ descriptions:
+ "de": "Verbinden Sie einen Discord-Bot mit einem Ihrer gespeicherten Agenten, damit andere über Discord mit ihm sprechen können."
+ "es": "Vincula un bot de Discord a uno de tus agentes guardados para que la gente pueda hablar con él desde Discord."
+ "fr": "Associez un bot Discord à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Discord."
+ "id": "Hubungkan bot Discord ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Discord."
+ "it": "Collega un bot Discord a uno dei tuoi agenti salvati, così le persone possono parlarci da Discord."
+ "ko": "저장한 에이전트 중 하나에 Discord 봇을 연결하여 사람들이 Discord에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um bot do Discord a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Discord."
+ "zh-CN": "将 Discord 机器人绑定到你保存的某个智能体,让大家可以在 Discord 中与它对话。"
- id: "face"
name: Face
description: "Find out whose face is in a photo, or whether two photos show the same person."
@@ -100,6 +172,24 @@
description: "Find out whose face is in a photo, or whether two photos show the same person."
mainPage: "recognition"
icon: "ui/icon.png"
+ titles:
+ "de": "Gesicht"
+ "es": "Rostro"
+ "fr": "Visage"
+ "id": "Wajah"
+ "it": "Volto"
+ "ko": "얼굴"
+ "pt-BR": "Rosto"
+ "zh-CN": "人脸"
+ descriptions:
+ "de": "Finden Sie heraus, wessen Gesicht auf einem Foto zu sehen ist oder ob zwei Fotos dieselbe Person zeigen."
+ "es": "Descubre de quién es el rostro de una foto, o si dos fotos muestran a la misma persona."
+ "fr": "Découvrez à qui appartient le visage sur une photo, ou si deux photos montrent la même personne."
+ "id": "Cari tahu wajah siapa yang ada di sebuah foto, atau apakah dua foto menunjukkan orang yang sama."
+ "it": "Scopri di chi è il volto in una foto, o se due foto mostrano la stessa persona."
+ "ko": "사진 속 얼굴이 누구인지, 또는 두 사진이 같은 사람인지 확인합니다."
+ "pt-BR": "Descubra de quem é o rosto em uma foto, ou se duas fotos mostram a mesma pessoa."
+ "zh-CN": "找出照片中的人是谁,或判断两张照片是否是同一个人。"
- id: "face"
name: Face
description: "Find out whose face is in a photo, or whether two photos show the same person."
@@ -118,6 +208,24 @@
description: "Find out whose face is in a photo, or whether two photos show the same person."
mainPage: "recognition"
icon: "ui/icon.png"
+ titles:
+ "de": "Gesicht"
+ "es": "Rostro"
+ "fr": "Visage"
+ "id": "Wajah"
+ "it": "Volto"
+ "ko": "얼굴"
+ "pt-BR": "Rosto"
+ "zh-CN": "人脸"
+ descriptions:
+ "de": "Finden Sie heraus, wessen Gesicht auf einem Foto zu sehen ist oder ob zwei Fotos dieselbe Person zeigen."
+ "es": "Descubre de quién es el rostro de una foto, o si dos fotos muestran a la misma persona."
+ "fr": "Découvrez à qui appartient le visage sur une photo, ou si deux photos montrent la même personne."
+ "id": "Cari tahu wajah siapa yang ada di sebuah foto, atau apakah dua foto menunjukkan orang yang sama."
+ "it": "Scopri di chi è il volto in una foto, o se due foto mostrano la stessa persona."
+ "ko": "사진 속 얼굴이 누구인지, 또는 두 사진이 같은 사람인지 확인합니다."
+ "pt-BR": "Descubra de quem é o rosto em uma foto, ou se duas fotos mostram a mesma pessoa."
+ "zh-CN": "找出照片中的人是谁,或判断两张照片是否是同一个人。"
- id: "face"
name: Face
description: "Find out whose face is in a photo, or whether two photos show the same person."
@@ -136,6 +244,24 @@
description: "Find out whose face is in a photo, or whether two photos show the same person."
mainPage: "recognition"
icon: "ui/icon.png"
+ titles:
+ "de": "Gesicht"
+ "es": "Rostro"
+ "fr": "Visage"
+ "id": "Wajah"
+ "it": "Volto"
+ "ko": "얼굴"
+ "pt-BR": "Rosto"
+ "zh-CN": "人脸"
+ descriptions:
+ "de": "Finden Sie heraus, wessen Gesicht auf einem Foto zu sehen ist oder ob zwei Fotos dieselbe Person zeigen."
+ "es": "Descubre de quién es el rostro de una foto, o si dos fotos muestran a la misma persona."
+ "fr": "Découvrez à qui appartient le visage sur une photo, ou si deux photos montrent la même personne."
+ "id": "Cari tahu wajah siapa yang ada di sebuah foto, atau apakah dua foto menunjukkan orang yang sama."
+ "it": "Scopri di chi è il volto in una foto, o se due foto mostrano la stessa persona."
+ "ko": "사진 속 얼굴이 누구인지, 또는 두 사진이 같은 사람인지 확인합니다."
+ "pt-BR": "Descubra de quem é o rosto em uma foto, ou se duas fotos mostram a mesma pessoa."
+ "zh-CN": "找出照片中的人是谁,或判断两张照片是否是同一个人。"
- id: "face"
name: Face
description: "Find out whose face is in a photo, or whether two photos show the same person."
@@ -154,6 +280,24 @@
description: "Find out whose face is in a photo, or whether two photos show the same person."
mainPage: "recognition"
icon: "ui/icon.png"
+ titles:
+ "de": "Gesicht"
+ "es": "Rostro"
+ "fr": "Visage"
+ "id": "Wajah"
+ "it": "Volto"
+ "ko": "얼굴"
+ "pt-BR": "Rosto"
+ "zh-CN": "人脸"
+ descriptions:
+ "de": "Finden Sie heraus, wessen Gesicht auf einem Foto zu sehen ist oder ob zwei Fotos dieselbe Person zeigen."
+ "es": "Descubre de quién es el rostro de una foto, o si dos fotos muestran a la misma persona."
+ "fr": "Découvrez à qui appartient le visage sur une photo, ou si deux photos montrent la même personne."
+ "id": "Cari tahu wajah siapa yang ada di sebuah foto, atau apakah dua foto menunjukkan orang yang sama."
+ "it": "Scopri di chi è il volto in una foto, o se due foto mostrano la stessa persona."
+ "ko": "사진 속 얼굴이 누구인지, 또는 두 사진이 같은 사람인지 확인합니다."
+ "pt-BR": "Descubra de quem é o rosto em uma foto, ou se duas fotos mostram a mesma pessoa."
+ "zh-CN": "找出照片中的人是谁,或判断两张照片是否是同一个人。"
- id: "fine-tuning"
name: Fine-tuning
description: "Train an adapter on your own data, then export it as a model you can chat with."
@@ -172,6 +316,24 @@
description: "Train an adapter on your own data, then export it as a model you can chat with."
mainPage: "jobs"
icon: "ui/icon.png"
+ titles:
+ "de": "Fine-Tuning"
+ "es": "Ajuste fino"
+ "fr": "Fine-tuning"
+ "id": "Fine-tuning"
+ "it": "Fine-tuning"
+ "ko": "파인튜닝"
+ "pt-BR": "Ajuste fino"
+ "zh-CN": "微调"
+ descriptions:
+ "de": "Trainieren Sie einen Adapter mit Ihren eigenen Daten und exportieren Sie ihn dann als Modell, mit dem Sie chatten können."
+ "es": "Entrena un adaptador con tus propios datos y expórtalo como un modelo con el que puedes chatear."
+ "fr": "Entraînez un adaptateur sur vos propres données, puis exportez-le sous forme de modèle avec lequel vous pouvez discuter."
+ "id": "Latih adapter dengan data Anda sendiri, lalu ekspor sebagai model yang dapat diajak mengobrol."
+ "it": "Addestra un adapter sui tuoi dati, poi esportalo come modello con cui puoi chattare."
+ "ko": "내 데이터로 어댑터를 학습한 다음, 채팅할 수 있는 모델로 내보냅니다."
+ "pt-BR": "Treine um adaptador com seus próprios dados e depois exporte-o como um modelo com o qual você pode conversar."
+ "zh-CN": "用你自己的数据训练适配器,然后将其导出为可以聊天的模型。"
toolCount: 8
tools:
- "resource-exists"
@@ -200,6 +362,24 @@
description: "Train an adapter on your own data, then export it as a model you can chat with."
mainPage: "jobs"
icon: "ui/icon.png"
+ titles:
+ "de": "Fine-Tuning"
+ "es": "Ajuste fino"
+ "fr": "Fine-tuning"
+ "id": "Fine-tuning"
+ "it": "Fine-tuning"
+ "ko": "파인튜닝"
+ "pt-BR": "Ajuste fino"
+ "zh-CN": "微调"
+ descriptions:
+ "de": "Trainieren Sie einen Adapter mit Ihren eigenen Daten und exportieren Sie ihn dann als Modell, mit dem Sie chatten können."
+ "es": "Entrena un adaptador con tus propios datos y expórtalo como un modelo con el que puedes chatear."
+ "fr": "Entraînez un adaptateur sur vos propres données, puis exportez-le sous forme de modèle avec lequel vous pouvez discuter."
+ "id": "Latih adapter dengan data Anda sendiri, lalu ekspor sebagai model yang dapat diajak mengobrol."
+ "it": "Addestra un adapter sui tuoi dati, poi esportalo come modello con cui puoi chattare."
+ "ko": "내 데이터로 어댑터를 학습한 다음, 채팅할 수 있는 모델로 내보냅니다."
+ "pt-BR": "Treine um adaptador com seus próprios dados e depois exporte-o como um modelo com o qual você pode conversar."
+ "zh-CN": "用你自己的数据训练适配器,然后将其导出为可以聊天的模型。"
toolCount: 8
tools:
- "resource-exists"
@@ -228,6 +408,24 @@
description: "Train an adapter on your own data, then export it as a model you can chat with."
mainPage: "jobs"
icon: "ui/icon.png"
+ titles:
+ "de": "Fine-Tuning"
+ "es": "Ajuste fino"
+ "fr": "Fine-tuning"
+ "id": "Fine-tuning"
+ "it": "Fine-tuning"
+ "ko": "파인튜닝"
+ "pt-BR": "Ajuste fino"
+ "zh-CN": "微调"
+ descriptions:
+ "de": "Trainieren Sie einen Adapter mit Ihren eigenen Daten und exportieren Sie ihn dann als Modell, mit dem Sie chatten können."
+ "es": "Entrena un adaptador con tus propios datos y expórtalo como un modelo con el que puedes chatear."
+ "fr": "Entraînez un adaptateur sur vos propres données, puis exportez-le sous forme de modèle avec lequel vous pouvez discuter."
+ "id": "Latih adapter dengan data Anda sendiri, lalu ekspor sebagai model yang dapat diajak mengobrol."
+ "it": "Addestra un adapter sui tuoi dati, poi esportalo come modello con cui puoi chattare."
+ "ko": "내 데이터로 어댑터를 학습한 다음, 채팅할 수 있는 모델로 내보냅니다."
+ "pt-BR": "Treine um adaptador com seus próprios dados e depois exporte-o como um modelo com o qual você pode conversar."
+ "zh-CN": "用你自己的数据训练适配器,然后将其导出为可以聊天的模型。"
toolCount: 8
tools:
- "resource-exists"
@@ -256,6 +454,24 @@
description: "Train an adapter on your own data, then export it as a model you can chat with."
mainPage: "jobs"
icon: "ui/icon.png"
+ titles:
+ "de": "Fine-Tuning"
+ "es": "Ajuste fino"
+ "fr": "Fine-tuning"
+ "id": "Fine-tuning"
+ "it": "Fine-tuning"
+ "ko": "파인튜닝"
+ "pt-BR": "Ajuste fino"
+ "zh-CN": "微调"
+ descriptions:
+ "de": "Trainieren Sie einen Adapter mit Ihren eigenen Daten und exportieren Sie ihn dann als Modell, mit dem Sie chatten können."
+ "es": "Entrena un adaptador con tus propios datos y expórtalo como un modelo con el que puedes chatear."
+ "fr": "Entraînez un adaptateur sur vos propres données, puis exportez-le sous forme de modèle avec lequel vous pouvez discuter."
+ "id": "Latih adapter dengan data Anda sendiri, lalu ekspor sebagai model yang dapat diajak mengobrol."
+ "it": "Addestra un adapter sui tuoi dati, poi esportalo come modello con cui puoi chattare."
+ "ko": "내 데이터로 어댑터를 학습한 다음, 채팅할 수 있는 모델로 내보냅니다."
+ "pt-BR": "Treine um adaptador com seus próprios dados e depois exporte-o como um modelo com o qual você pode conversar."
+ "zh-CN": "用你自己的数据训练适配器,然后将其导出为可以聊天的模型。"
toolCount: 8
tools:
- "resource-exists"
@@ -284,6 +500,24 @@
description: "Let your agents read the code, issues and pull requests of the repositories you choose, and write to them if you allow it."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "GitHub"
+ "es": "GitHub"
+ "fr": "GitHub"
+ "id": "GitHub"
+ "it": "GitHub"
+ "ko": "GitHub"
+ "pt-BR": "GitHub"
+ "zh-CN": "GitHub"
+ descriptions:
+ "de": "Lassen Sie Ihre Agenten den Code, die Issues und die Pull Requests der von Ihnen gewählten Repositorys lesen und, wenn Sie es erlauben, darin schreiben."
+ "es": "Permite que tus agentes lean el código, las issues y los pull requests de los repositorios que elijas y escriban en ellos si lo autorizas."
+ "fr": "Laissez vos agents lire le code, les issues et les pull requests des dépôts que vous choisissez, et y écrire si vous l'autorisez."
+ "id": "Izinkan agen Anda membaca kode, isu, dan pull request dari repositori yang Anda pilih, dan menulis di sana jika Anda mengizinkan."
+ "it": "Lascia che i tuoi agenti leggano il codice, le issue e le pull request dei repository che scegli, e ci scrivano se lo permetti."
+ "ko": "에이전트가 선택한 저장소의 코드, 이슈, 풀 리퀘스트를 읽고, 허용하면 그곳에 쓸 수 있게 합니다."
+ "pt-BR": "Deixe seus agentes lerem o código, as issues e os pull requests dos repositórios que você escolher, e escreverem neles se você permitir."
+ "zh-CN": "让你的智能体读取你所选仓库的代码、Issue 和 Pull Request,并在你允许时向其中写入。"
toolCount: 14
tools:
- "search-code"
@@ -318,6 +552,24 @@
description: "Let your agents read the code, issues and pull requests of the repositories you choose, and write to them if you allow it."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "GitHub"
+ "es": "GitHub"
+ "fr": "GitHub"
+ "id": "GitHub"
+ "it": "GitHub"
+ "ko": "GitHub"
+ "pt-BR": "GitHub"
+ "zh-CN": "GitHub"
+ descriptions:
+ "de": "Lassen Sie Ihre Agenten den Code, die Issues und die Pull Requests der von Ihnen gewählten Repositorys lesen und, wenn Sie es erlauben, darin schreiben."
+ "es": "Permite que tus agentes lean el código, las issues y los pull requests de los repositorios que elijas y escriban en ellos si lo autorizas."
+ "fr": "Laissez vos agents lire le code, les issues et les pull requests des dépôts que vous choisissez, et y écrire si vous l'autorisez."
+ "id": "Izinkan agen Anda membaca kode, isu, dan pull request dari repositori yang Anda pilih, dan menulis di sana jika Anda mengizinkan."
+ "it": "Lascia che i tuoi agenti leggano il codice, le issue e le pull request dei repository che scegli, e ci scrivano se lo permetti."
+ "ko": "에이전트가 선택한 저장소의 코드, 이슈, 풀 리퀘스트를 읽고, 허용하면 그곳에 쓸 수 있게 합니다."
+ "pt-BR": "Deixe seus agentes lerem o código, as issues e os pull requests dos repositórios que você escolher, e escreverem neles se você permitir."
+ "zh-CN": "让你的智能体读取你所选仓库的代码、Issue 和 Pull Request,并在你允许时向其中写入。"
toolCount: 14
tools:
- "search-code"
@@ -352,6 +604,24 @@
description: "Let your agents read the code, issues and pull requests of the repositories you choose, and write to them if you allow it."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "GitHub"
+ "es": "GitHub"
+ "fr": "GitHub"
+ "id": "GitHub"
+ "it": "GitHub"
+ "ko": "GitHub"
+ "pt-BR": "GitHub"
+ "zh-CN": "GitHub"
+ descriptions:
+ "de": "Lassen Sie Ihre Agenten den Code, die Issues und die Pull Requests der von Ihnen gewählten Repositorys lesen und, wenn Sie es erlauben, darin schreiben."
+ "es": "Permite que tus agentes lean el código, las issues y los pull requests de los repositorios que elijas y escriban en ellos si lo autorizas."
+ "fr": "Laissez vos agents lire le code, les issues et les pull requests des dépôts que vous choisissez, et y écrire si vous l'autorisez."
+ "id": "Izinkan agen Anda membaca kode, isu, dan pull request dari repositori yang Anda pilih, dan menulis di sana jika Anda mengizinkan."
+ "it": "Lascia che i tuoi agenti leggano il codice, le issue e le pull request dei repository che scegli, e ci scrivano se lo permetti."
+ "ko": "에이전트가 선택한 저장소의 코드, 이슈, 풀 리퀘스트를 읽고, 허용하면 그곳에 쓸 수 있게 합니다."
+ "pt-BR": "Deixe seus agentes lerem o código, as issues e os pull requests dos repositórios que você escolher, e escreverem neles se você permitir."
+ "zh-CN": "让你的智能体读取你所选仓库的代码、Issue 和 Pull Request,并在你允许时向其中写入。"
toolCount: 14
tools:
- "search-code"
@@ -386,6 +656,24 @@
description: "Let your agents read the code, issues and pull requests of the repositories you choose, and write to them if you allow it."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "GitHub"
+ "es": "GitHub"
+ "fr": "GitHub"
+ "id": "GitHub"
+ "it": "GitHub"
+ "ko": "GitHub"
+ "pt-BR": "GitHub"
+ "zh-CN": "GitHub"
+ descriptions:
+ "de": "Lassen Sie Ihre Agenten den Code, die Issues und die Pull Requests der von Ihnen gewählten Repositorys lesen und, wenn Sie es erlauben, darin schreiben."
+ "es": "Permite que tus agentes lean el código, las issues y los pull requests de los repositorios que elijas y escriban en ellos si lo autorizas."
+ "fr": "Laissez vos agents lire le code, les issues et les pull requests des dépôts que vous choisissez, et y écrire si vous l'autorisez."
+ "id": "Izinkan agen Anda membaca kode, isu, dan pull request dari repositori yang Anda pilih, dan menulis di sana jika Anda mengizinkan."
+ "it": "Lascia che i tuoi agenti leggano il codice, le issue e le pull request dei repository che scegli, e ci scrivano se lo permetti."
+ "ko": "에이전트가 선택한 저장소의 코드, 이슈, 풀 리퀘스트를 읽고, 허용하면 그곳에 쓸 수 있게 합니다."
+ "pt-BR": "Deixe seus agentes lerem o código, as issues e os pull requests dos repositórios que você escolher, e escreverem neles se você permitir."
+ "zh-CN": "让你的智能体读取你所选仓库的代码、Issue 和 Pull Request,并在你允许时向其中写入。"
toolCount: 14
tools:
- "search-code"
@@ -420,6 +708,24 @@
description: "Let your chats and agents read your smart home and, if you allow it, switch lights and other things, with limits you set."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "Home Assistant"
+ "es": "Home Assistant"
+ "fr": "Home Assistant"
+ "id": "Home Assistant"
+ "it": "Home Assistant"
+ "ko": "Home Assistant"
+ "pt-BR": "Home Assistant"
+ "zh-CN": "Home Assistant"
+ descriptions:
+ "de": "Lassen Sie Ihre Chats und Agenten Ihr Smart Home auslesen und, wenn Sie es erlauben, Lichter und andere Geräte schalten, innerhalb der von Ihnen gesetzten Grenzen."
+ "es": "Permite que tus chats y agentes lean tu hogar inteligente y, si lo autorizas, enciendan luces y controlen otros dispositivos, dentro de los límites que establezcas."
+ "fr": "Laissez vos conversations et vos agents lire votre maison connectée et, si vous l'autorisez, allumer les lumières et commander d'autres appareils, dans les limites que vous fixez."
+ "id": "Izinkan obrolan dan agen Anda membaca rumah pintar Anda dan, jika Anda mengizinkan, menyalakan lampu serta perangkat lain, dengan batas yang Anda tentukan."
+ "it": "Lascia che le tue chat e i tuoi agenti leggano la tua casa intelligente e, se lo permetti, accendano le luci e altri dispositivi, entro i limiti che imposti."
+ "ko": "채팅과 에이전트가 스마트홈을 읽고, 허용하면 조명 등 기기를 켜고 끌 수 있게 합니다. 한도는 직접 정합니다."
+ "pt-BR": "Deixe seus chats e agentes lerem sua casa inteligente e, se você permitir, ligarem luzes e controlarem outros dispositivos, dentro dos limites que você define."
+ "zh-CN": "让你的聊天和智能体读取你的智能家居,并在你允许时开关灯光等设备,权限范围由你设定。"
toolCount: 11
tools:
- "get-config"
@@ -451,6 +757,24 @@
description: "Let your chats and agents read your smart home and, if you allow it, switch lights and other things, with limits you set."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "Home Assistant"
+ "es": "Home Assistant"
+ "fr": "Home Assistant"
+ "id": "Home Assistant"
+ "it": "Home Assistant"
+ "ko": "Home Assistant"
+ "pt-BR": "Home Assistant"
+ "zh-CN": "Home Assistant"
+ descriptions:
+ "de": "Lassen Sie Ihre Chats und Agenten Ihr Smart Home auslesen und, wenn Sie es erlauben, Lichter und andere Geräte schalten, innerhalb der von Ihnen gesetzten Grenzen."
+ "es": "Permite que tus chats y agentes lean tu hogar inteligente y, si lo autorizas, enciendan luces y controlen otros dispositivos, dentro de los límites que establezcas."
+ "fr": "Laissez vos conversations et vos agents lire votre maison connectée et, si vous l'autorisez, allumer les lumières et commander d'autres appareils, dans les limites que vous fixez."
+ "id": "Izinkan obrolan dan agen Anda membaca rumah pintar Anda dan, jika Anda mengizinkan, menyalakan lampu serta perangkat lain, dengan batas yang Anda tentukan."
+ "it": "Lascia che le tue chat e i tuoi agenti leggano la tua casa intelligente e, se lo permetti, accendano le luci e altri dispositivi, entro i limiti che imposti."
+ "ko": "채팅과 에이전트가 스마트홈을 읽고, 허용하면 조명 등 기기를 켜고 끌 수 있게 합니다. 한도는 직접 정합니다."
+ "pt-BR": "Deixe seus chats e agentes lerem sua casa inteligente e, se você permitir, ligarem luzes e controlarem outros dispositivos, dentro dos limites que você define."
+ "zh-CN": "让你的聊天和智能体读取你的智能家居,并在你允许时开关灯光等设备,权限范围由你设定。"
toolCount: 11
tools:
- "get-config"
@@ -482,6 +806,24 @@
description: "Let your chats and agents read your smart home and, if you allow it, switch lights and other things, with limits you set."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "Home Assistant"
+ "es": "Home Assistant"
+ "fr": "Home Assistant"
+ "id": "Home Assistant"
+ "it": "Home Assistant"
+ "ko": "Home Assistant"
+ "pt-BR": "Home Assistant"
+ "zh-CN": "Home Assistant"
+ descriptions:
+ "de": "Lassen Sie Ihre Chats und Agenten Ihr Smart Home auslesen und, wenn Sie es erlauben, Lichter und andere Geräte schalten, innerhalb der von Ihnen gesetzten Grenzen."
+ "es": "Permite que tus chats y agentes lean tu hogar inteligente y, si lo autorizas, enciendan luces y controlen otros dispositivos, dentro de los límites que establezcas."
+ "fr": "Laissez vos conversations et vos agents lire votre maison connectée et, si vous l'autorisez, allumer les lumières et commander d'autres appareils, dans les limites que vous fixez."
+ "id": "Izinkan obrolan dan agen Anda membaca rumah pintar Anda dan, jika Anda mengizinkan, menyalakan lampu serta perangkat lain, dengan batas yang Anda tentukan."
+ "it": "Lascia che le tue chat e i tuoi agenti leggano la tua casa intelligente e, se lo permetti, accendano le luci e altri dispositivi, entro i limiti che imposti."
+ "ko": "채팅과 에이전트가 스마트홈을 읽고, 허용하면 조명 등 기기를 켜고 끌 수 있게 합니다. 한도는 직접 정합니다."
+ "pt-BR": "Deixe seus chats e agentes lerem sua casa inteligente e, se você permitir, ligarem luzes e controlarem outros dispositivos, dentro dos limites que você define."
+ "zh-CN": "让你的聊天和智能体读取你的智能家居,并在你允许时开关灯光等设备,权限范围由你设定。"
toolCount: 11
tools:
- "get-config"
@@ -513,6 +855,24 @@
description: "Let your chats and agents read your smart home and, if you allow it, switch lights and other things, with limits you set."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "Home Assistant"
+ "es": "Home Assistant"
+ "fr": "Home Assistant"
+ "id": "Home Assistant"
+ "it": "Home Assistant"
+ "ko": "Home Assistant"
+ "pt-BR": "Home Assistant"
+ "zh-CN": "Home Assistant"
+ descriptions:
+ "de": "Lassen Sie Ihre Chats und Agenten Ihr Smart Home auslesen und, wenn Sie es erlauben, Lichter und andere Geräte schalten, innerhalb der von Ihnen gesetzten Grenzen."
+ "es": "Permite que tus chats y agentes lean tu hogar inteligente y, si lo autorizas, enciendan luces y controlen otros dispositivos, dentro de los límites que establezcas."
+ "fr": "Laissez vos conversations et vos agents lire votre maison connectée et, si vous l'autorisez, allumer les lumières et commander d'autres appareils, dans les limites que vous fixez."
+ "id": "Izinkan obrolan dan agen Anda membaca rumah pintar Anda dan, jika Anda mengizinkan, menyalakan lampu serta perangkat lain, dengan batas yang Anda tentukan."
+ "it": "Lascia che le tue chat e i tuoi agenti leggano la tua casa intelligente e, se lo permetti, accendano le luci e altri dispositivi, entro i limiti che imposti."
+ "ko": "채팅과 에이전트가 스마트홈을 읽고, 허용하면 조명 등 기기를 켜고 끌 수 있게 합니다. 한도는 직접 정합니다."
+ "pt-BR": "Deixe seus chats e agentes lerem sua casa inteligente e, se você permitir, ligarem luzes e controlarem outros dispositivos, dentro dos limites que você define."
+ "zh-CN": "让你的聊天和智能体读取你的智能家居,并在你允许时开关灯光等设备,权限范围由你设定。"
toolCount: 11
tools:
- "get-config"
@@ -544,6 +904,24 @@
description: "Let your agents search your Jellyfin library, see what is playing, and remote control players if you allow it."
mainPage: "jellyfin"
icon: "ui/icon.png"
+ titles:
+ "de": "Jellyfin"
+ "es": "Jellyfin"
+ "fr": "Jellyfin"
+ "id": "Jellyfin"
+ "it": "Jellyfin"
+ "ko": "Jellyfin"
+ "pt-BR": "Jellyfin"
+ "zh-CN": "Jellyfin"
+ descriptions:
+ "de": "Lassen Sie Ihre Agenten Ihre Jellyfin-Bibliothek durchsuchen, sehen, was gerade läuft, und, wenn Sie es erlauben, Wiedergabegeräte fernsteuern."
+ "es": "Permite que tus agentes busquen en tu biblioteca de Jellyfin, vean qué se está reproduciendo y, si lo autorizas, controlen los reproductores de forma remota."
+ "fr": "Laissez vos agents chercher dans votre bibliothèque Jellyfin, voir ce qui est en cours de lecture et, si vous l'autorisez, contrôler les lecteurs à distance."
+ "id": "Izinkan agen Anda mencari di pustaka Jellyfin Anda, melihat apa yang sedang diputar, dan mengendalikan pemutar dari jarak jauh jika Anda mengizinkan."
+ "it": "Lascia che i tuoi agenti cerchino nella tua libreria Jellyfin, vedano cosa è in riproduzione e controllino i player a distanza se lo permetti."
+ "ko": "에이전트가 Jellyfin 라이브러리를 검색하고 재생 중인 항목을 확인하며, 허용하면 플레이어를 원격으로 제어할 수 있게 합니다."
+ "pt-BR": "Deixe seus agentes pesquisarem na sua biblioteca do Jellyfin, verem o que está tocando e, se você permitir, controlarem os players remotamente."
+ "zh-CN": "让你的智能体搜索你的 Jellyfin 媒体库,查看正在播放的内容,并在你允许时远程控制播放器。"
toolCount: 16
tools:
- "search-items"
@@ -580,6 +958,24 @@
description: "Let your agents search your Jellyfin library, see what is playing, and remote control players if you allow it."
mainPage: "jellyfin"
icon: "ui/icon.png"
+ titles:
+ "de": "Jellyfin"
+ "es": "Jellyfin"
+ "fr": "Jellyfin"
+ "id": "Jellyfin"
+ "it": "Jellyfin"
+ "ko": "Jellyfin"
+ "pt-BR": "Jellyfin"
+ "zh-CN": "Jellyfin"
+ descriptions:
+ "de": "Lassen Sie Ihre Agenten Ihre Jellyfin-Bibliothek durchsuchen, sehen, was gerade läuft, und, wenn Sie es erlauben, Wiedergabegeräte fernsteuern."
+ "es": "Permite que tus agentes busquen en tu biblioteca de Jellyfin, vean qué se está reproduciendo y, si lo autorizas, controlen los reproductores de forma remota."
+ "fr": "Laissez vos agents chercher dans votre bibliothèque Jellyfin, voir ce qui est en cours de lecture et, si vous l'autorisez, contrôler les lecteurs à distance."
+ "id": "Izinkan agen Anda mencari di pustaka Jellyfin Anda, melihat apa yang sedang diputar, dan mengendalikan pemutar dari jarak jauh jika Anda mengizinkan."
+ "it": "Lascia che i tuoi agenti cerchino nella tua libreria Jellyfin, vedano cosa è in riproduzione e controllino i player a distanza se lo permetti."
+ "ko": "에이전트가 Jellyfin 라이브러리를 검색하고 재생 중인 항목을 확인하며, 허용하면 플레이어를 원격으로 제어할 수 있게 합니다."
+ "pt-BR": "Deixe seus agentes pesquisarem na sua biblioteca do Jellyfin, verem o que está tocando e, se você permitir, controlarem os players remotamente."
+ "zh-CN": "让你的智能体搜索你的 Jellyfin 媒体库,查看正在播放的内容,并在你允许时远程控制播放器。"
toolCount: 16
tools:
- "search-items"
@@ -616,6 +1012,24 @@
description: "Let your agents search your Jellyfin library, see what is playing, and remote control players if you allow it."
mainPage: "jellyfin"
icon: "ui/icon.png"
+ titles:
+ "de": "Jellyfin"
+ "es": "Jellyfin"
+ "fr": "Jellyfin"
+ "id": "Jellyfin"
+ "it": "Jellyfin"
+ "ko": "Jellyfin"
+ "pt-BR": "Jellyfin"
+ "zh-CN": "Jellyfin"
+ descriptions:
+ "de": "Lassen Sie Ihre Agenten Ihre Jellyfin-Bibliothek durchsuchen, sehen, was gerade läuft, und, wenn Sie es erlauben, Wiedergabegeräte fernsteuern."
+ "es": "Permite que tus agentes busquen en tu biblioteca de Jellyfin, vean qué se está reproduciendo y, si lo autorizas, controlen los reproductores de forma remota."
+ "fr": "Laissez vos agents chercher dans votre bibliothèque Jellyfin, voir ce qui est en cours de lecture et, si vous l'autorisez, contrôler les lecteurs à distance."
+ "id": "Izinkan agen Anda mencari di pustaka Jellyfin Anda, melihat apa yang sedang diputar, dan mengendalikan pemutar dari jarak jauh jika Anda mengizinkan."
+ "it": "Lascia che i tuoi agenti cerchino nella tua libreria Jellyfin, vedano cosa è in riproduzione e controllino i player a distanza se lo permetti."
+ "ko": "에이전트가 Jellyfin 라이브러리를 검색하고 재생 중인 항목을 확인하며, 허용하면 플레이어를 원격으로 제어할 수 있게 합니다."
+ "pt-BR": "Deixe seus agentes pesquisarem na sua biblioteca do Jellyfin, verem o que está tocando e, se você permitir, controlarem os players remotamente."
+ "zh-CN": "让你的智能体搜索你的 Jellyfin 媒体库,查看正在播放的内容,并在你允许时远程控制播放器。"
toolCount: 16
tools:
- "search-items"
@@ -652,6 +1066,24 @@
description: "Let your agents search your Jellyfin library, see what is playing, and remote control players if you allow it."
mainPage: "jellyfin"
icon: "ui/icon.png"
+ titles:
+ "de": "Jellyfin"
+ "es": "Jellyfin"
+ "fr": "Jellyfin"
+ "id": "Jellyfin"
+ "it": "Jellyfin"
+ "ko": "Jellyfin"
+ "pt-BR": "Jellyfin"
+ "zh-CN": "Jellyfin"
+ descriptions:
+ "de": "Lassen Sie Ihre Agenten Ihre Jellyfin-Bibliothek durchsuchen, sehen, was gerade läuft, und, wenn Sie es erlauben, Wiedergabegeräte fernsteuern."
+ "es": "Permite que tus agentes busquen en tu biblioteca de Jellyfin, vean qué se está reproduciendo y, si lo autorizas, controlen los reproductores de forma remota."
+ "fr": "Laissez vos agents chercher dans votre bibliothèque Jellyfin, voir ce qui est en cours de lecture et, si vous l'autorisez, contrôler les lecteurs à distance."
+ "id": "Izinkan agen Anda mencari di pustaka Jellyfin Anda, melihat apa yang sedang diputar, dan mengendalikan pemutar dari jarak jauh jika Anda mengizinkan."
+ "it": "Lascia che i tuoi agenti cerchino nella tua libreria Jellyfin, vedano cosa è in riproduzione e controllino i player a distanza se lo permetti."
+ "ko": "에이전트가 Jellyfin 라이브러리를 검색하고 재생 중인 항목을 확인하며, 허용하면 플레이어를 원격으로 제어할 수 있게 합니다."
+ "pt-BR": "Deixe seus agentes pesquisarem na sua biblioteca do Jellyfin, verem o que está tocando e, se você permitir, controlarem os players remotamente."
+ "zh-CN": "让你的智能体搜索你的 Jellyfin 媒体库,查看正在播放的内容,并在你允许时远程控制播放器。"
toolCount: 16
tools:
- "search-items"
@@ -688,6 +1120,24 @@
description: "Your chats keep short notes about you and read them before they answer. By default a new note is saved only after you confirm it; you can choose to have it saved at once with an Undo, or never."
mainPage: "memory"
icon: "ui/icon.png"
+ titles:
+ "de": "Kurzzeitgedächtnis"
+ "es": "Memoria breve"
+ "fr": "Mémoire courte"
+ "id": "Memori singkat"
+ "it": "Memoria breve"
+ "ko": "짧은 기억"
+ "pt-BR": "Memória curta"
+ "zh-CN": "简短记忆"
+ descriptions:
+ "de": "Ihre Chats führen kurze Notizen über Sie und lesen sie, bevor sie antworten. Standardmäßig wird eine neue Notiz erst nach Ihrer Bestätigung gespeichert; Sie können sie auch sofort mit Rückgängig-Option speichern lassen oder nie."
+ "es": "Tus chats guardan notas breves sobre ti y las leen antes de responder. De forma predeterminada, una nota nueva se guarda solo después de que la confirmes; puedes elegir que se guarde al instante con la opción de deshacer, o que nunca se guarde."
+ "fr": "Vos conversations gardent de courtes notes sur vous et les lisent avant de répondre. Par défaut, une nouvelle note n'est enregistrée qu'après votre confirmation ; vous pouvez choisir de l'enregistrer immédiatement avec une option Annuler, ou jamais."
+ "id": "Obrolan Anda menyimpan catatan singkat tentang Anda dan membacanya sebelum menjawab. Secara bawaan, catatan baru disimpan hanya setelah Anda mengonfirmasinya; Anda dapat memilih agar langsung disimpan dengan opsi Urungkan, atau tidak pernah."
+ "it": "Le tue chat conservano brevi note su di te e le leggono prima di rispondere. Di norma una nuova nota viene salvata solo dopo la tua conferma; puoi scegliere di salvarla subito con un Annulla, oppure mai."
+ "ko": "채팅이 나에 대한 짧은 메모를 보관하고, 답하기 전에 읽습니다. 기본적으로 새 메모는 확인한 후에만 저장되며, 실행 취소와 함께 바로 저장하거나 저장하지 않도록 선택할 수 있습니다."
+ "pt-BR": "Seus chats guardam notas curtas sobre você e as leem antes de responder. Por padrão, uma nova nota só é salva depois que você a confirma; você pode escolher salvá-la na hora, com a opção de desfazer, ou nunca."
+ "zh-CN": "你的聊天会记下关于你的简短笔记,并在回答前读取它们。默认情况下,新笔记须经你确认后才会保存;你也可以选择立即保存并提供撤销,或从不保存。"
- id: "memory"
name: Short memory
description: "Your chats keep short notes about you and read them before they answer. By default a new note is saved only after you confirm it; you can choose to have it saved at once with an Undo, or never."
@@ -706,6 +1156,24 @@
description: "Your chats keep short notes about you and read them before they answer. By default a new note is saved only after you confirm it; you can choose to have it saved at once with an Undo, or never."
mainPage: "memory"
icon: "ui/icon.png"
+ titles:
+ "de": "Kurzzeitgedächtnis"
+ "es": "Memoria breve"
+ "fr": "Mémoire courte"
+ "id": "Memori singkat"
+ "it": "Memoria breve"
+ "ko": "짧은 기억"
+ "pt-BR": "Memória curta"
+ "zh-CN": "简短记忆"
+ descriptions:
+ "de": "Ihre Chats führen kurze Notizen über Sie und lesen sie, bevor sie antworten. Standardmäßig wird eine neue Notiz erst nach Ihrer Bestätigung gespeichert; Sie können sie auch sofort mit Rückgängig-Option speichern lassen oder nie."
+ "es": "Tus chats guardan notas breves sobre ti y las leen antes de responder. De forma predeterminada, una nota nueva se guarda solo después de que la confirmes; puedes elegir que se guarde al instante con la opción de deshacer, o que nunca se guarde."
+ "fr": "Vos conversations gardent de courtes notes sur vous et les lisent avant de répondre. Par défaut, une nouvelle note n'est enregistrée qu'après votre confirmation ; vous pouvez choisir de l'enregistrer immédiatement avec une option Annuler, ou jamais."
+ "id": "Obrolan Anda menyimpan catatan singkat tentang Anda dan membacanya sebelum menjawab. Secara bawaan, catatan baru disimpan hanya setelah Anda mengonfirmasinya; Anda dapat memilih agar langsung disimpan dengan opsi Urungkan, atau tidak pernah."
+ "it": "Le tue chat conservano brevi note su di te e le leggono prima di rispondere. Di norma una nuova nota viene salvata solo dopo la tua conferma; puoi scegliere di salvarla subito con un Annulla, oppure mai."
+ "ko": "채팅이 나에 대한 짧은 메모를 보관하고, 답하기 전에 읽습니다. 기본적으로 새 메모는 확인한 후에만 저장되며, 실행 취소와 함께 바로 저장하거나 저장하지 않도록 선택할 수 있습니다."
+ "pt-BR": "Seus chats guardam notas curtas sobre você e as leem antes de responder. Por padrão, uma nova nota só é salva depois que você a confirma; você pode escolher salvá-la na hora, com a opção de desfazer, ou nunca."
+ "zh-CN": "你的聊天会记下关于你的简短笔记,并在回答前读取它们。默认情况下,新笔记须经你确认后才会保存;你也可以选择立即保存并提供撤销,或从不保存。"
- id: "memory"
name: Short memory
description: "Your chats keep short notes about you and read them before they answer. By default a new note is saved only after you confirm it; you can choose to have it saved at once with an Undo, or never."
@@ -724,6 +1192,24 @@
description: "Your chats keep short notes about you and read them before they answer. By default a new note is saved only after you confirm it; you can choose to have it saved at once with an Undo, or never."
mainPage: "memory"
icon: "ui/icon.png"
+ titles:
+ "de": "Kurzzeitgedächtnis"
+ "es": "Memoria breve"
+ "fr": "Mémoire courte"
+ "id": "Memori singkat"
+ "it": "Memoria breve"
+ "ko": "짧은 기억"
+ "pt-BR": "Memória curta"
+ "zh-CN": "简短记忆"
+ descriptions:
+ "de": "Ihre Chats führen kurze Notizen über Sie und lesen sie, bevor sie antworten. Standardmäßig wird eine neue Notiz erst nach Ihrer Bestätigung gespeichert; Sie können sie auch sofort mit Rückgängig-Option speichern lassen oder nie."
+ "es": "Tus chats guardan notas breves sobre ti y las leen antes de responder. De forma predeterminada, una nota nueva se guarda solo después de que la confirmes; puedes elegir que se guarde al instante con la opción de deshacer, o que nunca se guarde."
+ "fr": "Vos conversations gardent de courtes notes sur vous et les lisent avant de répondre. Par défaut, une nouvelle note n'est enregistrée qu'après votre confirmation ; vous pouvez choisir de l'enregistrer immédiatement avec une option Annuler, ou jamais."
+ "id": "Obrolan Anda menyimpan catatan singkat tentang Anda dan membacanya sebelum menjawab. Secara bawaan, catatan baru disimpan hanya setelah Anda mengonfirmasinya; Anda dapat memilih agar langsung disimpan dengan opsi Urungkan, atau tidak pernah."
+ "it": "Le tue chat conservano brevi note su di te e le leggono prima di rispondere. Di norma una nuova nota viene salvata solo dopo la tua conferma; puoi scegliere di salvarla subito con un Annulla, oppure mai."
+ "ko": "채팅이 나에 대한 짧은 메모를 보관하고, 답하기 전에 읽습니다. 기본적으로 새 메모는 확인한 후에만 저장되며, 실행 취소와 함께 바로 저장하거나 저장하지 않도록 선택할 수 있습니다."
+ "pt-BR": "Seus chats guardam notas curtas sobre você e as leem antes de responder. Por padrão, uma nova nota só é salva depois que você a confirma; você pode escolher salvá-la na hora, com a opção de desfazer, ou nunca."
+ "zh-CN": "你的聊天会记下关于你的简短笔记,并在回答前读取它们。默认情况下,新笔记须经你确认后才会保存;你也可以选择立即保存并提供撤销,或从不保存。"
- id: "memory"
name: Short memory
description: "Your chats keep short notes about you and read them before they answer. By default a new note is saved only after you confirm it; you can choose to have it saved at once with an Undo, or never."
@@ -742,6 +1228,24 @@
description: "Your chats keep short notes about you and read them before they answer. By default a new note is saved only after you confirm it; you can choose to have it saved at once with an Undo, or never."
mainPage: "memory"
icon: "ui/icon.png"
+ titles:
+ "de": "Kurzzeitgedächtnis"
+ "es": "Memoria breve"
+ "fr": "Mémoire courte"
+ "id": "Memori singkat"
+ "it": "Memoria breve"
+ "ko": "짧은 기억"
+ "pt-BR": "Memória curta"
+ "zh-CN": "简短记忆"
+ descriptions:
+ "de": "Ihre Chats führen kurze Notizen über Sie und lesen sie, bevor sie antworten. Standardmäßig wird eine neue Notiz erst nach Ihrer Bestätigung gespeichert; Sie können sie auch sofort mit Rückgängig-Option speichern lassen oder nie."
+ "es": "Tus chats guardan notas breves sobre ti y las leen antes de responder. De forma predeterminada, una nota nueva se guarda solo después de que la confirmes; puedes elegir que se guarde al instante con la opción de deshacer, o que nunca se guarde."
+ "fr": "Vos conversations gardent de courtes notes sur vous et les lisent avant de répondre. Par défaut, une nouvelle note n'est enregistrée qu'après votre confirmation ; vous pouvez choisir de l'enregistrer immédiatement avec une option Annuler, ou jamais."
+ "id": "Obrolan Anda menyimpan catatan singkat tentang Anda dan membacanya sebelum menjawab. Secara bawaan, catatan baru disimpan hanya setelah Anda mengonfirmasinya; Anda dapat memilih agar langsung disimpan dengan opsi Urungkan, atau tidak pernah."
+ "it": "Le tue chat conservano brevi note su di te e le leggono prima di rispondere. Di norma una nuova nota viene salvata solo dopo la tua conferma; puoi scegliere di salvarla subito con un Annulla, oppure mai."
+ "ko": "채팅이 나에 대한 짧은 메모를 보관하고, 답하기 전에 읽습니다. 기본적으로 새 메모는 확인한 후에만 저장되며, 실행 취소와 함께 바로 저장하거나 저장하지 않도록 선택할 수 있습니다."
+ "pt-BR": "Seus chats guardam notas curtas sobre você e as leem antes de responder. Por padrão, uma nova nota só é salva depois que você a confirma; você pode escolher salvá-la na hora, com a opção de desfazer, ou nunca."
+ "zh-CN": "你的聊天会记下关于你的简短笔记,并在回答前读取它们。默认情况下,新笔记须经你确认后才会保存;你也可以选择立即保存并提供撤销,或从不保存。"
- id: "office"
name: Simple Office
description: "Lightweight basic Word, Excel, PowerPoint and PDF file work from your chats, without requiring Sandbox."
@@ -760,6 +1264,24 @@
description: "Lightweight basic Word, Excel, PowerPoint and PDF file work from your chats, without requiring Sandbox."
mainPage: "office"
icon: "ui/icon.png"
+ titles:
+ "de": "Einfaches Office"
+ "es": "Office sencillo"
+ "fr": "Office simple"
+ "id": "Office Sederhana"
+ "it": "Office semplice"
+ "ko": "간단한 Office"
+ "pt-BR": "Office simples"
+ "zh-CN": "简易 Office"
+ descriptions:
+ "de": "Einfache Arbeit mit Word-, Excel-, PowerPoint- und PDF-Dateien direkt aus Ihren Chats, ohne dass Sandbox nötig ist."
+ "es": "Trabajo ligero y básico con archivos de Word, Excel, PowerPoint y PDF desde tus chats, sin necesidad de Sandbox."
+ "fr": "Travail léger et de base sur des fichiers Word, Excel, PowerPoint et PDF depuis vos conversations, sans avoir besoin de Sandbox."
+ "id": "Pekerjaan ringan dan dasar dengan berkas Word, Excel, PowerPoint, dan PDF dari obrolan Anda, tanpa memerlukan Sandbox."
+ "it": "Lavoro leggero e di base su file Word, Excel, PowerPoint e PDF dalle tue chat, senza richiedere Sandbox."
+ "ko": "Sandbox 없이도 채팅에서 Word, Excel, PowerPoint, PDF 파일의 기본 작업을 가볍게 처리합니다."
+ "pt-BR": "Trabalho leve e básico com arquivos Word, Excel, PowerPoint e PDF a partir dos seus chats, sem precisar do Sandbox."
+ "zh-CN": "在聊天中轻量地处理基础的 Word、Excel、PowerPoint 和 PDF 文件,无需 Sandbox。"
toolCount: 11
tools:
- "read-docx"
@@ -796,6 +1318,24 @@
description: "Lightweight basic Word, Excel, PowerPoint and PDF file work from your chats, without requiring Sandbox."
mainPage: "office"
icon: "ui/icon.png"
+ titles:
+ "de": "Einfaches Office"
+ "es": "Office sencillo"
+ "fr": "Office simple"
+ "id": "Office Sederhana"
+ "it": "Office semplice"
+ "ko": "간단한 Office"
+ "pt-BR": "Office simples"
+ "zh-CN": "简易 Office"
+ descriptions:
+ "de": "Einfache Arbeit mit Word-, Excel-, PowerPoint- und PDF-Dateien direkt aus Ihren Chats, ohne dass Sandbox nötig ist."
+ "es": "Trabajo ligero y básico con archivos de Word, Excel, PowerPoint y PDF desde tus chats, sin necesidad de Sandbox."
+ "fr": "Travail léger et de base sur des fichiers Word, Excel, PowerPoint et PDF depuis vos conversations, sans avoir besoin de Sandbox."
+ "id": "Pekerjaan ringan dan dasar dengan berkas Word, Excel, PowerPoint, dan PDF dari obrolan Anda, tanpa memerlukan Sandbox."
+ "it": "Lavoro leggero e di base su file Word, Excel, PowerPoint e PDF dalle tue chat, senza richiedere Sandbox."
+ "ko": "Sandbox 없이도 채팅에서 Word, Excel, PowerPoint, PDF 파일의 기본 작업을 가볍게 처리합니다."
+ "pt-BR": "Trabalho leve e básico com arquivos Word, Excel, PowerPoint e PDF a partir dos seus chats, sem precisar do Sandbox."
+ "zh-CN": "在聊天中轻量地处理基础的 Word、Excel、PowerPoint 和 PDF 文件,无需 Sandbox。"
toolCount: 11
tools:
- "read-docx"
@@ -832,6 +1372,24 @@
description: "Lightweight basic Word, Excel, PowerPoint and PDF file work from your chats, without requiring Sandbox."
mainPage: "office"
icon: "ui/icon.png"
+ titles:
+ "de": "Einfaches Office"
+ "es": "Office sencillo"
+ "fr": "Office simple"
+ "id": "Office Sederhana"
+ "it": "Office semplice"
+ "ko": "간단한 Office"
+ "pt-BR": "Office simples"
+ "zh-CN": "简易 Office"
+ descriptions:
+ "de": "Einfache Arbeit mit Word-, Excel-, PowerPoint- und PDF-Dateien direkt aus Ihren Chats, ohne dass Sandbox nötig ist."
+ "es": "Trabajo ligero y básico con archivos de Word, Excel, PowerPoint y PDF desde tus chats, sin necesidad de Sandbox."
+ "fr": "Travail léger et de base sur des fichiers Word, Excel, PowerPoint et PDF depuis vos conversations, sans avoir besoin de Sandbox."
+ "id": "Pekerjaan ringan dan dasar dengan berkas Word, Excel, PowerPoint, dan PDF dari obrolan Anda, tanpa memerlukan Sandbox."
+ "it": "Lavoro leggero e di base su file Word, Excel, PowerPoint e PDF dalle tue chat, senza richiedere Sandbox."
+ "ko": "Sandbox 없이도 채팅에서 Word, Excel, PowerPoint, PDF 파일의 기본 작업을 가볍게 처리합니다."
+ "pt-BR": "Trabalho leve e básico com arquivos Word, Excel, PowerPoint e PDF a partir dos seus chats, sem precisar do Sandbox."
+ "zh-CN": "在聊天中轻量地处理基础的 Word、Excel、PowerPoint 和 PDF 文件,无需 Sandbox。"
toolCount: 11
tools:
- "read-docx"
@@ -868,6 +1426,24 @@
description: "Lightweight basic Word, Excel, PowerPoint and PDF file work from your chats, without requiring Sandbox."
mainPage: "office"
icon: "ui/icon.png"
+ titles:
+ "de": "Einfaches Office"
+ "es": "Office sencillo"
+ "fr": "Office simple"
+ "id": "Office Sederhana"
+ "it": "Office semplice"
+ "ko": "간단한 Office"
+ "pt-BR": "Office simples"
+ "zh-CN": "简易 Office"
+ descriptions:
+ "de": "Einfache Arbeit mit Word-, Excel-, PowerPoint- und PDF-Dateien direkt aus Ihren Chats, ohne dass Sandbox nötig ist."
+ "es": "Trabajo ligero y básico con archivos de Word, Excel, PowerPoint y PDF desde tus chats, sin necesidad de Sandbox."
+ "fr": "Travail léger et de base sur des fichiers Word, Excel, PowerPoint et PDF depuis vos conversations, sans avoir besoin de Sandbox."
+ "id": "Pekerjaan ringan dan dasar dengan berkas Word, Excel, PowerPoint, dan PDF dari obrolan Anda, tanpa memerlukan Sandbox."
+ "it": "Lavoro leggero e di base su file Word, Excel, PowerPoint e PDF dalle tue chat, senza richiedere Sandbox."
+ "ko": "Sandbox 없이도 채팅에서 Word, Excel, PowerPoint, PDF 파일의 기본 작업을 가볍게 처리합니다."
+ "pt-BR": "Trabalho leve e básico com arquivos Word, Excel, PowerPoint e PDF a partir dos seus chats, sem precisar do Sandbox."
+ "zh-CN": "在聊天中轻量地处理基础的 Word、Excel、PowerPoint 和 PDF 文件,无需 Sandbox。"
toolCount: 11
tools:
- "read-docx"
@@ -1016,6 +1592,24 @@
description: "Shrink a model to a smaller quantized file, then import it so you can chat with it."
mainPage: "jobs"
icon: "ui/icon.png"
+ titles:
+ "de": "Quantisierung"
+ "es": "Cuantización"
+ "fr": "Quantification"
+ "id": "Kuantisasi"
+ "it": "Quantizzazione"
+ "ko": "양자화"
+ "pt-BR": "Quantização"
+ "zh-CN": "量化"
+ descriptions:
+ "de": "Verkleinern Sie ein Modell zu einer kleineren quantisierten Datei und importieren Sie sie dann, um mit ihr zu chatten."
+ "es": "Reduce un modelo a un archivo cuantizado más pequeño y luego impórtalo para poder chatear con él."
+ "fr": "Réduisez un modèle à un fichier quantifié plus petit, puis importez-le pour pouvoir discuter avec lui."
+ "id": "Perkecil model menjadi berkas terkuantisasi yang lebih kecil, lalu impor agar Anda dapat mengobrol dengannya."
+ "it": "Riduci un modello in un file quantizzato più piccolo, poi importalo per poterci chattare."
+ "ko": "모델을 더 작은 양자화 파일로 줄인 다음 가져와서 채팅할 수 있습니다."
+ "pt-BR": "Reduza um modelo a um arquivo quantizado menor e depois importe-o para poder conversar com ele."
+ "zh-CN": "把模型压缩为更小的量化文件,再导入它,即可与之聊天。"
toolCount: 6
tools:
- "resource-exists"
@@ -1042,6 +1636,24 @@
description: "Shrink a model to a smaller quantized file, then import it so you can chat with it."
mainPage: "jobs"
icon: "ui/icon.png"
+ titles:
+ "de": "Quantisierung"
+ "es": "Cuantización"
+ "fr": "Quantification"
+ "id": "Kuantisasi"
+ "it": "Quantizzazione"
+ "ko": "양자화"
+ "pt-BR": "Quantização"
+ "zh-CN": "量化"
+ descriptions:
+ "de": "Verkleinern Sie ein Modell zu einer kleineren quantisierten Datei und importieren Sie sie dann, um mit ihr zu chatten."
+ "es": "Reduce un modelo a un archivo cuantizado más pequeño y luego impórtalo para poder chatear con él."
+ "fr": "Réduisez un modèle à un fichier quantifié plus petit, puis importez-le pour pouvoir discuter avec lui."
+ "id": "Perkecil model menjadi berkas terkuantisasi yang lebih kecil, lalu impor agar Anda dapat mengobrol dengannya."
+ "it": "Riduci un modello in un file quantizzato più piccolo, poi importalo per poterci chattare."
+ "ko": "모델을 더 작은 양자화 파일로 줄인 다음 가져와서 채팅할 수 있습니다."
+ "pt-BR": "Reduza um modelo a um arquivo quantizado menor e depois importe-o para poder conversar com ele."
+ "zh-CN": "把模型压缩为更小的量化文件,再导入它,即可与之聊天。"
toolCount: 6
tools:
- "resource-exists"
@@ -1068,6 +1680,24 @@
description: "Shrink a model to a smaller quantized file, then import it so you can chat with it."
mainPage: "jobs"
icon: "ui/icon.png"
+ titles:
+ "de": "Quantisierung"
+ "es": "Cuantización"
+ "fr": "Quantification"
+ "id": "Kuantisasi"
+ "it": "Quantizzazione"
+ "ko": "양자화"
+ "pt-BR": "Quantização"
+ "zh-CN": "量化"
+ descriptions:
+ "de": "Verkleinern Sie ein Modell zu einer kleineren quantisierten Datei und importieren Sie sie dann, um mit ihr zu chatten."
+ "es": "Reduce un modelo a un archivo cuantizado más pequeño y luego impórtalo para poder chatear con él."
+ "fr": "Réduisez un modèle à un fichier quantifié plus petit, puis importez-le pour pouvoir discuter avec lui."
+ "id": "Perkecil model menjadi berkas terkuantisasi yang lebih kecil, lalu impor agar Anda dapat mengobrol dengannya."
+ "it": "Riduci un modello in un file quantizzato più piccolo, poi importalo per poterci chattare."
+ "ko": "모델을 더 작은 양자화 파일로 줄인 다음 가져와서 채팅할 수 있습니다."
+ "pt-BR": "Reduza um modelo a um arquivo quantizado menor e depois importe-o para poder conversar com ele."
+ "zh-CN": "把模型压缩为更小的量化文件,再导入它,即可与之聊天。"
toolCount: 6
tools:
- "resource-exists"
@@ -1094,6 +1724,24 @@
description: "Shrink a model to a smaller quantized file, then import it so you can chat with it."
mainPage: "jobs"
icon: "ui/icon.png"
+ titles:
+ "de": "Quantisierung"
+ "es": "Cuantización"
+ "fr": "Quantification"
+ "id": "Kuantisasi"
+ "it": "Quantizzazione"
+ "ko": "양자화"
+ "pt-BR": "Quantização"
+ "zh-CN": "量化"
+ descriptions:
+ "de": "Verkleinern Sie ein Modell zu einer kleineren quantisierten Datei und importieren Sie sie dann, um mit ihr zu chatten."
+ "es": "Reduce un modelo a un archivo cuantizado más pequeño y luego impórtalo para poder chatear con él."
+ "fr": "Réduisez un modèle à un fichier quantifié plus petit, puis importez-le pour pouvoir discuter avec lui."
+ "id": "Perkecil model menjadi berkas terkuantisasi yang lebih kecil, lalu impor agar Anda dapat mengobrol dengannya."
+ "it": "Riduci un modello in un file quantizzato più piccolo, poi importalo per poterci chattare."
+ "ko": "모델을 더 작은 양자화 파일로 줄인 다음 가져와서 채팅할 수 있습니다."
+ "pt-BR": "Reduza um modelo a um arquivo quantizado menor e depois importe-o para poder conversar com ele."
+ "zh-CN": "把模型压缩为更小的量化文件,再导入它,即可与之聊天。"
toolCount: 6
tools:
- "resource-exists"
@@ -1120,6 +1768,24 @@
description: "Let your chats run commands and code in an isolated sandbox, with your files mounted."
mainPage: "sandbox"
icon: "ui/icon.png"
+ titles:
+ "de": "Sandbox"
+ "es": "Sandbox"
+ "fr": "Sandbox"
+ "id": "Sandbox"
+ "it": "Sandbox"
+ "ko": "샌드박스"
+ "pt-BR": "Sandbox"
+ "zh-CN": "沙箱"
+ descriptions:
+ "de": "Lassen Sie Ihre Chats Befehle und Code in einer isolierten Sandbox ausführen, mit eingebundenen Dateien."
+ "es": "Permite que tus chats ejecuten comandos y código en un sandbox aislado, con tus archivos montados."
+ "fr": "Laissez vos conversations exécuter des commandes et du code dans une sandbox isolée, avec vos fichiers montés."
+ "id": "Izinkan obrolan Anda menjalankan perintah dan kode di sandbox terisolasi, dengan berkas Anda terpasang."
+ "it": "Lascia che le tue chat eseguano comandi e codice in una sandbox isolata, con i tuoi file montati."
+ "ko": "채팅이 격리된 샌드박스에서 명령과 코드를 실행할 수 있게 하며, 내 파일이 마운트됩니다."
+ "pt-BR": "Deixe seus chats executarem comandos e código em um sandbox isolado, com seus arquivos montados."
+ "zh-CN": "让你的聊天在隔离的沙箱中运行命令和代码,并挂载你的文件。"
toolCount: 5
tools:
- "bash"
@@ -1152,6 +1818,24 @@
description: "Let your chats run commands and code in an isolated sandbox, with your files mounted."
mainPage: "sandbox"
icon: "ui/icon.png"
+ titles:
+ "de": "Sandbox"
+ "es": "Sandbox"
+ "fr": "Sandbox"
+ "id": "Sandbox"
+ "it": "Sandbox"
+ "ko": "샌드박스"
+ "pt-BR": "Sandbox"
+ "zh-CN": "沙箱"
+ descriptions:
+ "de": "Lassen Sie Ihre Chats Befehle und Code in einer isolierten Sandbox ausführen, mit eingebundenen Dateien."
+ "es": "Permite que tus chats ejecuten comandos y código en un sandbox aislado, con tus archivos montados."
+ "fr": "Laissez vos conversations exécuter des commandes et du code dans une sandbox isolée, avec vos fichiers montés."
+ "id": "Izinkan obrolan Anda menjalankan perintah dan kode di sandbox terisolasi, dengan berkas Anda terpasang."
+ "it": "Lascia che le tue chat eseguano comandi e codice in una sandbox isolata, con i tuoi file montati."
+ "ko": "채팅이 격리된 샌드박스에서 명령과 코드를 실행할 수 있게 하며, 내 파일이 마운트됩니다."
+ "pt-BR": "Deixe seus chats executarem comandos e código em um sandbox isolado, com seus arquivos montados."
+ "zh-CN": "让你的聊天在隔离的沙箱中运行命令和代码,并挂载你的文件。"
toolCount: 5
tools:
- "bash"
@@ -1184,6 +1868,24 @@
description: "Bind a Slack app to one of your saved agents, so people can talk to it from Slack."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "Slack"
+ "es": "Slack"
+ "fr": "Slack"
+ "id": "Slack"
+ "it": "Slack"
+ "ko": "Slack"
+ "pt-BR": "Slack"
+ "zh-CN": "Slack"
+ descriptions:
+ "de": "Verbinden Sie eine Slack-App mit einem Ihrer gespeicherten Agenten, damit andere über Slack mit ihm sprechen können."
+ "es": "Vincula una aplicación de Slack a uno de tus agentes guardados para que la gente pueda hablar con él desde Slack."
+ "fr": "Associez une application Slack à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Slack."
+ "id": "Hubungkan aplikasi Slack ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Slack."
+ "it": "Collega un'app Slack a uno dei tuoi agenti salvati, così le persone possono parlarci da Slack."
+ "ko": "저장한 에이전트 중 하나에 Slack 앱을 연결하여 사람들이 Slack에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um app do Slack a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Slack."
+ "zh-CN": "将 Slack 应用绑定到你保存的某个智能体,让大家可以在 Slack 中与它对话。"
- id: "slack"
name: Slack
description: "Bind a Slack app to one of your saved agents, so people can talk to it from Slack."
@@ -1202,6 +1904,24 @@
description: "Bind a Slack app to one of your saved agents, so people can talk to it from Slack."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "Slack"
+ "es": "Slack"
+ "fr": "Slack"
+ "id": "Slack"
+ "it": "Slack"
+ "ko": "Slack"
+ "pt-BR": "Slack"
+ "zh-CN": "Slack"
+ descriptions:
+ "de": "Verbinden Sie eine Slack-App mit einem Ihrer gespeicherten Agenten, damit andere über Slack mit ihm sprechen können."
+ "es": "Vincula una aplicación de Slack a uno de tus agentes guardados para que la gente pueda hablar con él desde Slack."
+ "fr": "Associez une application Slack à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Slack."
+ "id": "Hubungkan aplikasi Slack ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Slack."
+ "it": "Collega un'app Slack a uno dei tuoi agenti salvati, così le persone possono parlarci da Slack."
+ "ko": "저장한 에이전트 중 하나에 Slack 앱을 연결하여 사람들이 Slack에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um app do Slack a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Slack."
+ "zh-CN": "将 Slack 应用绑定到你保存的某个智能体,让大家可以在 Slack 中与它对话。"
- id: "slack"
name: Slack
description: "Bind a Slack app to one of your saved agents, so people can talk to it from Slack."
@@ -1220,6 +1940,24 @@
description: "Bind a Slack app to one of your saved agents, so people can talk to it from Slack."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "Slack"
+ "es": "Slack"
+ "fr": "Slack"
+ "id": "Slack"
+ "it": "Slack"
+ "ko": "Slack"
+ "pt-BR": "Slack"
+ "zh-CN": "Slack"
+ descriptions:
+ "de": "Verbinden Sie eine Slack-App mit einem Ihrer gespeicherten Agenten, damit andere über Slack mit ihm sprechen können."
+ "es": "Vincula una aplicación de Slack a uno de tus agentes guardados para que la gente pueda hablar con él desde Slack."
+ "fr": "Associez une application Slack à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Slack."
+ "id": "Hubungkan aplikasi Slack ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Slack."
+ "it": "Collega un'app Slack a uno dei tuoi agenti salvati, così le persone possono parlarci da Slack."
+ "ko": "저장한 에이전트 중 하나에 Slack 앱을 연결하여 사람들이 Slack에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um app do Slack a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Slack."
+ "zh-CN": "将 Slack 应用绑定到你保存的某个智能体,让大家可以在 Slack 中与它对话。"
- id: "slack"
name: Slack
description: "Bind a Slack app to one of your saved agents, so people can talk to it from Slack."
@@ -1238,6 +1976,24 @@
description: "Bind a Slack app to one of your saved agents, so people can talk to it from Slack."
mainPage: "apps"
icon: "ui/icon.png"
+ titles:
+ "de": "Slack"
+ "es": "Slack"
+ "fr": "Slack"
+ "id": "Slack"
+ "it": "Slack"
+ "ko": "Slack"
+ "pt-BR": "Slack"
+ "zh-CN": "Slack"
+ descriptions:
+ "de": "Verbinden Sie eine Slack-App mit einem Ihrer gespeicherten Agenten, damit andere über Slack mit ihm sprechen können."
+ "es": "Vincula una aplicación de Slack a uno de tus agentes guardados para que la gente pueda hablar con él desde Slack."
+ "fr": "Associez une application Slack à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Slack."
+ "id": "Hubungkan aplikasi Slack ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Slack."
+ "it": "Collega un'app Slack a uno dei tuoi agenti salvati, così le persone possono parlarci da Slack."
+ "ko": "저장한 에이전트 중 하나에 Slack 앱을 연결하여 사람들이 Slack에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um app do Slack a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Slack."
+ "zh-CN": "将 Slack 应用绑定到你保存的某个智能体,让大家可以在 Slack 中与它对话。"
- id: "studio"
name: Studio
description: "Make images, video, 3D models, speech, sound and transformed audio with the models you have installed, and keep what you make."
@@ -1256,6 +2012,24 @@
description: "Make images, video, 3D models, speech, sound and transformed audio with the models you have installed, and keep what you make."
mainPage: "studio"
icon: "ui/icon.png"
+ titles:
+ "de": "Studio"
+ "es": "Studio"
+ "fr": "Studio"
+ "id": "Studio"
+ "it": "Studio"
+ "ko": "스튜디오"
+ "pt-BR": "Studio"
+ "zh-CN": "Studio"
+ descriptions:
+ "de": "Erstellen Sie Bilder, Videos, 3D-Modelle, Sprache, Klänge und umgewandeltes Audio mit den installierten Modellen und behalten Sie, was Sie erstellen."
+ "es": "Crea imágenes, video, modelos 3D, voz, sonido y audio transformado con los modelos que tienes instalados, y conserva lo que crees."
+ "fr": "Créez des images, des vidéos, des modèles 3D, de la voix, des sons et de l'audio transformé avec les modèles que vous avez installés, et conservez ce que vous créez."
+ "id": "Buat gambar, video, model 3D, ucapan, bunyi, dan audio hasil transformasi dengan model yang telah Anda pasang, dan simpan hasil karya Anda."
+ "it": "Crea immagini, video, modelli 3D, voce, suoni e audio trasformato con i modelli che hai installato, e conserva ciò che crei."
+ "ko": "설치한 모델로 이미지, 비디오, 3D 모델, 음성, 소리, 변환된 오디오를 만들고 결과물을 보관합니다."
+ "pt-BR": "Crie imagens, vídeo, modelos 3D, fala, som e áudio transformado com os modelos que você instalou, e guarde o que criar."
+ "zh-CN": "用你已安装的模型生成图像、视频、3D 模型、语音、声音和转换后的音频,并保存你的作品。"
- id: "studio"
name: Studio
description: "Make images, video, 3D models, speech, sound and transformed audio with the models you have installed, and keep what you make."
@@ -1274,6 +2048,24 @@
description: "Make images, video, 3D models, speech, sound and transformed audio with the models you have installed, and keep what you make."
mainPage: "studio"
icon: "ui/icon.png"
+ titles:
+ "de": "Studio"
+ "es": "Studio"
+ "fr": "Studio"
+ "id": "Studio"
+ "it": "Studio"
+ "ko": "스튜디오"
+ "pt-BR": "Studio"
+ "zh-CN": "Studio"
+ descriptions:
+ "de": "Erstellen Sie Bilder, Videos, 3D-Modelle, Sprache, Klänge und umgewandeltes Audio mit den installierten Modellen und behalten Sie, was Sie erstellen."
+ "es": "Crea imágenes, video, modelos 3D, voz, sonido y audio transformado con los modelos que tienes instalados, y conserva lo que crees."
+ "fr": "Créez des images, des vidéos, des modèles 3D, de la voix, des sons et de l'audio transformé avec les modèles que vous avez installés, et conservez ce que vous créez."
+ "id": "Buat gambar, video, model 3D, ucapan, bunyi, dan audio hasil transformasi dengan model yang telah Anda pasang, dan simpan hasil karya Anda."
+ "it": "Crea immagini, video, modelli 3D, voce, suoni e audio trasformato con i modelli che hai installato, e conserva ciò che crei."
+ "ko": "설치한 모델로 이미지, 비디오, 3D 모델, 음성, 소리, 변환된 오디오를 만들고 결과물을 보관합니다."
+ "pt-BR": "Crie imagens, vídeo, modelos 3D, fala, som e áudio transformado com os modelos que você instalou, e guarde o que criar."
+ "zh-CN": "用你已安装的模型生成图像、视频、3D 模型、语音、声音和转换后的音频,并保存你的作品。"
- id: "studio"
name: Studio
description: "Make images, video, 3D models, speech, sound and transformed audio with the models you have installed, and keep what you make."
@@ -1292,6 +2084,24 @@
description: "Make images, video, 3D models, speech, sound and transformed audio with the models you have installed, and keep what you make."
mainPage: "studio"
icon: "ui/icon.png"
+ titles:
+ "de": "Studio"
+ "es": "Studio"
+ "fr": "Studio"
+ "id": "Studio"
+ "it": "Studio"
+ "ko": "스튜디오"
+ "pt-BR": "Studio"
+ "zh-CN": "Studio"
+ descriptions:
+ "de": "Erstellen Sie Bilder, Videos, 3D-Modelle, Sprache, Klänge und umgewandeltes Audio mit den installierten Modellen und behalten Sie, was Sie erstellen."
+ "es": "Crea imágenes, video, modelos 3D, voz, sonido y audio transformado con los modelos que tienes instalados, y conserva lo que crees."
+ "fr": "Créez des images, des vidéos, des modèles 3D, de la voix, des sons et de l'audio transformé avec les modèles que vous avez installés, et conservez ce que vous créez."
+ "id": "Buat gambar, video, model 3D, ucapan, bunyi, dan audio hasil transformasi dengan model yang telah Anda pasang, dan simpan hasil karya Anda."
+ "it": "Crea immagini, video, modelli 3D, voce, suoni e audio trasformato con i modelli che hai installato, e conserva ciò che crei."
+ "ko": "설치한 모델로 이미지, 비디오, 3D 모델, 음성, 소리, 변환된 오디오를 만들고 결과물을 보관합니다."
+ "pt-BR": "Crie imagens, vídeo, modelos 3D, fala, som e áudio transformado com os modelos que você instalou, e guarde o que criar."
+ "zh-CN": "用你已安装的模型生成图像、视频、3D 模型、语音、声音和转换后的音频,并保存你的作品。"
- id: "studio"
name: Studio
description: "Make images, video, 3D models, speech, sound and transformed audio with the models you have installed, and keep what you make."
@@ -1310,6 +2120,24 @@
description: "Make images, video, 3D models, speech, sound and transformed audio with the models you have installed, and keep what you make."
mainPage: "studio"
icon: "ui/icon.png"
+ titles:
+ "de": "Studio"
+ "es": "Studio"
+ "fr": "Studio"
+ "id": "Studio"
+ "it": "Studio"
+ "ko": "스튜디오"
+ "pt-BR": "Studio"
+ "zh-CN": "Studio"
+ descriptions:
+ "de": "Erstellen Sie Bilder, Videos, 3D-Modelle, Sprache, Klänge und umgewandeltes Audio mit den installierten Modellen und behalten Sie, was Sie erstellen."
+ "es": "Crea imágenes, video, modelos 3D, voz, sonido y audio transformado con los modelos que tienes instalados, y conserva lo que crees."
+ "fr": "Créez des images, des vidéos, des modèles 3D, de la voix, des sons et de l'audio transformé avec les modèles que vous avez installés, et conservez ce que vous créez."
+ "id": "Buat gambar, video, model 3D, ucapan, bunyi, dan audio hasil transformasi dengan model yang telah Anda pasang, dan simpan hasil karya Anda."
+ "it": "Crea immagini, video, modelli 3D, voce, suoni e audio trasformato con i modelli che hai installato, e conserva ciò che crei."
+ "ko": "설치한 모델로 이미지, 비디오, 3D 모델, 음성, 소리, 변환된 오디오를 만들고 결과물을 보관합니다."
+ "pt-BR": "Crie imagens, vídeo, modelos 3D, fala, som e áudio transformado com os modelos que você instalou, e guarde o que criar."
+ "zh-CN": "用你已安装的模型生成图像、视频、3D 模型、语音、声音和转换后的音频,并保存你的作品。"
- id: "tables"
name: Tables
description: "Databases of tables you own or share: typed rows, filters, schema and sharing."
@@ -1328,6 +2156,24 @@
description: "Databases of tables you own or share: typed rows, filters, schema and sharing."
mainPage: "databases"
icon: "ui/icon.png"
+ titles:
+ "de": "Tabellen"
+ "es": "Tablas"
+ "fr": "Tables"
+ "id": "Tabel"
+ "it": "Tabelle"
+ "ko": "테이블"
+ "pt-BR": "Tabelas"
+ "zh-CN": "数据表"
+ descriptions:
+ "de": "Datenbanken mit Tabellen, die Ihnen gehören oder geteilt wurden: typisierte Zeilen, Filter, Schema und Freigabe."
+ "es": "Bases de datos de tablas propias o compartidas: filas tipadas, filtros, esquema y uso compartido."
+ "fr": "Bases de données de tables que vous possédez ou partagez : lignes typées, filtres, schéma et partage."
+ "id": "Basis data tabel milik Anda atau yang dibagikan: baris bertipe, filter, skema, dan berbagi."
+ "it": "Database di tabelle tue o condivise: righe tipizzate, filtri, schema e condivisione."
+ "ko": "내가 소유하거나 공유받은 테이블 데이터베이스: 형식이 있는 행, 필터, 스키마, 공유."
+ "pt-BR": "Bancos de dados de tabelas próprios ou compartilhados: linhas tipadas, filtros, esquema e compartilhamento."
+ "zh-CN": "你拥有或被共享的数据表数据库:带类型的行、筛选、结构和共享。"
toolCount: 16
tools:
- "resource-exists"
@@ -1364,6 +2210,24 @@
description: "Databases of tables you own or share: typed rows, filters, schema and sharing."
mainPage: "databases"
icon: "ui/icon.png"
+ titles:
+ "de": "Tabellen"
+ "es": "Tablas"
+ "fr": "Tables"
+ "id": "Tabel"
+ "it": "Tabelle"
+ "ko": "테이블"
+ "pt-BR": "Tabelas"
+ "zh-CN": "数据表"
+ descriptions:
+ "de": "Datenbanken mit Tabellen, die Ihnen gehören oder geteilt wurden: typisierte Zeilen, Filter, Schema und Freigabe."
+ "es": "Bases de datos de tablas propias o compartidas: filas tipadas, filtros, esquema y uso compartido."
+ "fr": "Bases de données de tables que vous possédez ou partagez : lignes typées, filtres, schéma et partage."
+ "id": "Basis data tabel milik Anda atau yang dibagikan: baris bertipe, filter, skema, dan berbagi."
+ "it": "Database di tabelle tue o condivise: righe tipizzate, filtri, schema e condivisione."
+ "ko": "내가 소유하거나 공유받은 테이블 데이터베이스: 형식이 있는 행, 필터, 스키마, 공유."
+ "pt-BR": "Bancos de dados de tabelas próprios ou compartilhados: linhas tipadas, filtros, esquema e compartilhamento."
+ "zh-CN": "你拥有或被共享的数据表数据库:带类型的行、筛选、结构和共享。"
toolCount: 16
tools:
- "resource-exists"
@@ -1400,6 +2264,24 @@
description: "Databases of tables you own or share: typed rows, filters, schema and sharing."
mainPage: "databases"
icon: "ui/icon.png"
+ titles:
+ "de": "Tabellen"
+ "es": "Tablas"
+ "fr": "Tables"
+ "id": "Tabel"
+ "it": "Tabelle"
+ "ko": "테이블"
+ "pt-BR": "Tabelas"
+ "zh-CN": "数据表"
+ descriptions:
+ "de": "Datenbanken mit Tabellen, die Ihnen gehören oder geteilt wurden: typisierte Zeilen, Filter, Schema und Freigabe."
+ "es": "Bases de datos de tablas propias o compartidas: filas tipadas, filtros, esquema y uso compartido."
+ "fr": "Bases de données de tables que vous possédez ou partagez : lignes typées, filtres, schéma et partage."
+ "id": "Basis data tabel milik Anda atau yang dibagikan: baris bertipe, filter, skema, dan berbagi."
+ "it": "Database di tabelle tue o condivise: righe tipizzate, filtri, schema e condivisione."
+ "ko": "내가 소유하거나 공유받은 테이블 데이터베이스: 형식이 있는 행, 필터, 스키마, 공유."
+ "pt-BR": "Bancos de dados de tabelas próprios ou compartilhados: linhas tipadas, filtros, esquema e compartilhamento."
+ "zh-CN": "你拥有或被共享的数据表数据库:带类型的行、筛选、结构和共享。"
toolCount: 16
tools:
- "resource-exists"
@@ -1436,6 +2318,24 @@
description: "Databases of tables you own or share: typed rows, filters, schema and sharing."
mainPage: "databases"
icon: "ui/icon.png"
+ titles:
+ "de": "Tabellen"
+ "es": "Tablas"
+ "fr": "Tables"
+ "id": "Tabel"
+ "it": "Tabelle"
+ "ko": "테이블"
+ "pt-BR": "Tabelas"
+ "zh-CN": "数据表"
+ descriptions:
+ "de": "Datenbanken mit Tabellen, die Ihnen gehören oder geteilt wurden: typisierte Zeilen, Filter, Schema und Freigabe."
+ "es": "Bases de datos de tablas propias o compartidas: filas tipadas, filtros, esquema y uso compartido."
+ "fr": "Bases de données de tables que vous possédez ou partagez : lignes typées, filtres, schéma et partage."
+ "id": "Basis data tabel milik Anda atau yang dibagikan: baris bertipe, filter, skema, dan berbagi."
+ "it": "Database di tabelle tue o condivise: righe tipizzate, filtri, schema e condivisione."
+ "ko": "내가 소유하거나 공유받은 테이블 데이터베이스: 형식이 있는 행, 필터, 스키마, 공유."
+ "pt-BR": "Bancos de dados de tabelas próprios ou compartilhados: linhas tipadas, filtros, esquema e compartilhamento."
+ "zh-CN": "你拥有或被共享的数据表数据库:带类型的行、筛选、结构和共享。"
toolCount: 16
tools:
- "resource-exists"
@@ -1472,6 +2372,24 @@
description: "Have a spoken conversation with a model: your voice goes in, the answer is spoken back."
mainPage: "talk"
icon: "ui/icon.png"
+ titles:
+ "de": "Sprachchat"
+ "es": "Conversación de voz"
+ "fr": "Vocal"
+ "id": "Obrolan suara"
+ "it": "Conversazione vocale"
+ "ko": "음성 대화"
+ "pt-BR": "Conversa por voz"
+ "zh-CN": "语音对话"
+ descriptions:
+ "de": "Führen Sie ein gesprochenes Gespräch mit einem Modell: Ihre Stimme geht hinein, die Antwort wird gesprochen."
+ "es": "Mantén una conversación hablada con un modelo: tu voz entra y la respuesta se pronuncia en voz alta."
+ "fr": "Dialoguez à voix haute avec un modèle : votre voix entre, la réponse est prononcée."
+ "id": "Lakukan percakapan lisan dengan model: suara Anda masuk, jawabannya diucapkan kembali."
+ "it": "Conversa a voce con un modello: la tua voce entra, la risposta viene pronunciata."
+ "ko": "모델과 음성으로 대화합니다. 내 목소리가 입력되고 답변은 음성으로 들려줍니다."
+ "pt-BR": "Tenha uma conversa falada com um modelo: sua voz entra e a resposta é falada de volta."
+ "zh-CN": "与模型进行语音对话:你说话输入,回答以语音读出。"
- id: "talk"
name: Talk
description: "Have a spoken conversation with a model: your voice goes in, the answer is spoken back."
@@ -1490,6 +2408,24 @@
description: "Have a spoken conversation with a model: your voice goes in, the answer is spoken back."
mainPage: "talk"
icon: "ui/icon.png"
+ titles:
+ "de": "Sprachchat"
+ "es": "Conversación de voz"
+ "fr": "Vocal"
+ "id": "Obrolan suara"
+ "it": "Conversazione vocale"
+ "ko": "음성 대화"
+ "pt-BR": "Conversa por voz"
+ "zh-CN": "语音对话"
+ descriptions:
+ "de": "Führen Sie ein gesprochenes Gespräch mit einem Modell: Ihre Stimme geht hinein, die Antwort wird gesprochen."
+ "es": "Mantén una conversación hablada con un modelo: tu voz entra y la respuesta se pronuncia en voz alta."
+ "fr": "Dialoguez à voix haute avec un modèle : votre voix entre, la réponse est prononcée."
+ "id": "Lakukan percakapan lisan dengan model: suara Anda masuk, jawabannya diucapkan kembali."
+ "it": "Conversa a voce con un modello: la tua voce entra, la risposta viene pronunciata."
+ "ko": "모델과 음성으로 대화합니다. 내 목소리가 입력되고 답변은 음성으로 들려줍니다."
+ "pt-BR": "Tenha uma conversa falada com um modelo: sua voz entra e a resposta é falada de volta."
+ "zh-CN": "与模型进行语音对话:你说话输入,回答以语音读出。"
- id: "talk"
name: Talk
description: "Have a spoken conversation with a model: your voice goes in, the answer is spoken back."
@@ -1508,6 +2444,24 @@
description: "Have a spoken conversation with a model: your voice goes in, the answer is spoken back."
mainPage: "talk"
icon: "ui/icon.png"
+ titles:
+ "de": "Sprachchat"
+ "es": "Conversación de voz"
+ "fr": "Vocal"
+ "id": "Obrolan suara"
+ "it": "Conversazione vocale"
+ "ko": "음성 대화"
+ "pt-BR": "Conversa por voz"
+ "zh-CN": "语音对话"
+ descriptions:
+ "de": "Führen Sie ein gesprochenes Gespräch mit einem Modell: Ihre Stimme geht hinein, die Antwort wird gesprochen."
+ "es": "Mantén una conversación hablada con un modelo: tu voz entra y la respuesta se pronuncia en voz alta."
+ "fr": "Dialoguez à voix haute avec un modèle : votre voix entre, la réponse est prononcée."
+ "id": "Lakukan percakapan lisan dengan model: suara Anda masuk, jawabannya diucapkan kembali."
+ "it": "Conversa a voce con un modello: la tua voce entra, la risposta viene pronunciata."
+ "ko": "모델과 음성으로 대화합니다. 내 목소리가 입력되고 답변은 음성으로 들려줍니다."
+ "pt-BR": "Tenha uma conversa falada com um modelo: sua voz entra e a resposta é falada de volta."
+ "zh-CN": "与模型进行语音对话:你说话输入,回答以语音读出。"
- id: "talk"
name: Talk
description: "Have a spoken conversation with a model: your voice goes in, the answer is spoken back."
@@ -1526,6 +2480,24 @@
description: "Have a spoken conversation with a model: your voice goes in, the answer is spoken back."
mainPage: "talk"
icon: "ui/icon.png"
+ titles:
+ "de": "Sprachchat"
+ "es": "Conversación de voz"
+ "fr": "Vocal"
+ "id": "Obrolan suara"
+ "it": "Conversazione vocale"
+ "ko": "음성 대화"
+ "pt-BR": "Conversa por voz"
+ "zh-CN": "语音对话"
+ descriptions:
+ "de": "Führen Sie ein gesprochenes Gespräch mit einem Modell: Ihre Stimme geht hinein, die Antwort wird gesprochen."
+ "es": "Mantén una conversación hablada con un modelo: tu voz entra y la respuesta se pronuncia en voz alta."
+ "fr": "Dialoguez à voix haute avec un modèle : votre voix entre, la réponse est prononcée."
+ "id": "Lakukan percakapan lisan dengan model: suara Anda masuk, jawabannya diucapkan kembali."
+ "it": "Conversa a voce con un modello: la tua voce entra, la risposta viene pronunciata."
+ "ko": "모델과 음성으로 대화합니다. 내 목소리가 입력되고 답변은 음성으로 들려줍니다."
+ "pt-BR": "Tenha uma conversa falada com um modelo: sua voz entra e a resposta é falada de volta."
+ "zh-CN": "与模型进行语音对话:你说话输入,回答以语音读出。"
- id: "telegram"
name: Telegram
description: "Bind a Telegram bot to one of your saved agents, so people can talk to it from Telegram."
@@ -1544,6 +2516,24 @@
description: "Bind a Telegram bot to one of your saved agents, so people can talk to it from Telegram."
mainPage: "bots"
icon: "ui/icon.png"
+ titles:
+ "de": "Telegram"
+ "es": "Telegram"
+ "fr": "Telegram"
+ "id": "Telegram"
+ "it": "Telegram"
+ "ko": "Telegram"
+ "pt-BR": "Telegram"
+ "zh-CN": "Telegram"
+ descriptions:
+ "de": "Verbinden Sie einen Telegram-Bot mit einem Ihrer gespeicherten Agenten, damit andere über Telegram mit ihm sprechen können."
+ "es": "Vincula un bot de Telegram a uno de tus agentes guardados para que la gente pueda hablar con él desde Telegram."
+ "fr": "Associez un bot Telegram à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Telegram."
+ "id": "Hubungkan bot Telegram ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Telegram."
+ "it": "Collega un bot Telegram a uno dei tuoi agenti salvati, così le persone possono parlarci da Telegram."
+ "ko": "저장한 에이전트 중 하나에 Telegram 봇을 연결하여 사람들이 Telegram에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um bot do Telegram a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Telegram."
+ "zh-CN": "将 Telegram 机器人绑定到你保存的某个智能体,让大家可以在 Telegram 中与它对话。"
- id: "telegram"
name: Telegram
description: "Bind a Telegram bot to one of your saved agents, so people can talk to it from Telegram."
@@ -1562,6 +2552,24 @@
description: "Bind a Telegram bot to one of your saved agents, so people can talk to it from Telegram."
mainPage: "bots"
icon: "ui/icon.png"
+ titles:
+ "de": "Telegram"
+ "es": "Telegram"
+ "fr": "Telegram"
+ "id": "Telegram"
+ "it": "Telegram"
+ "ko": "Telegram"
+ "pt-BR": "Telegram"
+ "zh-CN": "Telegram"
+ descriptions:
+ "de": "Verbinden Sie einen Telegram-Bot mit einem Ihrer gespeicherten Agenten, damit andere über Telegram mit ihm sprechen können."
+ "es": "Vincula un bot de Telegram a uno de tus agentes guardados para que la gente pueda hablar con él desde Telegram."
+ "fr": "Associez un bot Telegram à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Telegram."
+ "id": "Hubungkan bot Telegram ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Telegram."
+ "it": "Collega un bot Telegram a uno dei tuoi agenti salvati, così le persone possono parlarci da Telegram."
+ "ko": "저장한 에이전트 중 하나에 Telegram 봇을 연결하여 사람들이 Telegram에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um bot do Telegram a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Telegram."
+ "zh-CN": "将 Telegram 机器人绑定到你保存的某个智能体,让大家可以在 Telegram 中与它对话。"
- id: "telegram"
name: Telegram
description: "Bind a Telegram bot to one of your saved agents, so people can talk to it from Telegram."
@@ -1580,6 +2588,24 @@
description: "Bind a Telegram bot to one of your saved agents, so people can talk to it from Telegram."
mainPage: "bots"
icon: "ui/icon.png"
+ titles:
+ "de": "Telegram"
+ "es": "Telegram"
+ "fr": "Telegram"
+ "id": "Telegram"
+ "it": "Telegram"
+ "ko": "Telegram"
+ "pt-BR": "Telegram"
+ "zh-CN": "Telegram"
+ descriptions:
+ "de": "Verbinden Sie einen Telegram-Bot mit einem Ihrer gespeicherten Agenten, damit andere über Telegram mit ihm sprechen können."
+ "es": "Vincula un bot de Telegram a uno de tus agentes guardados para que la gente pueda hablar con él desde Telegram."
+ "fr": "Associez un bot Telegram à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Telegram."
+ "id": "Hubungkan bot Telegram ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Telegram."
+ "it": "Collega un bot Telegram a uno dei tuoi agenti salvati, così le persone possono parlarci da Telegram."
+ "ko": "저장한 에이전트 중 하나에 Telegram 봇을 연결하여 사람들이 Telegram에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um bot do Telegram a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Telegram."
+ "zh-CN": "将 Telegram 机器人绑定到你保存的某个智能体,让大家可以在 Telegram 中与它对话。"
- id: "telegram"
name: Telegram
description: "Bind a Telegram bot to one of your saved agents, so people can talk to it from Telegram."
@@ -1598,6 +2624,24 @@
description: "Bind a Telegram bot to one of your saved agents, so people can talk to it from Telegram."
mainPage: "bots"
icon: "ui/icon.png"
+ titles:
+ "de": "Telegram"
+ "es": "Telegram"
+ "fr": "Telegram"
+ "id": "Telegram"
+ "it": "Telegram"
+ "ko": "Telegram"
+ "pt-BR": "Telegram"
+ "zh-CN": "Telegram"
+ descriptions:
+ "de": "Verbinden Sie einen Telegram-Bot mit einem Ihrer gespeicherten Agenten, damit andere über Telegram mit ihm sprechen können."
+ "es": "Vincula un bot de Telegram a uno de tus agentes guardados para que la gente pueda hablar con él desde Telegram."
+ "fr": "Associez un bot Telegram à l'un de vos agents enregistrés pour que les gens puissent lui parler depuis Telegram."
+ "id": "Hubungkan bot Telegram ke salah satu agen tersimpan Anda agar orang dapat mengobrol dengannya dari Telegram."
+ "it": "Collega un bot Telegram a uno dei tuoi agenti salvati, così le persone possono parlarci da Telegram."
+ "ko": "저장한 에이전트 중 하나에 Telegram 봇을 연결하여 사람들이 Telegram에서 대화할 수 있게 합니다."
+ "pt-BR": "Vincule um bot do Telegram a um dos seus agentes salvos para que as pessoas possam conversar com ele pelo Telegram."
+ "zh-CN": "将 Telegram 机器人绑定到你保存的某个智能体,让大家可以在 Telegram 中与它对话。"
- id: "voice"
name: Voice
description: "Recognise who is speaking, keep the voices that text-to-speech can use, and speak text with them."
@@ -1616,6 +2660,24 @@
description: "Recognise who is speaking, keep the voices that text-to-speech can use, and speak text with them."
mainPage: "voice"
icon: "ui/icon.png"
+ titles:
+ "de": "Stimme"
+ "es": "Voz"
+ "fr": "Voix"
+ "id": "Suara"
+ "it": "Voce"
+ "ko": "음성"
+ "pt-BR": "Voz"
+ "zh-CN": "声音"
+ descriptions:
+ "de": "Erkennen Sie, wer spricht, verwalten Sie die Stimmen, die die Sprachausgabe verwenden kann, und sprechen Sie Text mit ihnen."
+ "es": "Reconoce quién está hablando, guarda las voces que puede usar la síntesis de voz y di textos con ellas."
+ "fr": "Reconnaissez qui parle, gardez les voix que la synthèse vocale peut utiliser et prononcez du texte avec elles."
+ "id": "Kenali siapa yang berbicara, simpan suara yang dapat digunakan text-to-speech, dan ucapkan teks dengan suara tersebut."
+ "it": "Riconosci chi sta parlando, conserva le voci che la sintesi vocale può usare e leggi testi con quelle voci."
+ "ko": "말하는 사람을 인식하고, 음성 합성에서 사용할 수 있는 목소리를 보관하며, 그 목소리로 텍스트를 읽습니다."
+ "pt-BR": "Reconheça quem está falando, guarde as vozes que a síntese de fala pode usar e fale textos com elas."
+ "zh-CN": "识别说话的人,管理语音合成可用的声音,并用这些声音朗读文本。"
- id: "voice"
name: Voice
description: "Recognise who is speaking, keep the voices that text-to-speech can use, and speak text with them."
@@ -1634,6 +2696,24 @@
description: "Recognise who is speaking, keep the voices that text-to-speech can use, and speak text with them."
mainPage: "voice"
icon: "ui/icon.png"
+ titles:
+ "de": "Stimme"
+ "es": "Voz"
+ "fr": "Voix"
+ "id": "Suara"
+ "it": "Voce"
+ "ko": "음성"
+ "pt-BR": "Voz"
+ "zh-CN": "声音"
+ descriptions:
+ "de": "Erkennen Sie, wer spricht, verwalten Sie die Stimmen, die die Sprachausgabe verwenden kann, und sprechen Sie Text mit ihnen."
+ "es": "Reconoce quién está hablando, guarda las voces que puede usar la síntesis de voz y di textos con ellas."
+ "fr": "Reconnaissez qui parle, gardez les voix que la synthèse vocale peut utiliser et prononcez du texte avec elles."
+ "id": "Kenali siapa yang berbicara, simpan suara yang dapat digunakan text-to-speech, dan ucapkan teks dengan suara tersebut."
+ "it": "Riconosci chi sta parlando, conserva le voci che la sintesi vocale può usare e leggi testi con quelle voci."
+ "ko": "말하는 사람을 인식하고, 음성 합성에서 사용할 수 있는 목소리를 보관하며, 그 목소리로 텍스트를 읽습니다."
+ "pt-BR": "Reconheça quem está falando, guarde as vozes que a síntese de fala pode usar e fale textos com elas."
+ "zh-CN": "识别说话的人,管理语音合成可用的声音,并用这些声音朗读文本。"
- id: "voice"
name: Voice
description: "Recognise who is speaking, keep the voices that text-to-speech can use, and speak text with them."
@@ -1652,6 +2732,24 @@
description: "Recognise who is speaking, keep the voices that text-to-speech can use, and speak text with them."
mainPage: "voice"
icon: "ui/icon.png"
+ titles:
+ "de": "Stimme"
+ "es": "Voz"
+ "fr": "Voix"
+ "id": "Suara"
+ "it": "Voce"
+ "ko": "음성"
+ "pt-BR": "Voz"
+ "zh-CN": "声音"
+ descriptions:
+ "de": "Erkennen Sie, wer spricht, verwalten Sie die Stimmen, die die Sprachausgabe verwenden kann, und sprechen Sie Text mit ihnen."
+ "es": "Reconoce quién está hablando, guarda las voces que puede usar la síntesis de voz y di textos con ellas."
+ "fr": "Reconnaissez qui parle, gardez les voix que la synthèse vocale peut utiliser et prononcez du texte avec elles."
+ "id": "Kenali siapa yang berbicara, simpan suara yang dapat digunakan text-to-speech, dan ucapkan teks dengan suara tersebut."
+ "it": "Riconosci chi sta parlando, conserva le voci che la sintesi vocale può usare e leggi testi con quelle voci."
+ "ko": "말하는 사람을 인식하고, 음성 합성에서 사용할 수 있는 목소리를 보관하며, 그 목소리로 텍스트를 읽습니다."
+ "pt-BR": "Reconheça quem está falando, guarde as vozes que a síntese de fala pode usar e fale textos com elas."
+ "zh-CN": "识别说话的人,管理语音合成可用的声音,并用这些声音朗读文本。"
- id: "voice"
name: Voice
description: "Recognise who is speaking, keep the voices that text-to-speech can use, and speak text with them."
@@ -1670,6 +2768,24 @@
description: "Recognise who is speaking, keep the voices that text-to-speech can use, and speak text with them."
mainPage: "voice"
icon: "ui/icon.png"
+ titles:
+ "de": "Stimme"
+ "es": "Voz"
+ "fr": "Voix"
+ "id": "Suara"
+ "it": "Voce"
+ "ko": "음성"
+ "pt-BR": "Voz"
+ "zh-CN": "声音"
+ descriptions:
+ "de": "Erkennen Sie, wer spricht, verwalten Sie die Stimmen, die die Sprachausgabe verwenden kann, und sprechen Sie Text mit ihnen."
+ "es": "Reconoce quién está hablando, guarda las voces que puede usar la síntesis de voz y di textos con ellas."
+ "fr": "Reconnaissez qui parle, gardez les voix que la synthèse vocale peut utiliser et prononcez du texte avec elles."
+ "id": "Kenali siapa yang berbicara, simpan suara yang dapat digunakan text-to-speech, dan ucapkan teks dengan suara tersebut."
+ "it": "Riconosci chi sta parlando, conserva le voci che la sintesi vocale può usare e leggi testi con quelle voci."
+ "ko": "말하는 사람을 인식하고, 음성 합성에서 사용할 수 있는 목소리를 보관하며, 그 목소리로 텍스트를 읽습니다."
+ "pt-BR": "Reconheça quem está falando, guarde as vozes que a síntese de fala pode usar e fale textos com elas."
+ "zh-CN": "识别说话的人,管理语音合成可用的声音,并用这些声音朗读文本。"
- id: "web"
name: Web
description: "Search the web from your chats with Brave Search, Tavily or Google, and read PDF files on the web."
@@ -1688,6 +2804,24 @@
description: "Search the web from your chats with Brave Search, Tavily or Google, and read PDF files on the web."
mainPage: "web"
icon: "ui/icon.png"
+ titles:
+ "de": "Web"
+ "es": "Web"
+ "fr": "Web"
+ "id": "Web"
+ "it": "Web"
+ "ko": "웹"
+ "pt-BR": "Web"
+ "zh-CN": "网页"
+ descriptions:
+ "de": "Durchsuchen Sie das Web aus Ihren Chats mit Brave Search, Tavily oder Google und lesen Sie PDF-Dateien im Web."
+ "es": "Busca en la web desde tus chats con Brave Search, Tavily o Google, y lee archivos PDF de la web."
+ "fr": "Cherchez sur le web depuis vos conversations avec Brave Search, Tavily ou Google, et lisez des fichiers PDF sur le web."
+ "id": "Cari di web dari obrolan Anda dengan Brave Search, Tavily, atau Google, dan baca berkas PDF di web."
+ "it": "Cerca sul web dalle tue chat con Brave Search, Tavily o Google, e leggi i file PDF sul web."
+ "ko": "채팅에서 Brave Search, Tavily 또는 Google로 웹을 검색하고 웹의 PDF 파일을 읽습니다."
+ "pt-BR": "Pesquise na web a partir dos seus chats com Brave Search, Tavily ou Google, e leia arquivos PDF na web."
+ "zh-CN": "在聊天中用 Brave Search、Tavily 或 Google 搜索网页,并读取网络上的 PDF 文件。"
toolCount: 5
tools:
- "search"
@@ -1716,6 +2850,24 @@
description: "Search the web from your chats with Brave Search, Tavily or Google, and read PDF files on the web."
mainPage: "web"
icon: "ui/icon.png"
+ titles:
+ "de": "Web"
+ "es": "Web"
+ "fr": "Web"
+ "id": "Web"
+ "it": "Web"
+ "ko": "웹"
+ "pt-BR": "Web"
+ "zh-CN": "网页"
+ descriptions:
+ "de": "Durchsuchen Sie das Web aus Ihren Chats mit Brave Search, Tavily oder Google und lesen Sie PDF-Dateien im Web."
+ "es": "Busca en la web desde tus chats con Brave Search, Tavily o Google, y lee archivos PDF de la web."
+ "fr": "Cherchez sur le web depuis vos conversations avec Brave Search, Tavily ou Google, et lisez des fichiers PDF sur le web."
+ "id": "Cari di web dari obrolan Anda dengan Brave Search, Tavily, atau Google, dan baca berkas PDF di web."
+ "it": "Cerca sul web dalle tue chat con Brave Search, Tavily o Google, e leggi i file PDF sul web."
+ "ko": "채팅에서 Brave Search, Tavily 또는 Google로 웹을 검색하고 웹의 PDF 파일을 읽습니다."
+ "pt-BR": "Pesquise na web a partir dos seus chats com Brave Search, Tavily ou Google, e leia arquivos PDF na web."
+ "zh-CN": "在聊天中用 Brave Search、Tavily 或 Google 搜索网页,并读取网络上的 PDF 文件。"
toolCount: 5
tools:
- "search"
@@ -1744,6 +2896,24 @@
description: "Search the web from your chats with Brave Search, Tavily or Google, and read PDF files on the web."
mainPage: "web"
icon: "ui/icon.png"
+ titles:
+ "de": "Web"
+ "es": "Web"
+ "fr": "Web"
+ "id": "Web"
+ "it": "Web"
+ "ko": "웹"
+ "pt-BR": "Web"
+ "zh-CN": "网页"
+ descriptions:
+ "de": "Durchsuchen Sie das Web aus Ihren Chats mit Brave Search, Tavily oder Google und lesen Sie PDF-Dateien im Web."
+ "es": "Busca en la web desde tus chats con Brave Search, Tavily o Google, y lee archivos PDF de la web."
+ "fr": "Cherchez sur le web depuis vos conversations avec Brave Search, Tavily ou Google, et lisez des fichiers PDF sur le web."
+ "id": "Cari di web dari obrolan Anda dengan Brave Search, Tavily, atau Google, dan baca berkas PDF di web."
+ "it": "Cerca sul web dalle tue chat con Brave Search, Tavily o Google, e leggi i file PDF sul web."
+ "ko": "채팅에서 Brave Search, Tavily 또는 Google로 웹을 검색하고 웹의 PDF 파일을 읽습니다."
+ "pt-BR": "Pesquise na web a partir dos seus chats com Brave Search, Tavily ou Google, e leia arquivos PDF na web."
+ "zh-CN": "在聊天中用 Brave Search、Tavily 或 Google 搜索网页,并读取网络上的 PDF 文件。"
toolCount: 5
tools:
- "search"
@@ -1772,6 +2942,24 @@
description: "Search the web from your chats with Brave Search, Tavily or Google, and read PDF files on the web."
mainPage: "web"
icon: "ui/icon.png"
+ titles:
+ "de": "Web"
+ "es": "Web"
+ "fr": "Web"
+ "id": "Web"
+ "it": "Web"
+ "ko": "웹"
+ "pt-BR": "Web"
+ "zh-CN": "网页"
+ descriptions:
+ "de": "Durchsuchen Sie das Web aus Ihren Chats mit Brave Search, Tavily oder Google und lesen Sie PDF-Dateien im Web."
+ "es": "Busca en la web desde tus chats con Brave Search, Tavily o Google, y lee archivos PDF de la web."
+ "fr": "Cherchez sur le web depuis vos conversations avec Brave Search, Tavily ou Google, et lisez des fichiers PDF sur le web."
+ "id": "Cari di web dari obrolan Anda dengan Brave Search, Tavily, atau Google, dan baca berkas PDF di web."
+ "it": "Cerca sul web dalle tue chat con Brave Search, Tavily o Google, e leggi i file PDF sul web."
+ "ko": "채팅에서 Brave Search, Tavily 또는 Google로 웹을 검색하고 웹의 PDF 파일을 읽습니다."
+ "pt-BR": "Pesquise na web a partir dos seus chats com Brave Search, Tavily ou Google, e leia arquivos PDF na web."
+ "zh-CN": "在聊天中用 Brave Search、Tavily 或 Google 搜索网页,并读取网络上的 PDF 文件。"
toolCount: 5
tools:
- "search"
diff --git a/pkg/addons/contract/README.md b/pkg/addons/contract/README.md
index 70fed3e3cf..d436c8a26b 100644
--- a/pkg/addons/contract/README.md
+++ b/pkg/addons/contract/README.md
@@ -19,6 +19,21 @@ for JSON examples, bounds, collision rules, lifecycle behavior, and policy migra
Typed owner observations currently support local-owner JSON selections only.
Named-user and SQL selections remain unavailable and fail closed; they are not completed scope.
+## Optional translations
+
+The title and the description of an add-on are translated by the language maps of `ui.title` and `ui.description`.
+The top-level `i18n` field translates the text of connector kinds, by language tag:
+
+```json
+{"i18n":{"it":{"kinds":{"slack":{"title":"Slack","description":"Parla con uno dei tuoi agenti da Slack."}}}}}
+```
+
+The set of fields is closed: only the title and the description of a declared kind, and only where the English kind has them.
+Tool descriptions, schemas and skills are for the model and stay English. English is the manifest itself, so `en` is refused.
+Tags follow the same rule as the language maps, at most 16, and all translated text has at most `MaxI18nChars` characters.
+`NegotiateLanguage` picks the language of a request: the explicit one, else the `Accept-Language` header, else English.
+See [the addon guide](../../../docs/content/features/addons.md#translated-text).
+
## Optional model actions
`capabilities.ui.modelActions` maps a model capability to a declared page:
diff --git a/pkg/addons/contract/i18n.go b/pkg/addons/contract/i18n.go
new file mode 100644
index 0000000000..f560259572
--- /dev/null
+++ b/pkg/addons/contract/i18n.go
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: MIT
+package contract
+
+import (
+ "fmt"
+ "slices"
+ "sort"
+ "strconv"
+ "strings"
+ "unicode/utf8"
+)
+
+// MaxI18nChars bounds the characters of all translated text of one manifest.
+// The manifest as a whole is bound by Limits.ManifestBytes; this keeps the
+// translations a small part of it.
+const MaxI18nChars = 12000
+
+// maxAcceptLanguageBytes bounds the header that NegotiateLanguage reads.
+const maxAcceptLanguageBytes = 1024
+
+// Translation is the text of one language for the parts of a manifest that
+// the ui.title and ui.description language maps do not reach. The English
+// fields stay the source of truth and the fallback: a translation holds no
+// field that the English manifest lacks, and a field it leaves out is shown
+// in English.
+//
+// The set of fields is closed on purpose. Only text that the host draws for a
+// person is translatable. Tool descriptions, input schemas and skills are
+// written for a model and stay English, and an unknown field fails the strict
+// parser, as it does on a host that does not know this field.
+type Translation struct {
+ // Kinds translates connector kinds, by kind ID.
+ Kinds map[string]KindText `json:"kinds,omitempty"`
+}
+
+// KindText is the translated text of one connector kind. A field left out is
+// shown in English.
+type KindText struct {
+ Title string `json:"title,omitempty"`
+ Description string `json:"description,omitempty"`
+}
+
+// validateI18n checks the translations against the manifest.
+func validateI18n(m Manifest) error {
+ if len(m.I18n) == 0 {
+ if m.I18n != nil {
+ return fmt.Errorf("i18n has no language")
+ }
+ return nil
+ }
+ if len(m.I18n) > MaxLanguages {
+ return fmt.Errorf("i18n has more than %d languages", MaxLanguages)
+ }
+ kinds := map[string]ConnectorKind{}
+ if m.Capabilities.Connector != nil {
+ for _, k := range m.Capabilities.Connector.Kinds {
+ kinds[k.ID] = k
+ }
+ }
+ seen := map[string]bool{}
+ total := 0
+ for tag, t := range m.I18n {
+ key := strings.ToLower(tag)
+ if len(tag) > 35 || !languageTag.MatchString(tag) || seen[key] {
+ return fmt.Errorf("i18n language %q is not a distinct language tag", tag)
+ }
+ if key == "en" || strings.HasPrefix(key, "en-") {
+ return fmt.Errorf("i18n language %q: English is the manifest itself", tag)
+ }
+ seen[key] = true
+ if len(t.Kinds) == 0 {
+ return fmt.Errorf("i18n language %q translates nothing", tag)
+ }
+ for id, text := range t.Kinds {
+ k, ok := kinds[id]
+ if !ok {
+ return fmt.Errorf("i18n %q translates the connector kind %q that the manifest does not declare", tag, id)
+ }
+ if text == (KindText{}) {
+ return fmt.Errorf("i18n %q kind %q has no text", tag, id)
+ }
+ if text.Title != "" && !plainText(text.Title, MaxTitleChars) {
+ return fmt.Errorf("i18n %q kind %q: invalid title", tag, id)
+ }
+ if text.Description != "" && (k.Description == "" || !plainText(text.Description, MaxDescriptionChars)) {
+ return fmt.Errorf("i18n %q kind %q: invalid description", tag, id)
+ }
+ total += utf8.RuneCountInString(text.Title) + utf8.RuneCountInString(text.Description)
+ }
+ }
+ if total > MaxI18nChars {
+ return fmt.Errorf("i18n text exceeds %d characters", MaxI18nChars)
+ }
+ return nil
+}
+
+// Languages returns the language tags of the map other than English, sorted.
+func (l Localized) Languages() []string {
+ var out []string
+ for tag := range l.ByLanguage {
+ if !isEnglish(tag) {
+ out = append(out, tag)
+ }
+ }
+ sort.Strings(out)
+ return out
+}
+
+// For returns the text for a language tag: the plain string, the entry the
+// tag matches (see MatchLanguage), else Default. An empty tag is English.
+func (l Localized) For(tag string) string {
+ if l.ByLanguage != nil && tag != "" {
+ if got, ok := MatchLanguage(tag, l.Languages()); ok {
+ return l.ByLanguage[got]
+ }
+ }
+ return l.Default()
+}
+
+// KindLanguages returns the languages in which the manifest translates at
+// least part of a connector kind, sorted.
+func (m Manifest) KindLanguages() []string {
+ out := make([]string, 0, len(m.I18n))
+ for tag := range m.I18n {
+ out = append(out, tag)
+ }
+ sort.Strings(out)
+ return out
+}
+
+// KindTranslations returns the translations of one connector kind, by
+// language tag. It is nil when no language translates the kind.
+func (m Manifest) KindTranslations(kindID string) map[string]KindText {
+ var out map[string]KindText
+ for tag, t := range m.I18n {
+ if text, ok := t.Kinds[kindID]; ok {
+ if out == nil {
+ out = map[string]KindText{}
+ }
+ out[tag] = text
+ }
+ }
+ return out
+}
+
+// KindText returns the translated title and description of a connector kind
+// for a language tag. A field that the translation lacks is the English text.
+func (m Manifest) KindText(kindID, tag string) (title, description string) {
+ if m.Capabilities.Connector == nil {
+ return "", ""
+ }
+ for _, k := range m.Capabilities.Connector.Kinds {
+ if k.ID != kindID {
+ continue
+ }
+ title, description = k.Title, k.Description
+ if got, ok := MatchLanguage(tag, m.KindLanguages()); ok {
+ if t := m.I18n[got].Kinds[kindID]; t != (KindText{}) {
+ if t.Title != "" {
+ title = t.Title
+ }
+ if t.Description != "" {
+ description = t.Description
+ }
+ }
+ }
+ return title, description
+ }
+ return "", ""
+}
+
+// ValidLanguageTag reports whether s is a language tag a manifest may use: a
+// BCP 47 subset of a two or three letter language and up to three subtags.
+func ValidLanguageTag(s string) bool { return len(s) <= 35 && languageTag.MatchString(s) }
+
+func isEnglish(tag string) bool {
+ t := strings.ToLower(tag)
+ return t == "en" || strings.HasPrefix(t, "en-")
+}
+
+// MatchLanguage finds the available tag that serves a requested tag: the same
+// tag, else the tag with its trailing subtags removed, else the first
+// available tag (in sorted order) with the same primary language. Case does
+// not matter; the available tag is returned as written. English and an empty
+// request match nothing, because English is the manifest's own text.
+func MatchLanguage(request string, available []string) (string, bool) {
+ if request == "" || isEnglish(request) || !languageTag.MatchString(request) || len(request) > 35 {
+ return "", false
+ }
+ sorted := slices.Clone(available)
+ sort.Strings(sorted)
+ want := strings.ToLower(request)
+ for {
+ for _, a := range sorted {
+ if strings.ToLower(a) == want {
+ return a, true
+ }
+ }
+ i := strings.LastIndex(want, "-")
+ if i < 0 {
+ break
+ }
+ want = want[:i]
+ }
+ for _, a := range sorted {
+ if strings.ToLower(strings.SplitN(a, "-", 2)[0]) == want {
+ return a, true
+ }
+ }
+ return "", false
+}
+
+// NegotiateLanguage picks the available tag for a request. The explicit
+// language (a lang query parameter) wins; without one the Accept-Language
+// header is read in order of its quality values; without either the answer is
+// English. English is the empty answer: a caller then keeps the English
+// fields. An explicit language that nothing matches is English too; the
+// header is not consulted behind it. A malformed or oversized input is
+// English.
+func NegotiateLanguage(explicit, acceptLanguage string, available []string) string {
+ if len(available) == 0 {
+ return ""
+ }
+ if explicit != "" {
+ got, _ := MatchLanguage(explicit, available)
+ return got
+ }
+ if acceptLanguage == "" || len(acceptLanguage) > maxAcceptLanguageBytes {
+ return ""
+ }
+ type choice struct {
+ tag string
+ q float64
+ at int
+ }
+ var choices []choice
+ for i, part := range strings.Split(acceptLanguage, ",") {
+ fields := strings.Split(strings.TrimSpace(part), ";")
+ tag, q := strings.TrimSpace(fields[0]), 1.0
+ if tag == "" {
+ continue
+ }
+ ok := true
+ for _, f := range fields[1:] {
+ name, value, found := strings.Cut(strings.TrimSpace(f), "=")
+ if !found || strings.TrimSpace(name) != "q" {
+ ok = false
+ break
+ }
+ v, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
+ if err != nil || v < 0 || v > 1 {
+ ok = false
+ break
+ }
+ q = v
+ }
+ if ok && q > 0 {
+ choices = append(choices, choice{tag, q, i})
+ }
+ }
+ sort.SliceStable(choices, func(i, j int) bool { return choices[i].q > choices[j].q })
+ for _, c := range choices {
+ if c.tag == "*" || isEnglish(c.tag) {
+ return ""
+ }
+ if got, ok := MatchLanguage(c.tag, available); ok {
+ return got
+ }
+ }
+ return ""
+}
diff --git a/pkg/addons/contract/i18n_test.go b/pkg/addons/contract/i18n_test.go
new file mode 100644
index 0000000000..e1e16cff8e
--- /dev/null
+++ b/pkg/addons/contract/i18n_test.go
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: MIT
+package contract
+
+import (
+ "encoding/json"
+ "os"
+ "strings"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("manifest translations (i18n)", func() {
+ var m Manifest
+ BeforeEach(func() {
+ b, err := os.ReadFile("../../../tests/addons/fixtures/contracts/page.json")
+ Expect(err).NotTo(HaveOccurred())
+ m, err = ParseManifest(b)
+ Expect(err).NotTo(HaveOccurred())
+ m.Capabilities.UI.SDK = VersionRange{Min: 2, Max: 2}
+ m.Capabilities.UI.Pages = []string{"bot"}
+ m.Capabilities.UI.Navigation = nil
+ m.Capabilities.UI.MainPage = ""
+ m.Capabilities.Connector = &ConnectorCapability{Kinds: []ConnectorKind{{ID: "telegram", Title: "Telegram", Description: "Bind a bot.", Delivery: DeliveryPull, Page: "bot"}}}
+ })
+ host := func() HostCompatibility {
+ h := compatible()
+ h.SDK = HostSDKVersion
+ return h
+ }
+ kind := func(title, description string) map[string]KindText {
+ return map[string]KindText{"telegram": {Title: title, Description: description}}
+ }
+
+ It("accepts translated connector kinds and keeps them through a JSON round trip", func() {
+ m.I18n = map[string]Translation{
+ "it": {Kinds: kind("Telegram", "Collega un bot.")},
+ "pt-BR": {Kinds: kind("Telegram", "Vincule um bot.")},
+ }
+ Expect(ValidateManifest(m, host())).To(Succeed())
+ raw, err := json.Marshal(m)
+ Expect(err).NotTo(HaveOccurred())
+ back, err := ParseManifest(raw)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(back.I18n).To(Equal(m.I18n))
+ })
+ It("is optional: an absent field writes nothing", func() {
+ raw, err := json.Marshal(m)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(raw)).NotTo(ContainSubstring("i18n"))
+ })
+ It("refuses an unknown field inside a translation, as a host that does not know the field does", func() {
+ raw, _ := json.Marshal(m)
+ for _, extra := range []string{`"toolDescriptions":{}`, `"skills":{}`, `"settings":{}`, `"description":"x"`} {
+ doc := strings.TrimSuffix(string(raw), "}") + `,"i18n":{"it":{` + extra + `}}}`
+ _, err := ParseManifest([]byte(doc))
+ Expect(err).To(HaveOccurred(), extra)
+ }
+ doc := strings.TrimSuffix(string(raw), "}") + `,"i18n":{"it":{"kinds":{"telegram":{"title":"T","tool":"x"}}}}}`
+ _, err := ParseManifest([]byte(doc))
+ Expect(err).To(HaveOccurred())
+ })
+ It("refuses duplicate keys and a translation that is not an object", func() {
+ raw, _ := json.Marshal(m)
+ base := strings.TrimSuffix(string(raw), "}")
+ for _, extra := range []string{`"i18n":{"it":{},"it":{}}`, `"i18n":{"it":"x"}`, `"i18n":[]`} {
+ _, err := ParseManifest([]byte(base + "," + extra + "}"))
+ Expect(err).To(HaveOccurred(), extra)
+ }
+ })
+ It("refuses bad language tags, English and a repeated tag", func() {
+ for _, tag := range []string{"", "x", "en", "EN", "en_US", "not a tag", strings.Repeat("a", 36), "it-IT-", "italiano-lungo-assai"} {
+ m.I18n = map[string]Translation{tag: {Kinds: kind("T", "D")}}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed(), "tag %q", tag)
+ }
+ m.I18n = map[string]Translation{"pt-BR": {Kinds: kind("T", "D")}, "pt-br": {Kinds: kind("T", "D")}}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed(), "tags differing in case")
+ for _, tag := range []string{"de", "es", "fr", "id", "it", "ko", "pt-BR", "zh-CN", "ja", "zh-Hant-TW"} {
+ m.I18n = map[string]Translation{tag: {Kinds: kind("T", "D")}}
+ Expect(ValidateManifest(m, host())).To(Succeed(), "tag %q", tag)
+ }
+ })
+ It("limits the number of languages", func() {
+ m.I18n = map[string]Translation{}
+ for i := 0; i < MaxLanguages; i++ {
+ m.I18n[string(rune('a'+i%26))+string(rune('b'+i/26))+"-US"] = Translation{Kinds: kind("T", "D")}
+ }
+ Expect(ValidateManifest(m, host())).To(Succeed())
+ m.I18n["zz-US"] = Translation{Kinds: kind("T", "D")}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed())
+ })
+ It("refuses an empty translation and an empty entry", func() {
+ m.I18n = map[string]Translation{"it": {}}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed())
+ m.I18n = map[string]Translation{"it": {Kinds: map[string]KindText{"telegram": {}}}}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed())
+ })
+ It("translates only a kind that the manifest declares", func() {
+ m.I18n = map[string]Translation{"it": {Kinds: map[string]KindText{"slack": {Title: "Slack"}}}}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed())
+ m.Capabilities.Connector = nil
+ m.I18n = map[string]Translation{"it": {Kinds: kind("T", "")}}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed(), "no connector, no kinds")
+ })
+ It("translates only a text that the English kind has", func() {
+ m.Capabilities.Connector.Kinds[0].Description = ""
+ m.I18n = map[string]Translation{"it": {Kinds: kind("Telegram", "Collega un bot.")}}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed())
+ m.I18n = map[string]Translation{"it": {Kinds: kind("Telegram", "")}}
+ Expect(ValidateManifest(m, host())).To(Succeed())
+ })
+ It("bounds each string like its English field and refuses markup and control characters", func() {
+ bad := []string{"T", "a > b", "line\nbreak", "bell\a", "bidi override", "\xff\xfe", " padded", "padded "}
+ for _, s := range bad {
+ m.I18n = map[string]Translation{"it": {Kinds: kind(s, "")}}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed(), "title %q", s)
+ m.I18n = map[string]Translation{"it": {Kinds: kind("", s)}}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed(), "description %q", s)
+ }
+ m.I18n = map[string]Translation{"it": {Kinds: kind(strings.Repeat("è", MaxTitleChars), strings.Repeat("è", MaxDescriptionChars))}}
+ Expect(ValidateManifest(m, host())).To(Succeed(), "bounds count characters")
+ m.I18n = map[string]Translation{"it": {Kinds: kind(strings.Repeat("a", MaxTitleChars+1), "")}}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed())
+ m.I18n = map[string]Translation{"it": {Kinds: kind("", strings.Repeat("a", MaxDescriptionChars+1))}}
+ Expect(ValidateManifest(m, host())).NotTo(Succeed())
+ })
+ It("bounds the total text of all translations", func() {
+ kinds := []ConnectorKind{}
+ tr := map[string]KindText{}
+ for i := 0; i < MaxConnectorKinds; i++ {
+ id := "k" + string(rune('a'+i))
+ kinds = append(kinds, ConnectorKind{ID: id, Title: "T", Description: "D", Delivery: DeliveryPull, Page: "bot"})
+ tr[id] = KindText{Title: strings.Repeat("t", MaxTitleChars), Description: strings.Repeat("d", MaxDescriptionChars)}
+ }
+ m.Capabilities.Connector = &ConnectorCapability{Kinds: kinds}
+ m.I18n = map[string]Translation{}
+ for i := 0; i < MaxLanguages; i++ {
+ m.I18n[string(rune('a'+i%26))+string(rune('b'+i/26))+"-US"] = Translation{Kinds: tr}
+ }
+ Expect(MaxConnectorKinds*MaxLanguages*(MaxTitleChars+MaxDescriptionChars)).To(BeNumerically(">", MaxI18nChars), "the limit must be reachable")
+ Expect(ValidateManifest(m, host())).NotTo(Succeed())
+ })
+ It("is part of the manifest bytes, so a changed translation changes them", func() {
+ m.I18n = map[string]Translation{"it": {Kinds: kind("Telegram", "Collega un bot.")}}
+ a, _ := json.Marshal(m)
+ m.I18n = map[string]Translation{"it": {Kinds: kind("Telegram", "Collega un altro bot.")}}
+ b, _ := json.Marshal(m)
+ Expect(a).NotTo(Equal(b))
+ })
+})
+
+var _ = Describe("language negotiation", func() {
+ available := []string{"it", "de", "pt-BR", "zh-CN", "fr-CA"}
+ DescribeTable("NegotiateLanguage",
+ func(lang, header, want string) {
+ Expect(NegotiateLanguage(lang, header, available)).To(Equal(want))
+ },
+ Entry("an explicit language wins over the header", "de", "it", "de"),
+ Entry("the header is used without an explicit language", "", "it", "it"),
+ Entry("nothing asked is English", "", "", ""),
+ Entry("an unknown explicit language is English, not the header", "xx", "it", ""),
+ Entry("case does not matter", "PT-br", "", "pt-BR"),
+ Entry("a region falls back to the language", "it-IT", "", "it"),
+ Entry("a bare language finds its only regional entry", "pt", "", "pt-BR"),
+ Entry("zh finds zh-CN", "zh", "", "zh-CN"),
+ Entry("quality values order the header", "", "es;q=0.9, de;q=0.8, it;q=0.7", "de"),
+ Entry("a lower quality is used when the first is missing", "", "ja,it;q=0.5", "it"),
+ Entry("q=0 refuses a language", "", "it;q=0, de;q=0.1", "de"),
+ Entry("a wildcard is English", "", "*", ""),
+ Entry("English is the empty tag", "en", "", ""),
+ Entry("English in the header stops the search", "", "en, it;q=0.5", ""),
+ Entry("a malformed header is English", "", ";;;q=x,,,", ""),
+ Entry("a malformed explicit language is English", "it;drop table", "de", ""),
+ Entry("an overlong header is English", "", strings.Repeat("it,", 1000), ""),
+ )
+ It("lists no language when none is available", func() {
+ Expect(NegotiateLanguage("it", "it", nil)).To(Equal(""))
+ })
+})
+
+var _ = Describe("Localized.For", func() {
+ It("returns the text of the language and says whether it found one", func() {
+ l := Localized{ByLanguage: map[string]string{"en": "Tables", "it": "Tabelle", "pt-BR": "Tabelas"}}
+ Expect(l.For("it")).To(Equal("Tabelle"))
+ Expect(l.For("pt")).To(Equal("Tabelas"))
+ Expect(l.For("")).To(Equal("Tables"))
+ Expect(l.For("de")).To(Equal("Tables"))
+ Expect(Localized{Text: "Plain"}.For("it")).To(Equal("Plain"))
+ })
+ It("lists the languages other than English in order", func() {
+ l := Localized{ByLanguage: map[string]string{"en": "Tables", "pt-BR": "Tabelas", "it": "Tabelle"}}
+ Expect(l.Languages()).To(Equal([]string{"it", "pt-BR"}))
+ Expect(Localized{Text: "x"}.Languages()).To(BeEmpty())
+ })
+})
diff --git a/pkg/addons/contract/manifest.go b/pkg/addons/contract/manifest.go
index fb5d8ca63b..eb9a8488f3 100644
--- a/pkg/addons/contract/manifest.go
+++ b/pkg/addons/contract/manifest.go
@@ -91,6 +91,13 @@ type Manifest struct {
// enabled. "on-demand" is reserved and rejected until it is implemented.
LaunchMode string `json:"launchMode,omitempty"`
Capabilities Capabilities `json:"capabilities"`
+ // I18n translates the text that the host shows for the manifest, by
+ // language tag (see i18n.go). It is optional under api 2. A host that
+ // does not know the field refuses the manifest as it refuses any unknown
+ // field. The English fields remain the source and the fallback. The title
+ // and description of the add-on are translated by the language maps of
+ // ui.title and ui.description.
+ I18n map[string]Translation `json:"i18n,omitempty"`
}
type Capabilities struct {
Service bool `json:"service,omitempty"`
@@ -418,6 +425,9 @@ func ValidateManifest(m Manifest, h HostCompatibility) error {
if e := validateSkillDefinitions(m.ID, c.SkillDefinitions); e != nil {
return e
}
+ if e := validateI18n(m); e != nil {
+ return e
+ }
if u := c.UI; u != nil {
if u.SDK.Min > 0 && u.SDK.Max >= u.SDK.Min && !u.SDK.accepts(h.SDK) {
return &SDKRefusal{Min: u.SDK.Min, Max: u.SDK.Max, Host: h.SDK}
diff --git a/pkg/addons/packaging/catalog.go b/pkg/addons/packaging/catalog.go
index 15addb4ab6..8ea686201c 100644
--- a/pkg/addons/packaging/catalog.go
+++ b/pkg/addons/packaging/catalog.go
@@ -51,10 +51,12 @@ type catalogRelease struct {
// the manifest's skill definitions replace.
SkillShadows []string `json:"skillShadows,omitempty"`
UI struct {
- Title string `json:"title"`
- Description string `json:"description"`
- MainPage string `json:"mainPage"`
- Icon string `json:"icon"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ Titles map[string]string `json:"titles,omitempty"`
+ Descriptions map[string]string `json:"descriptions,omitempty"`
+ MainPage string `json:"mainPage"`
+ Icon string `json:"icon"`
} `json:"ui"`
}
@@ -110,6 +112,34 @@ func skillFieldsYAML(shadows []string, indent string) string {
return b.String()
}
+// translationFieldsYAML renders the titles and descriptions fields of the ui
+// block of a catalog entry: the languages of the manifest's ui.title and
+// ui.description other than English, sorted, so the file is stable. English is
+// the title and description of the entry itself.
+func translationFieldsYAML(ui *contract.UI, indent string) string {
+ if ui == nil {
+ return ""
+ }
+ var b strings.Builder
+ for _, f := range []struct {
+ key string
+ text *contract.Localized
+ }{{"titles", ui.Title}, {"descriptions", ui.Description}} {
+ if f.text == nil {
+ continue
+ }
+ tags := f.text.Languages()
+ if len(tags) == 0 {
+ continue
+ }
+ b.WriteString(indent + f.key + ":\n")
+ for _, tag := range tags {
+ b.WriteString(indent + " \"" + jsonString(tag) + "\": \"" + jsonString(f.text.ByLanguage[tag]) + "\"\n")
+ }
+ }
+ return b.String()
+}
+
// cmdCatalogTools adds the toolCount and tools fields to the entries of an
// existing library index, reading each manifest from addons//manifest.json
// of the repository. It touches no other field, so the digests and artifacts
@@ -263,7 +293,9 @@ func cmdCatalog(args []string) error {
"title": title, "description": description, "main_page": mainPage, "icon": icon,
}
text := string(tmpl)
- text = strings.TrimRight(text, "\n") + "\n" + toolFieldsYAML(manifest.Capabilities.Tools, "", contract.ShadowedTools(manifest.Capabilities.ToolDefinitions)...) +
+ // The ui block is the last block of every template, so the translated
+ // text lands inside it.
+ text = strings.TrimRight(text, "\n") + "\n" + translationFieldsYAML(ui, " ") + toolFieldsYAML(manifest.Capabilities.Tools, "", contract.ShadowedTools(manifest.Capabilities.ToolDefinitions)...) +
skillFieldsYAML(contract.ShadowedSkills(manifest.Capabilities.SkillDefinitions), "")
if mutable {
text = strings.Replace(text, "digest: \"{{digest}}\"", "mutable: true", 1)
diff --git a/pkg/addons/packaging/translations_test.go b/pkg/addons/packaging/translations_test.go
new file mode 100644
index 0000000000..0405ac0ccb
--- /dev/null
+++ b/pkg/addons/packaging/translations_test.go
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: MIT
+package packaging
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "slices"
+ "strings"
+
+ "github.com/mudler/LocalAI/pkg/addons/contract"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+// mainLanguages are the languages of the main app locales. Every first-party
+// add-on ships its title and description in all of them; Italian is the one
+// the project requires first.
+var mainLanguages = []string{"de", "es", "fr", "id", "it", "ko", "pt-BR", "zh-CN"}
+
+func readManifest(id string) (contract.Manifest, []byte) {
+ GinkgoHelper()
+ raw, err := os.ReadFile("../../../addons/" + id + "/manifest.json")
+ Expect(err).NotTo(HaveOccurred())
+ m, err := contract.ParseManifest(raw)
+ Expect(err).NotTo(HaveOccurred())
+ return m, raw
+}
+
+var _ = Describe("first-party add-on translations", func() {
+ for _, id := range firstPartyAddons {
+ It("translates the title and the description of "+id+" in every main language", func() {
+ m, _ := readManifest(id)
+ ui := m.Capabilities.UI
+ Expect(ui).NotTo(BeNil())
+ for name, text := range map[string]*contract.Localized{"title": ui.Title, "description": ui.Description} {
+ Expect(text).NotTo(BeNil(), "%s of %s", name, id)
+ Expect(text.ByLanguage).To(HaveKey("en"), "the English %s is the source and the fallback", name)
+ Expect(text.ByLanguage).To(HaveKey("it"), "Italian %s of %s is required", name, id)
+ for _, tag := range mainLanguages {
+ Expect(text.ByLanguage).To(HaveKey(tag), "%s of %s in %s", name, id, tag)
+ Expect(strings.TrimSpace(text.ByLanguage[tag])).NotTo(BeEmpty())
+ }
+ }
+ // The two maps cover the same languages, so a card never shows a
+ // translated title over an English description by omission.
+ var titles, descriptions []string
+ for tag := range ui.Title.ByLanguage {
+ titles = append(titles, tag)
+ }
+ for tag := range ui.Description.ByLanguage {
+ descriptions = append(descriptions, tag)
+ }
+ slices.Sort(titles)
+ slices.Sort(descriptions)
+ Expect(titles).To(Equal(descriptions), "title and description of %s have different languages", id)
+ })
+
+ It("gives every translated connector kind of "+id+" the fields of its English kind, and no others", func() {
+ m, raw := readManifest(id)
+ var kinds []contract.ConnectorKind
+ if m.Capabilities.Connector != nil {
+ kinds = m.Capabilities.Connector.Kinds
+ }
+ if len(kinds) == 0 {
+ Expect(m.I18n).To(BeEmpty(), "%s has no connector kind to translate", id)
+ return
+ }
+ Expect(m.I18n).NotTo(BeEmpty(), "%s declares a connector kind and no translation of it", id)
+ for _, tag := range mainLanguages {
+ tr, ok := m.I18n[tag]
+ Expect(ok).To(BeTrue(), "%s has no %s translation of its kinds", id, tag)
+ Expect(tr.Kinds).To(HaveLen(len(kinds)), "%s %s translates a different set of kinds", id, tag)
+ for _, k := range kinds {
+ got := tr.Kinds[k.ID]
+ Expect(got.Title != "").To(BeTrue(), "%s %s kind %s: title missing", id, tag, k.ID)
+ Expect(got.Description != "").To(Equal(k.Description != ""), "%s %s kind %s: description must exist exactly when the English one does", id, tag, k.ID)
+ }
+ }
+ // The strict parser already refuses unknown fields; this pins the
+ // closed set at the byte level, so a looser parser would not hide one.
+ var loose map[string]map[string]map[string]map[string]json.RawMessage
+ var top map[string]json.RawMessage
+ Expect(json.Unmarshal(raw, &top)).To(Succeed())
+ Expect(json.Unmarshal(top["i18n"], &loose)).To(Succeed())
+ for tag, tr := range loose {
+ Expect(tr).To(HaveLen(1), "%s %s: only kinds is translatable", id, tag)
+ Expect(tr).To(HaveKey("kinds"))
+ for kid, fields := range tr["kinds"] {
+ for f := range fields {
+ Expect(f).To(BeElementOf("title", "description"), "%s %s %s: %s is not translatable", id, tag, kid, f)
+ }
+ }
+ }
+ })
+ }
+
+ It("lists every add-on directory that has a manifest", func() {
+ dirs, err := filepath.Glob("../../../addons/*/manifest.json")
+ Expect(err).NotTo(HaveOccurred())
+ var ids []string
+ for _, d := range dirs {
+ ids = append(ids, filepath.Base(filepath.Dir(d)))
+ }
+ slices.Sort(ids)
+ want := slices.Clone(firstPartyAddons)
+ slices.Sort(want)
+ Expect(ids).To(Equal(want), "a new add-on joins firstPartyAddons and gets its translations")
+ })
+})
+
+var _ = Describe("translations in the package and the library", func() {
+ It("changes the package digest when a translation changes, and the package still verifies", func() {
+ build := func(edit func(string) string) (string, string) {
+ p := newProjectFor("telegram", both)
+ path := filepath.Join(p.root, "addons/telegram/manifest.json")
+ b, err := os.ReadFile(path)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(os.WriteFile(path, []byte(edit(string(b))), 0o644)).To(Succeed())
+ dir := p.mustBuild(amd, "1.2.3", "same bytes", "--revision", rev)
+ Expect(p.verify(dir, amd, "1.2.3")).To(Succeed())
+ l, _, err := loadLeg(dir)
+ Expect(err).NotTo(HaveOccurred())
+ return l.descriptor.Digest.String(), dir
+ }
+ same := func(s string) string { return s }
+ a, _ := build(same)
+ b, _ := build(same)
+ Expect(a).To(Equal(b), "the build is deterministic")
+ changed, _ := build(func(s string) string {
+ Expect(s).To(ContainSubstring("Parla con uno dei tuoi agenti salvati da un bot Telegram."))
+ return strings.Replace(s, "Parla con uno dei tuoi agenti salvati da un bot Telegram.", "Parla con un tuo agente salvato da un bot Telegram.", 1)
+ })
+ Expect(changed).NotTo(Equal(a), "a changed translation is a different package")
+ dropped, _ := build(func(s string) string {
+ return strings.Replace(s, "Parla con uno dei tuoi agenti salvati da un bot Telegram.", "Parla con uno dei tuoi agenti salvati da un bot Telegram!", 1)
+ })
+ Expect(dropped).NotTo(Equal(a), "even one character is a different package")
+ })
+
+ It("refuses to build a package whose translation the host would refuse", func() {
+ p := newProjectFor("telegram", both)
+ path := filepath.Join(p.root, "addons/telegram/manifest.json")
+ b, _ := os.ReadFile(path)
+ Expect(os.WriteFile(path, []byte(strings.Replace(string(b), `"i18n": {`, `"i18n": {"en": {"kinds": {"telegram": {"title": "T"}}},`, 1)), 0o644)).To(Succeed())
+ _, err := p.build(amd, "1.2.3", exe("x"), "--revision", rev)
+ Expect(err).To(HaveOccurred())
+ })
+
+ It("renders the translated title and description of every add-on into its library entry", func() {
+ for _, id := range firstPartyAddons {
+ m, _ := readManifest(id)
+ p := newProjectFor(id, both)
+ out := filepath.Join(GinkgoT().TempDir(), "catalog.yaml")
+ _, err := run("catalog", "--config", p.config, "--root", p.root, "--template", "../../../gallery/addons/"+id+".yaml.tmpl", "--from-manifest", "--repository", newRegistry().repo("addons"), "--channel", "development", "--owner-repo", "example/localai", "--signer-ref", "refs/heads/master", "--out", out)
+ Expect(err).NotTo(HaveOccurred(), id)
+ b, err := os.ReadFile(out)
+ Expect(err).NotTo(HaveOccurred())
+ entries, err := readCatalogIndex(b)
+ Expect(err).NotTo(HaveOccurred(), id)
+ Expect(entries).NotTo(BeEmpty())
+ for _, e := range entries {
+ Expect(e.UI.Title).To(Equal(m.Capabilities.UI.Title.Default()), id)
+ Expect(e.UI.Titles).To(HaveKey("it"), id)
+ Expect(e.UI.Titles).NotTo(HaveKey("en"), "English is the title itself")
+ for _, tag := range m.Capabilities.UI.Title.Languages() {
+ Expect(e.UI.Titles[tag]).To(Equal(m.Capabilities.UI.Title.ByLanguage[tag]), "%s %s", id, tag)
+ Expect(e.UI.Descriptions[tag]).To(Equal(m.Capabilities.UI.Description.ByLanguage[tag]), "%s %s", id, tag)
+ }
+ }
+ }
+ })
+})
diff --git a/swagger/docs.go b/swagger/docs.go
index 3cd395b085..9b608e80e3 100644
--- a/swagger/docs.go
+++ b/swagger/docs.go
@@ -122,7 +122,7 @@ const docTemplate = `{
},
"/api/addons/apps": {
"get": {
- "description": "Returns the installed addons that are enabled, for every authenticated caller who holds the addons feature. The owner decides what a caller sees; hiding an entry is never the control, because every call the entry leads to is authorized again. Each app has its title, description, icon URL, version and installed generation, a process state (running, starting, failed or stopped; the state is an observation, separate from the enabled flag), the path of its main page when its manifest declares a valid one, and a bounded tool summary. A headless addon is listed without a main page. Administrators additionally see installed addons that are disabled and, per app, the start and stop controls and the management link. An app carries update update-pending, separate from state, only when the owner's cached update check says an update is available for the installed package; the list never evaluates a check and never reaches a library.",
+ "description": "Returns the installed addons that are enabled, for every authenticated caller who holds the addons feature. The owner decides what a caller sees; hiding an entry is never the control, because every call the entry leads to is authorized again. Each app has its title, description, icon URL, version and installed generation, a process state (running, starting, failed or stopped; the state is an observation, separate from the enabled flag), the path of its main page when its manifest declares a valid one, and a bounded tool summary. A headless addon is listed without a main page. Administrators additionally see installed addons that are disabled and, per app, the start and stop controls and the management link. An app carries update update-pending, separate from state, only when the owner's cached update check says an update is available for the installed package; the list never evaluates a check and never reaches a library. Text that the manifest translates (ui.title and ui.description language maps) is returned in all languages in titles and descriptions. When the request names a language, each app that translates it also carries language, localizedTitle and localizedDescription, resolved per field with English as the fallback; title and description never change.",
"produces": [
"application/json"
],
@@ -130,6 +130,20 @@ const docTemplate = `{
"Addons"
],
"summary": "List the addon apps the caller may use",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Language tag to resolve the text for, such as it or pt-BR. Wins over Accept-Language. An unknown language gives the English text.",
+ "name": "lang",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "Used when lang is absent. Quality values are honored; English or no match adds no localized field.",
+ "name": "Accept-Language",
+ "in": "header"
+ }
+ ],
"responses": {
"200": {
"description": "Apps visible to the caller",
@@ -5752,11 +5766,19 @@ const docTemplate = `{
},
"/api/chats/connectors/kinds": {
"get": {
- "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.",
+ "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"
],
"summary": "List the connector kinds you can bind.",
+ "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.",
+ "name": "lang",
+ "in": "query"
+ }
+ ],
"responses": {
"200": {
"description": "The kinds",
@@ -16041,10 +16063,23 @@ const docTemplate = `{
"type": "string",
"example": "2026-10-30T09:15:00Z"
},
+ "language": {
+ "description": "Language, LocalizedTitle and LocalizedDescription are present only when\nthe request named a language (the lang query parameter, else the\nAccept-Language header) that the manifest translates. Language is the\ntag that matched. Each localized field falls back to English on its own,\nso a title without a translation is the English title. No other field\nchanges: Title and Description stay the default-language text.",
+ "type": "string",
+ "example": "it"
+ },
"launchMode": {
"type": "string",
"example": "always"
},
+ "localizedDescription": {
+ "type": "string",
+ "example": "Tieni degli appunti."
+ },
+ "localizedTitle": {
+ "type": "string",
+ "example": "Appunti"
+ },
"mainPage": {
"description": "MainPage is the path the Open action navigates to. It is present only\nfor an enabled addon whose manifest declares a main page that is one of\nits pages.",
"type": "string",
@@ -21908,6 +21943,12 @@ const docTemplate = `{
"description": {
"type": "string"
},
+ "descriptions": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
"files": {
"description": "Files are the limits of a binding of this kind that has no limit of\nyour own.",
"allOf": [
@@ -21919,6 +21960,16 @@ const docTemplate = `{
"kind": {
"type": "string"
},
+ "language": {
+ "description": "Language, LocalizedTitle and LocalizedDescription are present only when\nthe request names a language (the lang query parameter, else the\nAccept-Language header) that the kind translates. Each falls back to\nEnglish on its own.",
+ "type": "string"
+ },
+ "localized_description": {
+ "type": "string"
+ },
+ "localized_title": {
+ "type": "string"
+ },
"options": {
"description": "Options are the choices the add-on lets each binding of this kind make.",
"type": "array",
@@ -21952,6 +22003,13 @@ const docTemplate = `{
},
"title": {
"type": "string"
+ },
+ "titles": {
+ "description": "Titles and Descriptions carry the translated text of the manifest by\nlanguage tag, when it has some. Title and Description stay English.",
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
}
}
},
diff --git a/swagger/swagger.json b/swagger/swagger.json
index 14b44bc03b..19ebc621ad 100644
--- a/swagger/swagger.json
+++ b/swagger/swagger.json
@@ -119,7 +119,7 @@
},
"/api/addons/apps": {
"get": {
- "description": "Returns the installed addons that are enabled, for every authenticated caller who holds the addons feature. The owner decides what a caller sees; hiding an entry is never the control, because every call the entry leads to is authorized again. Each app has its title, description, icon URL, version and installed generation, a process state (running, starting, failed or stopped; the state is an observation, separate from the enabled flag), the path of its main page when its manifest declares a valid one, and a bounded tool summary. A headless addon is listed without a main page. Administrators additionally see installed addons that are disabled and, per app, the start and stop controls and the management link. An app carries update update-pending, separate from state, only when the owner's cached update check says an update is available for the installed package; the list never evaluates a check and never reaches a library.",
+ "description": "Returns the installed addons that are enabled, for every authenticated caller who holds the addons feature. The owner decides what a caller sees; hiding an entry is never the control, because every call the entry leads to is authorized again. Each app has its title, description, icon URL, version and installed generation, a process state (running, starting, failed or stopped; the state is an observation, separate from the enabled flag), the path of its main page when its manifest declares a valid one, and a bounded tool summary. A headless addon is listed without a main page. Administrators additionally see installed addons that are disabled and, per app, the start and stop controls and the management link. An app carries update update-pending, separate from state, only when the owner's cached update check says an update is available for the installed package; the list never evaluates a check and never reaches a library. Text that the manifest translates (ui.title and ui.description language maps) is returned in all languages in titles and descriptions. When the request names a language, each app that translates it also carries language, localizedTitle and localizedDescription, resolved per field with English as the fallback; title and description never change.",
"produces": [
"application/json"
],
@@ -127,6 +127,20 @@
"Addons"
],
"summary": "List the addon apps the caller may use",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Language tag to resolve the text for, such as it or pt-BR. Wins over Accept-Language. An unknown language gives the English text.",
+ "name": "lang",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "Used when lang is absent. Quality values are honored; English or no match adds no localized field.",
+ "name": "Accept-Language",
+ "in": "header"
+ }
+ ],
"responses": {
"200": {
"description": "Apps visible to the caller",
@@ -5749,11 +5763,19 @@
},
"/api/chats/connectors/kinds": {
"get": {
- "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.",
+ "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"
],
"summary": "List the connector kinds you can bind.",
+ "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.",
+ "name": "lang",
+ "in": "query"
+ }
+ ],
"responses": {
"200": {
"description": "The kinds",
@@ -16038,10 +16060,23 @@
"type": "string",
"example": "2026-10-30T09:15:00Z"
},
+ "language": {
+ "description": "Language, LocalizedTitle and LocalizedDescription are present only when\nthe request named a language (the lang query parameter, else the\nAccept-Language header) that the manifest translates. Language is the\ntag that matched. Each localized field falls back to English on its own,\nso a title without a translation is the English title. No other field\nchanges: Title and Description stay the default-language text.",
+ "type": "string",
+ "example": "it"
+ },
"launchMode": {
"type": "string",
"example": "always"
},
+ "localizedDescription": {
+ "type": "string",
+ "example": "Tieni degli appunti."
+ },
+ "localizedTitle": {
+ "type": "string",
+ "example": "Appunti"
+ },
"mainPage": {
"description": "MainPage is the path the Open action navigates to. It is present only\nfor an enabled addon whose manifest declares a main page that is one of\nits pages.",
"type": "string",
@@ -21905,6 +21940,12 @@
"description": {
"type": "string"
},
+ "descriptions": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
"files": {
"description": "Files are the limits of a binding of this kind that has no limit of\nyour own.",
"allOf": [
@@ -21916,6 +21957,16 @@
"kind": {
"type": "string"
},
+ "language": {
+ "description": "Language, LocalizedTitle and LocalizedDescription are present only when\nthe request names a language (the lang query parameter, else the\nAccept-Language header) that the kind translates. Each falls back to\nEnglish on its own.",
+ "type": "string"
+ },
+ "localized_description": {
+ "type": "string"
+ },
+ "localized_title": {
+ "type": "string"
+ },
"options": {
"description": "Options are the choices the add-on lets each binding of this kind make.",
"type": "array",
@@ -21949,6 +22000,13 @@
},
"title": {
"type": "string"
+ },
+ "titles": {
+ "description": "Titles and Descriptions carry the translated text of the manifest by\nlanguage tag, when it has some. Title and Description stay English.",
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
}
}
},
diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml
index 3353616830..7b48af8458 100644
--- a/swagger/swagger.yaml
+++ b/swagger/swagger.yaml
@@ -343,9 +343,25 @@ definitions:
several add-ons replace.
example: "2026-10-30T09:15:00Z"
type: string
+ language:
+ description: |-
+ Language, LocalizedTitle and LocalizedDescription are present only when
+ the request named a language (the lang query parameter, else the
+ Accept-Language header) that the manifest translates. Language is the
+ tag that matched. Each localized field falls back to English on its own,
+ so a title without a translation is the English title. No other field
+ changes: Title and Description stay the default-language text.
+ example: it
+ type: string
launchMode:
example: always
type: string
+ localizedDescription:
+ example: Tieni degli appunti.
+ type: string
+ localizedTitle:
+ example: Appunti
+ type: string
mainPage:
description: |-
MainPage is the path the Open action navigates to. It is present only
@@ -4938,6 +4954,10 @@ definitions:
type: boolean
description:
type: string
+ descriptions:
+ additionalProperties:
+ type: string
+ type: object
files:
allOf:
- $ref: '#/definitions/schema.ChatsConnectorFiles'
@@ -4946,6 +4966,17 @@ definitions:
your own.
kind:
type: string
+ language:
+ description: |-
+ Language, LocalizedTitle and LocalizedDescription are present only when
+ the request names a language (the lang query parameter, else the
+ Accept-Language header) that the kind translates. Each falls back to
+ English on its own.
+ type: string
+ localized_description:
+ type: string
+ localized_title:
+ type: string
options:
description: Options are the choices the add-on lets each binding of this
kind make.
@@ -4978,6 +5009,13 @@ definitions:
type: boolean
title:
type: string
+ titles:
+ additionalProperties:
+ type: string
+ description: |-
+ Titles and Descriptions carry the translated text of the manifest by
+ language tag, when it has some. Title and Description stay English.
+ type: object
type: object
schema.ChatsConnectorKindList:
properties:
@@ -11817,7 +11855,22 @@ paths:
and the management link. An app carries update update-pending, separate from
state, only when the owner's cached update check says an update is available
for the installed package; the list never evaluates a check and never reaches
- a library.
+ a library. Text that the manifest translates (ui.title and ui.description
+ language maps) is returned in all languages in titles and descriptions. When
+ the request names a language, each app that translates it also carries language,
+ localizedTitle and localizedDescription, resolved per field with English as
+ the fallback; title and description never change.
+ parameters:
+ - description: Language tag to resolve the 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
+ - description: Used when lang is absent. Quality values are honored; English
+ or no match adds no localized field.
+ in: header
+ name: Accept-Language
+ type: string
produces:
- application/json
responses:
@@ -17192,7 +17245,16 @@ paths:
get:
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.
+ 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.
+ 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.
+ in: query
+ name: lang
+ type: string
responses:
"200":
description: The kinds