{renderSlot(seat, {
- entries,
+ entries: seat === 'conversation.session.header.views.actions' ? [] : entries,
onOpen: (id) => { setOpened(`${seat}:${id}`) },
onPinnedChange: (_id, next) => { setPinned(next) },
onDismiss: () => {},
@@ -148,8 +152,7 @@ describe('More menu registrations', () => {
const sidebar = () => within(b.view.getByRole('menu', { name: 'sidebar.panellist.actions' }))
expect(sidebar().queryByRole('menuitem', { name: 'Browse other plugins' })).toBeNull()
expect(sidebar().getByRole('menuitem', { name: 'Create your plugin' })).toBeTruthy()
- // The Creator row takes the separator the absent discovery row would carry.
- expect(sidebar().getAllByRole('separator')).toHaveLength(1)
+ expect(sidebar().queryByRole('separator')).toBeNull()
const injected = b.runtime.slots.entries('sidebar.panellist.actions')[0]!.inject!() as { browsePlugins: () => void }
expect(() => { injected.browsePlugins() }).not.toThrow()
@@ -170,15 +173,10 @@ describe('More menu registrations', () => {
const b = await bench()
for (const seat of SEATS) {
expect(b.runtime.slots.entries(seat).map(entry => entry.options.id)).toEqual(['entry-customization'])
- expect(within(b.view.getByRole('menu', { name: seat })).getByRole('menuitem', { name: 'Notes' })).toBeTruthy()
+ expect(within(b.view.getByRole('menu', { name: seat })).getAllByRole('menuitem').map(row => row.textContent))
+ .toEqual(['Browse other plugins', 'Create your plugin'])
}
const sidebar = within(b.view.getByRole('menu', { name: 'sidebar.panellist.actions' }))
- fireEvent.click(sidebar.getByRole('menuitem', { name: 'Unpin from left sidebar: Notes' }))
- expect(within(b.view.getByRole('menu', { name: 'sidebar.right.tab.guide.actions' }))
- .getByRole('menuitem', { name: 'Pin to right sidebar: Notes' })).toBeTruthy()
- fireEvent.click(within(b.view.getByRole('menu', { name: 'conversation.session.header.views.actions' }))
- .getByRole('menuitem', { name: 'Notes' }))
- expect(b.view.getByTestId('opened').textContent).toBe('conversation.session.header.views.actions:notes')
fireEvent.click(sidebar.getByRole('menuitem', { name: 'Browse other plugins' }))
expect(b.openList).toHaveBeenCalledExactlyOnceWith()
await b.plugin.dispose()
@@ -220,15 +218,6 @@ describe('More menu registrations', () => {
expect(b.track).toHaveBeenCalledTimes(2)
})
- it('reports neither entry opening nor pin changes', async () => {
- const b = await bench()
- const menu = within(b.view.getByRole('menu', { name: 'sidebar.panellist.actions' }))
- fireEvent.click(menu.getByRole('menuitem', { name: 'Unpin from left sidebar: Notes' }))
- fireEvent.click(menu.getByRole('menuitem', { name: 'Notes' }))
- expect(b.view.getByTestId('opened').textContent).toBe('sidebar.panellist.actions:notes')
- expect(b.track).not.toHaveBeenCalled()
- })
-
it('keeps both footer actions working without the analytics service', async () => {
const b = await bench('en', { analytics: false })
const menu = within(b.view.getByRole('menu', { name: 'sidebar.right.tab.guide.actions' }))
diff --git a/packages/client/ui-entry-customization/tests/pin-styles.client.spec.ts b/packages/client/ui-entry-customization/tests/pin-styles.client.spec.ts
deleted file mode 100644
index c30d3ee705..0000000000
--- a/packages/client/ui-entry-customization/tests/pin-styles.client.spec.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/** Pin controls use Session-action ink without changing the navigation control's dimensions. */
-import { readFileSync } from 'node:fs'
-import { describe, expect, it } from 'vitest'
-
-const css = readFileSync(new URL('../src/client/EntryMenu.module.css', import.meta.url), 'utf8')
-
-describe('entry pin styling', () => {
- it('uses tertiary ink for both pin states and primary ink on pointer hover', () => {
- expect(css).toMatch(/\.pinButton\s*\{[^}]*color:\s*var\(--dsw-alias-label-tertiary\)/)
- expect(css).toMatch(/\.pinButton:hover\s*\{[^}]*color:\s*var\(--dsw-alias-label-primary\)/)
- expect(css).not.toMatch(/\[data-pinned=['"]true['"]\]\s+\.pinButton\s*\{[^}]*color:/)
- })
-
- it('centers the pin without creating a transformed tooltip container', () => {
- const pinAnchor = css.match(/\.pinAnchor\s*\{([^}]*)\}/)?.[1]
- expect(pinAnchor).toBeDefined()
- expect(pinAnchor).toContain('align-items: center')
- expect(pinAnchor).not.toContain('transform:')
- })
-})
diff --git a/packages/client/ui-file-drag/locale/de.json b/packages/client/ui-file-drag/locale/de.json
new file mode 100644
index 0000000000..5f610493c3
--- /dev/null
+++ b/packages/client/ui-file-drag/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Ziehen von Dateien",
+ "description": "Ziehen Sie Originaldateien in den Composer oder andere Anwendungen, mit lokalisierter Fehlerrückmeldung."
+ }
+}
diff --git a/packages/client/ui-file-drag/locale/es.json b/packages/client/ui-file-drag/locale/es.json
new file mode 100644
index 0000000000..ff04cd6d4c
--- /dev/null
+++ b/packages/client/ui-file-drag/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Arrastrar archivos",
+ "description": "Arrastre los archivos originales al compositor u otras aplicaciones, con comentarios de falla localizados."
+ }
+}
diff --git a/packages/client/ui-file-drag/locale/fr.json b/packages/client/ui-file-drag/locale/fr.json
new file mode 100644
index 0000000000..302a536b7d
--- /dev/null
+++ b/packages/client/ui-file-drag/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Glisser un fichier",
+ "description": "Faites glisser les fichiers originaux dans le composeur ou d'autres applications, avec un retour d'échec localisé."
+ }
+}
diff --git a/packages/client/ui-file-drag/locale/pt.json b/packages/client/ui-file-drag/locale/pt.json
new file mode 100644
index 0000000000..9d59d7c683
--- /dev/null
+++ b/packages/client/ui-file-drag/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Arrastando arquivo",
+ "description": "Arraste os arquivos originais para o compositor ou outros aplicativos, com feedback de falha localizado."
+ }
+}
diff --git a/packages/client/ui-file-drag/locale/ru.json b/packages/client/ui-file-drag/locale/ru.json
new file mode 100644
index 0000000000..00cd72b430
--- /dev/null
+++ b/packages/client/ui-file-drag/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Перетаскивание файлов",
+ "description": "Перетащите исходные файлы в композитор или другие приложения с локализованной обратной связью об ошибках."
+ }
+}
diff --git a/packages/client/ui-file-drag/src/client/index.ts b/packages/client/ui-file-drag/src/client/index.ts
index efe3fb7eab..cfc9c1c85c 100644
--- a/packages/client/ui-file-drag/src/client/index.ts
+++ b/packages/client/ui-file-drag/src/client/index.ts
@@ -8,7 +8,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
import type {} from '@deepseek-ai/dsh-client-locale/client'
import { FileDragFeedback, type FileDragFailure, type FileDragFeedbackInjected } from './feedback.tsx'
import { FileTransfers } from './transfers.ts'
-import { en, ja, zh } from './locales.ts'
+import { en, ja, es, fr, de, ru, pt, zh } from './locales.ts'
/** Application-local file preparation and drop resolution. */
export interface FileDragService {
@@ -33,7 +33,7 @@ export function apply(ctx: Context): void {
const desktop = bridge?.protocolVersion === 1 ? bridge.fileDrag : undefined
const failure = createSnapshotStore
(null)
const t = ctx.locale.bind('fileDrag')
- ctx.effect(() => ctx.locale.register('fileDrag', { zh, en, ja }))
+ ctx.effect(() => ctx.locale.register('fileDrag', { zh, en, ja, es, fr, de, ru, pt }))
let sequence = 0
const reportFailure = (): void => { failure.set({ text: t('failed'), sequence: ++sequence }) }
const transfers = new FileTransfers(desktop, reportFailure)
diff --git a/packages/client/ui-file-drag/src/client/locales.ts b/packages/client/ui-file-drag/src/client/locales.ts
index 9497b29dbc..d0da371eca 100644
--- a/packages/client/ui-file-drag/src/client/locales.ts
+++ b/packages/client/ui-file-drag/src/client/locales.ts
@@ -7,6 +7,21 @@ export const en = { failed: 'Drag failed. Try again.' } satisfies Record
+/** Spanish dictionary, checked against the English keys. */
+export const es = { failed: 'Error al arrastrar. Intentar otra vez.' } satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = { failed: 'Le glissement a échoué. Essayer à nouveau.' } satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = { failed: 'Ziehen ist fehlgeschlagen. Versuchen Sie es erneut.' } satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = { failed: 'Перетащить не удалось. Попробуйте еще раз.' } satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = { failed: 'Falha ao arrastar. Tente novamente.' } satisfies Record
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
interface LocaleNamespaceMap {
/** File transfer failure feedback. */
diff --git a/packages/client/ui-goal/locale/de.json b/packages/client/ui-goal/locale/de.json
new file mode 100644
index 0000000000..2a2420166b
--- /dev/null
+++ b/packages/client/ui-goal/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Sitzungszielleiste",
+ "description": "Zeigt das Ziel und den Status der aktuellen Sitzung über dem Eingabefeld an."
+ }
+}
diff --git a/packages/client/ui-goal/locale/es.json b/packages/client/ui-goal/locale/es.json
new file mode 100644
index 0000000000..e0135182ed
--- /dev/null
+++ b/packages/client/ui-goal/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "barra de destino de sesión",
+ "description": "Muestra el objetivo y el estado de la sesión actual encima del cuadro de entrada."
+ }
+}
diff --git a/packages/client/ui-goal/locale/fr.json b/packages/client/ui-goal/locale/fr.json
new file mode 100644
index 0000000000..2282fdbd25
--- /dev/null
+++ b/packages/client/ui-goal/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "barre de cible de session",
+ "description": "Affiche l'objectif et le statut de la session en cours au-dessus de la zone de saisie."
+ }
+}
diff --git a/packages/client/ui-goal/locale/pt.json b/packages/client/ui-goal/locale/pt.json
new file mode 100644
index 0000000000..d7e3fa30c3
--- /dev/null
+++ b/packages/client/ui-goal/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "barra de destino da sessão",
+ "description": "Exibe a meta e o status da sessão atual acima da caixa de entrada."
+ }
+}
diff --git a/packages/client/ui-goal/locale/ru.json b/packages/client/ui-goal/locale/ru.json
new file mode 100644
index 0000000000..2736841f94
--- /dev/null
+++ b/packages/client/ui-goal/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "целевая панель сеанса",
+ "description": "Отображает цель и статус текущего сеанса над полем ввода."
+ }
+}
diff --git a/packages/client/ui-goal/src/client/index.ts b/packages/client/ui-goal/src/client/index.ts
index a38de98032..7c1eadb38a 100644
--- a/packages/client/ui-goal/src/client/index.ts
+++ b/packages/client/ui-goal/src/client/index.ts
@@ -35,7 +35,7 @@ import { GoalCommandReference } from './GoalCommandReference.tsx'
import { goalCommandInputDefinition } from './goal-command-input.ts'
import { goalCommandReferenceRule } from './user-text-rules.ts'
export type {} from './user-text-rules.ts'
-import { en, ja, zh, type GoalKey } from './locales.ts'
+import { en, ja, es, fr, de, ru, pt, zh, type GoalKey } from './locales.ts'
export { GoalBar, GoalDock } from './GoalBar.tsx'
export type {
@@ -70,7 +70,7 @@ export const inject = ['slots', 'sessions', 'remote', 'remote.goals', 'locale',
export function apply(ctx: ClientContext): void {
ctx.uiConversation.events.register(goalCommandInputDefinition)
ctx.uiConversation.userText.register(goalCommandReferenceRule)
- ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-goal: dictionaries')
+ ctx.effect(() => ctx.locale.register(NS, { zh, en, ja, es, fr, de, ru, pt }), 'ui-goal: dictionaries')
ctx.slots.inject('conversation.user.text.node', () => ctx.slots.register({
name: 'conversation.user.text.node',
diff --git a/packages/client/ui-goal/src/client/locales.ts b/packages/client/ui-goal/src/client/locales.ts
index b196c8c7a9..f52946329e 100644
--- a/packages/client/ui-goal/src/client/locales.ts
+++ b/packages/client/ui-goal/src/client/locales.ts
@@ -50,3 +50,83 @@ export const ja = {
'action.edit': '目標を編集',
'action.clear': '目標をクリア',
} satisfies Record
+
+/** Spanish dictionary, checked against the English keys. */
+export const es = {
+ 'phase.active': 'Meta en curso',
+ 'phase.active.disarmed': 'Meta inactiva',
+ 'phase.paused': 'Objetivo en pausa',
+ 'phase.blocked': 'Meta bloqueada',
+ 'objective.aria': 'Objetivo de meta',
+ 'commandInput.aria': 'Entrada de comando',
+ 'action.save': 'Guardar gol',
+ 'action.cancel': 'Cancelar edición',
+ 'action.pause': 'Pausar objetivo',
+ 'action.resume': 'Reanudar objetivo',
+ 'action.edit': 'Editar objetivo',
+ 'action.clear': 'Objetivo claro',
+} satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = {
+ 'phase.active': 'Objectif continu',
+ 'phase.active.disarmed': 'Objectif inactif',
+ 'phase.paused': 'Objectif suspendu',
+ 'phase.blocked': 'Objectif bloqué',
+ 'objective.aria': 'Objectif du but',
+ 'commandInput.aria': 'Entrée de commande',
+ 'action.save': "Sauvegarder l'objectif",
+ 'action.cancel': 'Annuler la modification',
+ 'action.pause': "Suspendre l'objectif",
+ 'action.resume': "Reprendre l'objectif",
+ 'action.edit': "Modifier l'objectif",
+ 'action.clear': 'Objectif clair',
+} satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = {
+ 'phase.active': 'Fortlaufendes Ziel',
+ 'phase.active.disarmed': 'Inaktives Ziel',
+ 'phase.paused': 'Pausiertes Ziel',
+ 'phase.blocked': 'Blockiertes Ziel',
+ 'objective.aria': 'Zielziel',
+ 'commandInput.aria': 'Befehlseingabe',
+ 'action.save': 'Ziel speichern',
+ 'action.cancel': 'Bearbeitung abbrechen',
+ 'action.pause': 'Ziel pausieren',
+ 'action.resume': 'Ziel fortsetzen',
+ 'action.edit': 'Ziel bearbeiten',
+ 'action.clear': 'Klares Ziel',
+} satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = {
+ 'phase.active': 'Текущая цель',
+ 'phase.active.disarmed': 'Неактивная цель',
+ 'phase.paused': 'Приостановленная цель',
+ 'phase.blocked': 'Заблокированная цель',
+ 'objective.aria': 'Цель Цель',
+ 'commandInput.aria': 'Ввод команды',
+ 'action.save': 'Сохранить цель',
+ 'action.cancel': 'Отменить редактирование',
+ 'action.pause': 'Приостановить цель',
+ 'action.resume': 'Возобновить цель',
+ 'action.edit': 'Изменить цель',
+ 'action.clear': 'Четкая цель',
+} satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = {
+ 'phase.active': 'Meta contínua',
+ 'phase.active.disarmed': 'Meta inativa',
+ 'phase.paused': 'Meta pausada',
+ 'phase.blocked': 'Meta bloqueada',
+ 'objective.aria': 'Objetivo da meta',
+ 'commandInput.aria': 'Entrada de comando',
+ 'action.save': 'Salvar meta',
+ 'action.cancel': 'Cancelar edição',
+ 'action.pause': 'Pausar meta',
+ 'action.resume': 'Retomar meta',
+ 'action.edit': 'Editar meta',
+ 'action.clear': 'Objetivo claro',
+} satisfies Record
diff --git a/packages/client/ui-input-trigger/locale/de.json b/packages/client/ui-input-trigger/locale/de.json
new file mode 100644
index 0000000000..be4ceb8980
--- /dev/null
+++ b/packages/client/ui-input-trigger/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Geben Sie Lenovo Services ein",
+ "description": "Erkennen Sie / und @ in der Eingabe, zeigen Sie die entsprechenden Kandidaten an und wählen Sie sie aus."
+ }
+}
diff --git a/packages/client/ui-input-trigger/locale/es.json b/packages/client/ui-input-trigger/locale/es.json
new file mode 100644
index 0000000000..48be835ec5
--- /dev/null
+++ b/packages/client/ui-input-trigger/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Ingrese a los servicios de Lenovo",
+ "description": "Reconocer / y @ en la entrada, mostrar y seleccionar los candidatos correspondientes."
+ }
+}
diff --git a/packages/client/ui-input-trigger/locale/fr.json b/packages/client/ui-input-trigger/locale/fr.json
new file mode 100644
index 0000000000..225c4f9e3f
--- /dev/null
+++ b/packages/client/ui-input-trigger/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Entrez dans les services Lenovo",
+ "description": "Reconnaître / et @ dans la saisie, afficher et sélectionner les candidats correspondants."
+ }
+}
diff --git a/packages/client/ui-input-trigger/locale/pt.json b/packages/client/ui-input-trigger/locale/pt.json
new file mode 100644
index 0000000000..91b25e4363
--- /dev/null
+++ b/packages/client/ui-input-trigger/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Entre nos serviços Lenovo",
+ "description": "Reconheça / e @ na entrada, exiba e selecione os candidatos correspondentes."
+ }
+}
diff --git a/packages/client/ui-input-trigger/locale/ru.json b/packages/client/ui-input-trigger/locale/ru.json
new file mode 100644
index 0000000000..9aeeefffd6
--- /dev/null
+++ b/packages/client/ui-input-trigger/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Войдите в услуги Lenovo",
+ "description": "Распознайте / и @ во входных данных, отобразите и выберите соответствующих кандидатов."
+ }
+}
diff --git a/packages/client/ui-input-trigger/src/client/index.ts b/packages/client/ui-input-trigger/src/client/index.ts
index cad11a5452..de4186135f 100644
--- a/packages/client/ui-input-trigger/src/client/index.ts
+++ b/packages/client/ui-input-trigger/src/client/index.ts
@@ -13,7 +13,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-session/client'
import { InputTriggerService } from './service.ts'
import type { MenuViewInjected } from './slots.ts'
import { MenuView } from './MenuView.tsx'
-import { en, ja, zh, type MenuKey } from './locales.ts'
+import { en, ja, es, fr, de, ru, pt, zh, type MenuKey } from './locales.ts'
export { InputTriggerService } from './service.ts'
export { InputTriggerController } from './controller.ts'
@@ -58,7 +58,7 @@ export const inject = ['sessions', 'locale']
*/
export function apply(ctx: ClientContext): void {
ctx.plugin(InputTriggerService)
- ctx.effect(() => ctx.locale.register(MENU_NS, { zh, en, ja }), 'ui-input-trigger: menu dictionaries')
+ ctx.effect(() => ctx.locale.register(MENU_NS, { zh, en, ja, es, fr, de, ru, pt }), 'ui-input-trigger: menu dictionaries')
ctx.inject(['slots', 'inputTriggers', 'sessions'], (scope: ClientContext) => {
const inputTriggers = scope.inputTriggers
const sessions = scope.sessions
diff --git a/packages/client/ui-input-trigger/src/client/locales.ts b/packages/client/ui-input-trigger/src/client/locales.ts
index 1b11342393..63f0780d1d 100644
--- a/packages/client/ui-input-trigger/src/client/locales.ts
+++ b/packages/client/ui-input-trigger/src/client/locales.ts
@@ -45,3 +45,68 @@ export const ja = {
'crumbs.aria': 'フォルダナビゲーション',
'suggestions.aria': '候補を表示',
} satisfies Record
+
+/** Spanish dictionary, checked against the English keys. */
+export const es = {
+ 'command': 'Comandos',
+ 'skill': 'Habilidades',
+ 'subagent': 'Subagentes',
+ 'loading': 'Cargando…',
+ 'drill.aria': 'Explorar carpeta',
+ 'drill.hint': 'Explorar carpeta',
+ 'drill.key': 'Pestaña',
+ 'crumbs.aria': 'Navegación de carpetas',
+ 'suggestions.aria': 'Sugerencias de activación',
+} satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = {
+ 'command': 'Commandes',
+ 'skill': 'Compétences',
+ 'subagent': 'Sous-agents',
+ 'loading': 'Chargement…',
+ 'drill.aria': 'Parcourir le dossier',
+ 'drill.hint': 'Parcourir le dossier',
+ 'drill.key': 'Onglet',
+ 'crumbs.aria': 'Navigation dans les dossiers',
+ 'suggestions.aria': 'Suggestions de déclenchement',
+} satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = {
+ 'command': 'Befehle',
+ 'skill': 'Fähigkeiten',
+ 'subagent': 'Subagenten',
+ 'loading': 'Laden…',
+ 'drill.aria': 'Ordner durchsuchen',
+ 'drill.hint': 'Ordner durchsuchen',
+ 'drill.key': 'Tab',
+ 'crumbs.aria': 'Ordnernavigation',
+ 'suggestions.aria': 'Trigger-Vorschläge',
+} satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = {
+ 'command': 'Команды',
+ 'skill': 'Навыки',
+ 'subagent': 'Субагенты',
+ 'loading': 'Загрузка…',
+ 'drill.aria': 'Просмотреть папку',
+ 'drill.hint': 'Просмотреть папку',
+ 'drill.key': 'Вкладка',
+ 'crumbs.aria': 'Навигация по папкам',
+ 'suggestions.aria': 'Триггерные предложения',
+} satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = {
+ 'command': 'Comandos',
+ 'skill': 'Habilidades',
+ 'subagent': 'Subagentes',
+ 'loading': 'Carregando…',
+ 'drill.aria': 'Navegar na pasta',
+ 'drill.hint': 'Navegar na pasta',
+ 'drill.key': 'Guia',
+ 'crumbs.aria': 'Navegação de pastas',
+ 'suggestions.aria': 'Sugestões de gatilho',
+} satisfies Record
diff --git a/packages/client/ui-jobs/locale/de.json b/packages/client/ui-jobs/locale/de.json
new file mode 100644
index 0000000000..87cc3c7d1f
--- /dev/null
+++ b/packages/client/ui-jobs/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Hintergrundaufgabenschnittstelle",
+ "description": "Zeigen Sie Hintergrundaufgabenlisten, Status und Echtzeitausgabe innerhalb einer Sitzung an."
+ }
+}
diff --git a/packages/client/ui-jobs/locale/es.json b/packages/client/ui-jobs/locale/es.json
new file mode 100644
index 0000000000..9ce2933127
--- /dev/null
+++ b/packages/client/ui-jobs/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interfaz de tareas en segundo plano",
+ "description": "Vea listas de tareas en segundo plano, estados y resultados en tiempo real dentro de una sesión."
+ }
+}
diff --git a/packages/client/ui-jobs/locale/fr.json b/packages/client/ui-jobs/locale/fr.json
new file mode 100644
index 0000000000..e950919b81
--- /dev/null
+++ b/packages/client/ui-jobs/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface de tâche en arrière-plan",
+ "description": "Affichez les listes de tâches en arrière-plan, l'état et la sortie en temps réel au sein d'une session."
+ }
+}
diff --git a/packages/client/ui-jobs/locale/pt.json b/packages/client/ui-jobs/locale/pt.json
new file mode 100644
index 0000000000..2820658f19
--- /dev/null
+++ b/packages/client/ui-jobs/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface de tarefa em segundo plano",
+ "description": "Visualize listas de tarefas em segundo plano, status e resultados em tempo real em uma sessão."
+ }
+}
diff --git a/packages/client/ui-jobs/locale/ru.json b/packages/client/ui-jobs/locale/ru.json
new file mode 100644
index 0000000000..3d2a910429
--- /dev/null
+++ b/packages/client/ui-jobs/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Интерфейс фоновой задачи",
+ "description": "Просматривайте списки фоновых задач, статус и выходные данные в режиме реального времени в течение сеанса."
+ }
+}
diff --git a/packages/client/ui-jobs/src/client/index.ts b/packages/client/ui-jobs/src/client/index.ts
index eb5f4d920c..5174aa1fb8 100644
--- a/packages/client/ui-jobs/src/client/index.ts
+++ b/packages/client/ui-jobs/src/client/index.ts
@@ -12,7 +12,7 @@ import type {} from '@deepseek-ai/dsh-api-job-controller/client'
import type {} from '@deepseek-ai/dsh-client-locale/client'
import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
import type {} from '@deepseek-ai/dsh-client-ui-session/client'
-import { en, ja, NS, zh, type JobKey } from './locales.ts'
+import { en, ja, es, fr, de, ru, pt, NS, zh, type JobKey } from './locales.ts'
declare module '@deepseek-ai/dsh-client-ui-slots' {
interface LocaleNamespaceMap {
@@ -31,7 +31,7 @@ export const inject = ['jobs', 'slots', 'locale']
* @param ctx - client root context.
*/
export function apply(ctx: ClientContext): void {
- ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-jobs: dictionaries')
+ ctx.effect(() => ctx.locale.register(NS, { zh, en, ja, es, fr, de, ru, pt }), 'ui-jobs: dictionaries')
ctx.slots.inject(
'conversation.session.header.actions',
() => ctx.slots.register({
diff --git a/packages/client/ui-jobs/src/client/locales.ts b/packages/client/ui-jobs/src/client/locales.ts
index c1aff15b1c..700009d9a5 100644
--- a/packages/client/ui-jobs/src/client/locales.ts
+++ b/packages/client/ui-jobs/src/client/locales.ts
@@ -137,3 +137,223 @@ export const ja = {
'terminal.expand': '残り {n} 行を表示',
'terminal.expandAria': '折りたたまれた出力 {n} 行を展開',
} satisfies Record
+
+/** Spanish dictionary, checked against the English keys. */
+export const es = {
+ 'count.live.one': 'Tareas en segundo plano en ejecución: {count}',
+ 'count.live.other': 'Tareas en segundo plano en ejecución: {count}',
+ 'count.idle.one': 'Tareas en segundo plano: {count}',
+ 'count.idle.other': 'Tareas en segundo plano: {count}',
+ 'list.aria': 'Trabajos en segundo plano',
+ 'section.live': 'corriendo',
+ 'section.settledCount': 'Terminado {count}',
+ 'section.clear': 'Borrar',
+ 'row.expandAria': 'Mostrar salida en vivo de {label}',
+ 'row.collapseAria': 'Ocultar salida en vivo de {label}',
+ 'kill.stop': 'Detener tarea {label}',
+ 'kill.confirm': 'Haga clic nuevamente para confirmar',
+ 'kill.confirmAction': 'Confirmar parada',
+ 'kill.failed': 'Detener falló',
+ 'status.running': 'corriendo',
+ 'status.stopping': 'parando',
+ 'status.completed': 'completado',
+ 'status.killed': 'cancelado',
+ 'status.failed': 'falló',
+ 'duration.seconds': '{seconds}s',
+ 'duration.minutes': '{minutes}m {seconds}s',
+ 'duration.hours': '{hours}h {minutes}m',
+ 'duration.title.live': 'Corriendo para {duration}',
+ 'duration.title.done': 'Tomó {duration}',
+ 'output.gap': '… la producción anterior cayó…',
+ 'output.error': 'transmisión de salida en vivo interrumpida: {error}',
+ 'terminal.signal': 'señal {signal}',
+ 'terminal.exitCode': 'salir {code}',
+ 'terminal.noExitCode': 'sin código de salida',
+ 'terminal.commandLine': 'Línea de comando {n}',
+ 'terminal.running': 'corriendo',
+ 'terminal.failed': 'falló',
+ 'terminal.done': 'hecho',
+ 'terminal.copy': 'Copiar',
+ 'terminal.copied': 'Copiado',
+ 'terminal.noOutput': '(sin salida)',
+ 'terminal.collapse': 'Contraer',
+ 'terminal.collapseAria': 'Contraer salida',
+ 'terminal.expand': 'Mostrar {n} más líneas',
+ 'terminal.expandAria': 'Expandir líneas de salida colapsadas {n}',
+} satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = {
+ 'count.live.one': 'Tâches en arrière-plan en cours : {count}',
+ 'count.live.other': 'Tâches en arrière-plan en cours : {count}',
+ 'count.idle.one': 'Tâches en arrière-plan : {count}',
+ 'count.idle.other': 'Tâches en arrière-plan : {count}',
+ 'list.aria': 'Travaux en arrière-plan',
+ 'section.live': 'Courir',
+ 'section.settledCount': 'Terminé {count}',
+ 'section.clear': 'Effacer',
+ 'row.expandAria': 'Afficher la sortie en direct de {label}',
+ 'row.collapseAria': 'Masquer la sortie en direct de {label}',
+ 'kill.stop': 'Arrêter la tâche {label}',
+ 'kill.confirm': 'Cliquez à nouveau pour confirmer',
+ 'kill.confirmAction': "Confirmer l'arrêt",
+ 'kill.failed': "L'arrêt a échoué",
+ 'status.running': 'courir',
+ 'status.stopping': 'arrêter',
+ 'status.completed': 'terminé',
+ 'status.killed': 'annulé',
+ 'status.failed': 'échoué',
+ 'duration.seconds': '{seconds}s',
+ 'duration.minutes': '{minutes}m {seconds}s',
+ 'duration.hours': '{hours}h {minutes}m',
+ 'duration.title.live': 'Courir pour {duration}',
+ 'duration.title.done': 'A pris {duration}',
+ 'output.gap': '… la production antérieure a chuté…',
+ 'output.error': 'flux de sortie en direct interrompu : {error}',
+ 'terminal.signal': 'signal {signal}',
+ 'terminal.exitCode': 'quitter {code}',
+ 'terminal.noExitCode': 'pas de code de sortie',
+ 'terminal.commandLine': 'Ligne de commande {n}',
+ 'terminal.running': 'courir',
+ 'terminal.failed': 'échoué',
+ 'terminal.done': 'fait',
+ 'terminal.copy': 'Copier',
+ 'terminal.copied': 'Copié',
+ 'terminal.noOutput': '(pas de sortie)',
+ 'terminal.collapse': 'Réduire',
+ 'terminal.collapseAria': 'Réduire la sortie',
+ 'terminal.expand': 'Afficher {n} plus de lignes',
+ 'terminal.expandAria': 'Développer les lignes de sortie réduites {n}',
+} satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = {
+ 'count.live.one': 'Laufende Hintergrundaufgaben: {count}',
+ 'count.live.other': 'Laufende Hintergrundaufgaben: {count}',
+ 'count.idle.one': 'Hintergrundaufgaben: {count}',
+ 'count.idle.other': 'Hintergrundaufgaben: {count}',
+ 'list.aria': 'Hintergrundjobs',
+ 'section.live': 'Laufen',
+ 'section.settledCount': 'Fertig {count}',
+ 'section.clear': 'Klar',
+ 'row.expandAria': 'Live-Ausgabe von {label} anzeigen',
+ 'row.collapseAria': 'Live-Ausgabe von {label} ausblenden',
+ 'kill.stop': 'Stoppen Sie die Aufgabe {label}',
+ 'kill.confirm': 'Klicken Sie zur Bestätigung erneut',
+ 'kill.confirmAction': 'Stopp bestätigen',
+ 'kill.failed': 'Stoppen ist fehlgeschlagen',
+ 'status.running': 'laufen',
+ 'status.stopping': 'anhalten',
+ 'status.completed': 'abgeschlossen',
+ 'status.killed': 'abgesagt',
+ 'status.failed': 'gescheitert',
+ 'duration.seconds': '{seconds}s',
+ 'duration.minutes': '{minutes}m {seconds}s',
+ 'duration.hours': '{hours}h {minutes}m',
+ 'duration.title.live': 'Laufen für {duration}',
+ 'duration.title.done': 'Nahm {duration}',
+ 'output.gap': '… frühere Produktion gesunken …',
+ 'output.error': 'Live-Ausgabestream unterbrochen: {error}',
+ 'terminal.signal': 'Signal {signal}',
+ 'terminal.exitCode': 'Beenden Sie {code}',
+ 'terminal.noExitCode': 'kein Exit-Code',
+ 'terminal.commandLine': 'Befehlszeile {n}',
+ 'terminal.running': 'laufen',
+ 'terminal.failed': 'gescheitert',
+ 'terminal.done': 'erledigt',
+ 'terminal.copy': 'Kopieren',
+ 'terminal.copied': 'Kopiert',
+ 'terminal.noOutput': '(keine Ausgabe)',
+ 'terminal.collapse': 'Einklappen',
+ 'terminal.collapseAria': 'Ausgabe reduzieren',
+ 'terminal.expand': 'Zeige {n} weitere Zeilen',
+ 'terminal.expandAria': 'Erweitern Sie die minimierten Ausgabezeilen {n}',
+} satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = {
+ 'count.live.one': 'Фоновые задачи выполняются: {count}',
+ 'count.live.other': 'Фоновые задачи выполняются: {count}',
+ 'count.idle.one': 'Фоновые задачи: {count}',
+ 'count.idle.other': 'Фоновые задачи: {count}',
+ 'list.aria': 'Фоновые задания',
+ 'section.live': 'Бег',
+ 'section.settledCount': 'Закончено {count}',
+ 'section.clear': 'Очистить',
+ 'row.expandAria': 'Показать живой вывод {label}',
+ 'row.collapseAria': 'Скрыть прямой вывод {label}',
+ 'kill.stop': 'Остановить задачу {label}',
+ 'kill.confirm': 'Нажмите еще раз, чтобы подтвердить',
+ 'kill.confirmAction': 'Подтвердить остановку',
+ 'kill.failed': 'Остановить не удалось',
+ 'status.running': 'бег',
+ 'status.stopping': 'остановка',
+ 'status.completed': 'завершено',
+ 'status.killed': 'отменен',
+ 'status.failed': 'не удалось',
+ 'duration.seconds': '{seconds}s',
+ 'duration.minutes': '{minutes}m {seconds}s',
+ 'duration.hours': '{hours}h {minutes}m',
+ 'duration.title.live': 'Бегу за {duration}',
+ 'duration.title.done': 'Взял {duration}',
+ 'output.gap': '… ранее объемы производства упали …',
+ 'output.error': 'поток вывода в реальном времени прерван: {error}',
+ 'terminal.signal': 'сигнал {signal}',
+ 'terminal.exitCode': 'выход {code}',
+ 'terminal.noExitCode': 'нет кода выхода',
+ 'terminal.commandLine': 'Командная строка {n}',
+ 'terminal.running': 'бег',
+ 'terminal.failed': 'не удалось',
+ 'terminal.done': 'сделано',
+ 'terminal.copy': 'Копировать',
+ 'terminal.copied': 'Скопировано',
+ 'terminal.noOutput': '(нет вывода)',
+ 'terminal.collapse': 'Свернуть',
+ 'terminal.collapseAria': 'Свернуть вывод',
+ 'terminal.expand': 'Показать {n} больше строк',
+ 'terminal.expandAria': 'Развернуть свернутые выходные строки {n}',
+} satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = {
+ 'count.live.one': 'Tarefas em segundo plano em execução: {count}',
+ 'count.live.other': 'Tarefas em segundo plano em execução: {count}',
+ 'count.idle.one': 'Tarefas em segundo plano: {count}',
+ 'count.idle.other': 'Tarefas em segundo plano: {count}',
+ 'list.aria': 'Trabalhos em segundo plano',
+ 'section.live': 'Correndo',
+ 'section.settledCount': 'Concluído {count}',
+ 'section.clear': 'Limpar',
+ 'row.expandAria': 'Mostrar saída ao vivo de {label}',
+ 'row.collapseAria': 'Ocultar saída ao vivo de {label}',
+ 'kill.stop': 'Parar tarefa {label}',
+ 'kill.confirm': 'Clique novamente para confirmar',
+ 'kill.confirmAction': 'Confirmar parada',
+ 'kill.failed': 'Falha na parada',
+ 'status.running': 'correndo',
+ 'status.stopping': 'parando',
+ 'status.completed': 'concluído',
+ 'status.killed': 'cancelado',
+ 'status.failed': 'falhou',
+ 'duration.seconds': '{seconds}s',
+ 'duration.minutes': '{minutes}m {seconds}s',
+ 'duration.hours': '{hours}h {minutes}m',
+ 'duration.title.live': 'Correndo por {duration}',
+ 'duration.title.done': 'Levou {duration}',
+ 'output.gap': '… a produção anterior caiu…',
+ 'output.error': 'transmissão ao vivo interrompida: {error}',
+ 'terminal.signal': 'sinal {signal}',
+ 'terminal.exitCode': 'sair {code}',
+ 'terminal.noExitCode': 'sem código de saída',
+ 'terminal.commandLine': 'Linha de comando {n}',
+ 'terminal.running': 'correndo',
+ 'terminal.failed': 'falhou',
+ 'terminal.done': 'feito',
+ 'terminal.copy': 'Copiar',
+ 'terminal.copied': 'Copiado',
+ 'terminal.noOutput': '(sem saída)',
+ 'terminal.collapse': 'Recolher',
+ 'terminal.collapseAria': 'Recolher saída',
+ 'terminal.expand': 'Mostrar mais {n} linhas',
+ 'terminal.expandAria': 'Expanda {n} linhas de saída recolhidas',
+} satisfies Record
diff --git a/packages/client/ui-jobs/tests/job-list-action.client.spec.tsx b/packages/client/ui-jobs/tests/job-list-action.client.spec.tsx
index e6866c4182..4dcf892f87 100644
--- a/packages/client/ui-jobs/tests/job-list-action.client.spec.tsx
+++ b/packages/client/ui-jobs/tests/job-list-action.client.spec.tsx
@@ -5,7 +5,7 @@ import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
import type { JobsSnapshot, JobView, ObservedJob } from '@deepseek-ai/dsh-api-job-controller/client'
import type { SessionId } from '@deepseek-ai/dsh-session/types'
import { JobListAction, type JobListActionProps } from '../src/client/JobListAction.tsx'
-import { zh } from '../src/client/locales.ts'
+import { zh, ru } from '../src/client/locales.ts'
afterEach(() => {
cleanup()
@@ -590,3 +590,12 @@ describe('JobListAction human kill', () => {
expect(document.querySelector('[data-kill-state]')).toBeNull()
})
})
+
+it.each([1, 2, 5, 21])('renders count-independent Russian labels for %i jobs', (count) => {
+ const jobs = Array.from({ length: count }, (_, i) => job({ id: `job-${i}` as JobView['id'] }))
+ const translate: JobListActionProps['t'] = makeTranslate(ru)
+ const view = render()
+ expect(screen.getByRole('button', { name: `Фоновые задачи выполняются: ${count}` })).toBeDefined()
+ view.rerender( ({ ...item, status: 'completed', finishedAt: 1_700_000_003_000 })))} t={translate} />)
+ expect(screen.getByRole('button', { name: `Фоновые задачи: ${count}` })).toBeDefined()
+})
diff --git a/packages/client/ui-layout/locale/de.json b/packages/client/ui-layout/locale/de.json
new file mode 100644
index 0000000000..9d6d23789f
--- /dev/null
+++ b/packages/client/ui-layout/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Layout anwenden",
+ "description": "Organisieren Sie Navigation, Konversationen und Seitenbereiche und unterstützen Sie das Ziehen, um das Layout anzupassen."
+ }
+}
diff --git a/packages/client/ui-layout/locale/es.json b/packages/client/ui-layout/locale/es.json
new file mode 100644
index 0000000000..01d553183d
--- /dev/null
+++ b/packages/client/ui-layout/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Aplicar diseño",
+ "description": "Organice la navegación, las conversaciones y los paneles laterales, y admita arrastrar para ajustar el diseño."
+ }
+}
diff --git a/packages/client/ui-layout/locale/fr.json b/packages/client/ui-layout/locale/fr.json
new file mode 100644
index 0000000000..7526ace8cc
--- /dev/null
+++ b/packages/client/ui-layout/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Appliquer la mise en page",
+ "description": "Organisez la navigation, les conversations et les panneaux latéraux, et prenez en charge le glisser-déposer pour ajuster la mise en page."
+ }
+}
diff --git a/packages/client/ui-layout/locale/pt.json b/packages/client/ui-layout/locale/pt.json
new file mode 100644
index 0000000000..10b0dcaae8
--- /dev/null
+++ b/packages/client/ui-layout/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Aplicar layout",
+ "description": "Organize a navegação, as conversas e os painéis laterais e suporte o recurso de arrastar para ajustar o layout."
+ }
+}
diff --git a/packages/client/ui-layout/locale/ru.json b/packages/client/ui-layout/locale/ru.json
new file mode 100644
index 0000000000..ab90630f4d
--- /dev/null
+++ b/packages/client/ui-layout/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Применить макет",
+ "description": "Организуйте навигацию, диалоги и боковые панели, а также поддерживайте перетаскивание для настройки макета."
+ }
+}
diff --git a/packages/client/ui-layout/src/client/index.ts b/packages/client/ui-layout/src/client/index.ts
index fb66376cfb..ee9da22d2b 100644
--- a/packages/client/ui-layout/src/client/index.ts
+++ b/packages/client/ui-layout/src/client/index.ts
@@ -18,7 +18,7 @@ import { AppFrame } from './AppFrame.tsx'
import { createLayoutStore } from './stores.ts'
import { LayoutController } from './service.ts'
import type { ShortcutCommandId } from '@deepseek-ai/dsh-client-shortcuts/client'
-import { en, ja, zh } from './shortcut-locales.ts'
+import { en, ja, es, fr, de, ru, pt, zh } from './shortcut-locales.ts'
import { ThemePresenter } from './theme-presenter.ts'
// Contract exports only (export-convergence rule: cross-package consumers
@@ -156,7 +156,7 @@ export const inject = ['slots', 'theme', 'locale', 'shortcuts']
* @param ctx - client root context.
*/
export function apply(ctx: ClientContext): void {
- ctx.effect(() => ctx.locale.register('shortcuts.layout', { zh, en, ja }), 'layout: command labels')
+ ctx.effect(() => ctx.locale.register('shortcuts.layout', { zh, en, ja, es, fr, de, ru, pt }), 'layout: command labels')
const t = ctx.locale.bind('shortcuts.layout')
ctx.effect(() => {
diff --git a/packages/client/ui-layout/src/client/shortcut-locales.ts b/packages/client/ui-layout/src/client/shortcut-locales.ts
index dcccfc8641..c87cdc5026 100644
--- a/packages/client/ui-layout/src/client/shortcut-locales.ts
+++ b/packages/client/ui-layout/src/client/shortcut-locales.ts
@@ -7,3 +7,18 @@ export const en: Record = { toggle: 'Toggle left sideba
export const ja = {
'toggle': '左サイドバーを切り替え',
} satisfies Record
+
+/** Spanish dictionary, checked against the English keys. */
+export const es = { toggle: 'Alternar barra lateral izquierda' } satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = { toggle: 'Basculer la barre latérale gauche' } satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = { toggle: 'Linke Seitenleiste umschalten' } satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = { toggle: 'Переключить левую боковую панель' } satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = { toggle: 'Alternar barra lateral esquerda' } satisfies Record
diff --git a/packages/client/ui-message-feedback/locale/de.json b/packages/client/ui-message-feedback/locale/de.json
new file mode 100644
index 0000000000..71b5fc4559
--- /dev/null
+++ b/packages/client/ui-message-feedback/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Nachrichten-Feedback-Schnittstelle",
+ "description": "Stellen Sie „Gefällt mir“- und „Gefällt mir nicht“-Schaltflächen für Antworten und Popup-Fenster für Gesprächsfeedback bereit."
+ }
+}
diff --git a/packages/client/ui-message-feedback/locale/es.json b/packages/client/ui-message-feedback/locale/es.json
new file mode 100644
index 0000000000..26672a96ab
--- /dev/null
+++ b/packages/client/ui-message-feedback/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interfaz de comentarios de mensajes",
+ "description": "Proporcione botones Me gusta y No me gusta para las ventanas emergentes de respuestas y comentarios de la conversación."
+ }
+}
diff --git a/packages/client/ui-message-feedback/locale/fr.json b/packages/client/ui-message-feedback/locale/fr.json
new file mode 100644
index 0000000000..71009572ad
--- /dev/null
+++ b/packages/client/ui-message-feedback/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface de retour de messages",
+ "description": "Fournissez des boutons J'aime et Je n'aime pas pour les réponses et les fenêtres contextuelles de commentaires sur la conversation."
+ }
+}
diff --git a/packages/client/ui-message-feedback/locale/pt.json b/packages/client/ui-message-feedback/locale/pt.json
new file mode 100644
index 0000000000..dcb779d6b0
--- /dev/null
+++ b/packages/client/ui-message-feedback/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface de feedback de mensagens",
+ "description": "Forneça botões de gostar e não gostar para respostas e janelas pop-up de feedback de conversas."
+ }
+}
diff --git a/packages/client/ui-message-feedback/locale/ru.json b/packages/client/ui-message-feedback/locale/ru.json
new file mode 100644
index 0000000000..388f33bf3a
--- /dev/null
+++ b/packages/client/ui-message-feedback/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Интерфейс обратной связи по сообщениям",
+ "description": "Предоставьте кнопки «Мне нравится» и «Не нравится» для ответов и всплывающих окон обратной связи."
+ }
+}
diff --git a/packages/client/ui-message-feedback/src/client/index.ts b/packages/client/ui-message-feedback/src/client/index.ts
index 7d95fe469a..0d22230e13 100644
--- a/packages/client/ui-message-feedback/src/client/index.ts
+++ b/packages/client/ui-message-feedback/src/client/index.ts
@@ -26,7 +26,7 @@ import { FeedbackDialog } from './FeedbackDialog.tsx'
import { MessageFeedbackActions } from './MessageFeedbackActions.tsx'
import type { FeedbackDialogInjected, MessageFeedbackInjected } from './slots.ts'
import { FeedbackSurface } from './surface.ts'
-import { en, ja, zh } from './locales.ts'
+import { en, ja, es, fr, de, ru, pt, zh } from './locales.ts'
export type {
MessageFeedbackActionFailure, MessageFeedbackActionResult, MessageFeedbackStatus,
@@ -66,7 +66,7 @@ export const inject = ['slots', 'remote', 'remote.messageFeedback', 'remote.sess
* @param ctx - client root context.
*/
export function apply(ctx: ClientContext): void {
- ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-message-feedback: dictionaries')
+ ctx.effect(() => ctx.locale.register(NS, { zh, en, ja, es, fr, de, ru, pt }), 'ui-message-feedback: dictionaries')
const surfaces = new Map()
const surfaceFor = (sessionId: SessionId): FeedbackSurface => {
diff --git a/packages/client/ui-message-feedback/src/client/locales.ts b/packages/client/ui-message-feedback/src/client/locales.ts
index f55054c71d..d5a0fc66e3 100644
--- a/packages/client/ui-message-feedback/src/client/locales.ts
+++ b/packages/client/ui-message-feedback/src/client/locales.ts
@@ -81,3 +81,123 @@ export const ja = {
'error.generic': 'フィードバックを保存できませんでした',
'error.noteTooLarge': '説明が長すぎます。短くして再送信してください',
} satisfies Record
+
+/** Spanish dictionary, checked against the English keys. */
+export const es = {
+ 'action.like': 'buena respuesta',
+ 'action.likeActive': 'Eliminar calificación',
+ 'action.dislike': 'Mala respuesta',
+ 'action.dislikeActive': 'Eliminar calificación',
+ 'dialog.title': 'Enviar comentarios',
+ 'dialog.categories': 'Categoría de comentarios',
+ 'dialog.detail': 'Detalles de comentarios',
+ 'dialog.hint': 'Añade detalles para ayudarnos a mejorar. Su envío incluirá el registro de conversación actual.',
+ 'category.task-result': 'Resultado de la tarea',
+ 'category.instruction-following': 'Comprensión y seguimiento de instrucciones.',
+ 'category.product-interaction': 'Características del producto e interacción.',
+ 'category.service-stability': 'Estabilidad y velocidad',
+ 'category.resource-cost': 'Uso y costo de recursos',
+ 'category.security-privacy-permission': 'Seguridad, privacidad y permisos',
+ 'category.other': 'Otro',
+ 'toast.recorded': 'Gracias por tus comentarios',
+ 'error.conflict': 'Esta retroalimentación cambió en otros lugares; se muestra el último estado',
+ 'error.load': 'No se pudieron cargar los comentarios',
+ 'error.generic': 'No se pudieron guardar los comentarios',
+ 'error.noteTooLarge': 'La descripción es demasiado larga; acortarlo y enviarlo de nuevo',
+} satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = {
+ 'action.like': 'Bonne réponse',
+ 'action.likeActive': 'Supprimer la note',
+ 'action.dislike': 'Mauvaise réponse',
+ 'action.dislikeActive': 'Supprimer la note',
+ 'dialog.title': 'Soumettre des commentaires',
+ 'dialog.categories': 'Catégorie de commentaires',
+ 'dialog.detail': 'Détails des commentaires',
+ 'dialog.hint': 'Ajoutez des détails pour nous aider à nous améliorer. Votre soumission inclura le journal de conversation actuel.',
+ 'category.task-result': 'Résultat de la tâche',
+ 'category.instruction-following': 'Compréhension et suivi des instructions',
+ 'category.product-interaction': 'Caractéristiques du produit et interaction',
+ 'category.service-stability': 'Stabilité et vitesse',
+ 'category.resource-cost': 'Utilisation et coût des ressources',
+ 'category.security-privacy-permission': 'Sécurité, confidentialité et autorisations',
+ 'category.other': 'Autre',
+ 'toast.recorded': 'Merci pour vos commentaires',
+ 'error.conflict': 'Cette rétroaction a changé ailleurs ; le dernier état est affiché',
+ 'error.load': 'Impossible de charger les commentaires',
+ 'error.generic': "Impossible d'enregistrer les commentaires",
+ 'error.noteTooLarge': 'La description est trop longue ; raccourcissez-le et soumettez-le à nouveau',
+} satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = {
+ 'action.like': 'Gute Resonanz',
+ 'action.likeActive': 'Bewertung entfernen',
+ 'action.dislike': 'Schlechte Reaktion',
+ 'action.dislikeActive': 'Bewertung entfernen',
+ 'dialog.title': 'Feedback abgeben',
+ 'dialog.categories': 'Feedback-Kategorie',
+ 'dialog.detail': 'Feedback-Details',
+ 'dialog.hint': 'Fügen Sie Details hinzu, um uns bei der Verbesserung zu helfen. Ihre Einreichung enthält das aktuelle Gesprächsprotokoll.',
+ 'category.task-result': 'Aufgabenergebnis',
+ 'category.instruction-following': 'Anweisungen verstehen und befolgen',
+ 'category.product-interaction': 'Produkteigenschaften und Interaktion',
+ 'category.service-stability': 'Stabilität und Geschwindigkeit',
+ 'category.resource-cost': 'Ressourcennutzung und Kosten',
+ 'category.security-privacy-permission': 'Sicherheit, Datenschutz und Berechtigungen',
+ 'category.other': 'Andere',
+ 'toast.recorded': 'Vielen Dank für Ihr Feedback',
+ 'error.conflict': 'Dieses Feedback hat sich an anderer Stelle geändert; der aktuelle Stand wird angezeigt',
+ 'error.load': 'Feedback konnte nicht geladen werden',
+ 'error.generic': 'Das Feedback konnte nicht gespeichert werden',
+ 'error.noteTooLarge': 'Die Beschreibung ist zu lang; kürzen Sie es und reichen Sie es erneut ein',
+} satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = {
+ 'action.like': 'Хороший ответ',
+ 'action.likeActive': 'Удалить оценку',
+ 'action.dislike': 'Плохой ответ',
+ 'action.dislikeActive': 'Удалить оценку',
+ 'dialog.title': 'Отправить отзыв',
+ 'dialog.categories': 'Категория обратной связи',
+ 'dialog.detail': 'Подробности обратной связи',
+ 'dialog.hint': 'Добавьте детали, чтобы помочь нам стать лучше. Ваше сообщение будет включать текущий журнал разговоров.',
+ 'category.task-result': 'Результат задачи',
+ 'category.instruction-following': 'Понимание и следование инструкциям',
+ 'category.product-interaction': 'Особенности продукта и взаимодействие',
+ 'category.service-stability': 'Стабильность и скорость',
+ 'category.resource-cost': 'Использование ресурсов и стоимость',
+ 'category.security-privacy-permission': 'Безопасность, конфиденциальность и разрешения',
+ 'category.other': 'Другое',
+ 'toast.recorded': 'Спасибо за ваш отзыв',
+ 'error.conflict': 'Этот отзыв изменился в другом месте; отображается последнее состояние',
+ 'error.load': 'Не удалось загрузить отзыв.',
+ 'error.generic': 'Не удалось сохранить отзыв',
+ 'error.noteTooLarge': 'Описание слишком длинное; сократите его и отправьте снова',
+} satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = {
+ 'action.like': 'Boa resposta',
+ 'action.likeActive': 'Remover avaliação',
+ 'action.dislike': 'Resposta ruim',
+ 'action.dislikeActive': 'Remover avaliação',
+ 'dialog.title': 'Enviar comentários',
+ 'dialog.categories': 'Categoria de comentários',
+ 'dialog.detail': 'Detalhes do feedback',
+ 'dialog.hint': 'Adicione detalhes para nos ajudar a melhorar. Seu envio incluirá o registro da conversa atual.',
+ 'category.task-result': 'Resultado da tarefa',
+ 'category.instruction-following': 'Compreensão e seguimento das instruções',
+ 'category.product-interaction': 'Recursos e interação do produto',
+ 'category.service-stability': 'Estabilidade e velocidade',
+ 'category.resource-cost': 'Uso e custo de recursos',
+ 'category.security-privacy-permission': 'Segurança, privacidade e permissões',
+ 'category.other': 'Outro',
+ 'toast.recorded': 'Obrigado pelo seu feedback',
+ 'error.conflict': 'Este feedback mudou em outro lugar; o estado mais recente é mostrado',
+ 'error.load': 'Não foi possível carregar o feedback',
+ 'error.generic': 'Não foi possível salvar o feedback',
+ 'error.noteTooLarge': 'A descrição é muito longa; encurte-o e envie novamente',
+} satisfies Record
diff --git a/packages/client/ui-model-selection/locale/de.json b/packages/client/ui-model-selection/locale/de.json
new file mode 100644
index 0000000000..c37cd4ec24
--- /dev/null
+++ b/packages/client/ui-model-selection/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Schnittstelle zur Modellauswahl",
+ "description": "Sehen Sie sich die verfügbaren Modelle an und wechseln Sie das von der aktuellen Sitzung verwendete Modell."
+ }
+}
diff --git a/packages/client/ui-model-selection/locale/es.json b/packages/client/ui-model-selection/locale/es.json
new file mode 100644
index 0000000000..4ce95350a1
--- /dev/null
+++ b/packages/client/ui-model-selection/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interfaz de selección de modelo",
+ "description": "Vea los modelos disponibles y cambie el modelo utilizado por la sesión actual."
+ }
+}
diff --git a/packages/client/ui-model-selection/locale/fr.json b/packages/client/ui-model-selection/locale/fr.json
new file mode 100644
index 0000000000..bc3548d52e
--- /dev/null
+++ b/packages/client/ui-model-selection/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface de sélection de modèle",
+ "description": "Affichez les modèles disponibles et changez le modèle utilisé par la session en cours."
+ }
+}
diff --git a/packages/client/ui-model-selection/locale/pt.json b/packages/client/ui-model-selection/locale/pt.json
new file mode 100644
index 0000000000..17caef7c92
--- /dev/null
+++ b/packages/client/ui-model-selection/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface de seleção de modelo",
+ "description": "Visualize os modelos disponíveis e alterne o modelo usado pela sessão atual."
+ }
+}
diff --git a/packages/client/ui-model-selection/locale/ru.json b/packages/client/ui-model-selection/locale/ru.json
new file mode 100644
index 0000000000..572c9b5541
--- /dev/null
+++ b/packages/client/ui-model-selection/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Интерфейс выбора модели",
+ "description": "Просмотрите доступные модели и переключите модель, используемую в текущем сеансе."
+ }
+}
diff --git a/packages/client/ui-model-selection/src/client/index.ts b/packages/client/ui-model-selection/src/client/index.ts
index ae47f156ed..f1f15a1154 100644
--- a/packages/client/ui-model-selection/src/client/index.ts
+++ b/packages/client/ui-model-selection/src/client/index.ts
@@ -27,7 +27,7 @@ import type { ModelDirectoryState } from './directory.ts'
import { ModelDirectoryResolver } from './service.ts'
import type { ModelSelectInjected } from './slots.ts'
import { ModelSelect } from './ModelSelect.tsx'
-import { en, ja, zh, type ModelKey } from './locales.ts'
+import { en, ja, es, fr, de, ru, pt, zh, type ModelKey } from './locales.ts'
import { orderModelProviders } from './provider-order.ts'
export { ModelDirectory } from './directory.ts'
@@ -113,7 +113,7 @@ export const inject = ['commandUi', 'locale', 'sessions', 'slots', 'remote', 're
* @param ctx - client root context.
*/
export function apply(ctx: ClientContext): void {
- ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-model-selection: dictionaries')
+ ctx.effect(() => ctx.locale.register(NS, { zh, en, ja, es, fr, de, ru, pt }), 'ui-model-selection: dictionaries')
// Non-slot faces (the command description, the popup option builder) read
// through the bound translate; the seat component reads the standard seat.
diff --git a/packages/client/ui-model-selection/src/client/locales.ts b/packages/client/ui-model-selection/src/client/locales.ts
index 4e931226ea..cb2749f9a9 100644
--- a/packages/client/ui-model-selection/src/client/locales.ts
+++ b/packages/client/ui-model-selection/src/client/locales.ts
@@ -91,3 +91,138 @@ export const ja = {
'empty.models': '利用可能なモデルがありません。',
'empty.efforts': 'このモデルには推論強度の設定がありません。',
} satisfies Record
+
+/** Spanish dictionary, checked against the English keys. */
+export const es = {
+ 'provider.account': 'Cuenta DeepSeek',
+ 'command.label': 'modelo',
+ 'command.description': 'Selecciona el modelo para esta conversación',
+ 'option.loadError': 'No se pudo cargar el catálogo: {message}',
+ 'trigger.fallback': 'Seleccionar modelo',
+ 'trigger.loading': 'Cargando modelos…',
+ 'trigger.selectAria': 'Seleccionar modelo',
+ 'trigger.aria': 'Seleccionar modelo, actual {model}',
+ 'trigger.ariaEffort': 'Seleccionar modelo, {model} actual, esfuerzo de razonamiento {effort}',
+ 'menu.aria': 'Esfuerzo de modelo y razonamiento.',
+ 'menu.model': 'modelo',
+ 'menu.effort': 'Esfuerzo',
+ 'effort.providerDefault': 'Predeterminado',
+ 'status.loading': 'Lista de modelos refrescante...',
+ 'error.action': 'Error en la operación del modelo: {message}',
+ 'error.sessionInUse': 'Esta sesión ya está en uso, posiblemente por otra instancia de DSH en ejecución (como la web dsh o la aplicación de escritorio). Salga de otras instancias de DSH en ejecución y vuelva a intentarlo.',
+ 'action.reload': 'recargar',
+ 'warning.groupLoad': '{name} no pudo cargar: {message}',
+ 'search.placeholder': 'Buscar modelos…',
+ 'search.clear': 'Borrar búsqueda',
+ 'search.empty': 'No hay modelos coincidentes.',
+ 'empty.models': 'No hay modelos disponibles.',
+ 'empty.efforts': 'Este modelo no proporciona niveles de esfuerzo de razonamiento.',
+} satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = {
+ 'provider.account': 'Compte DeepSeek',
+ 'command.label': 'Modèle',
+ 'command.description': 'Sélectionnez le modèle pour cette conversation',
+ 'option.loadError': 'Échec du chargement du catalogue : {message}',
+ 'trigger.fallback': 'Sélectionnez le modèle',
+ 'trigger.loading': 'Chargement des modèles…',
+ 'trigger.selectAria': 'Sélectionnez le modèle',
+ 'trigger.aria': 'Sélectionnez le modèle, {model} actuel',
+ 'trigger.ariaEffort': 'Sélectionnez le modèle, {model} actuel, effort de raisonnement {effort}',
+ 'menu.aria': 'Effort de modélisation et de raisonnement',
+ 'menu.model': 'Modèle',
+ 'menu.effort': 'Effort',
+ 'effort.providerDefault': 'Par défaut',
+ 'status.loading': 'Liste des modèles actualisée…',
+ 'error.action': "Échec de l'opération du modèle : {message}",
+ 'error.sessionInUse': "Cette session est déjà utilisée, éventuellement par une autre instance DSH en cours d'exécution (telle que le Web dsh ou l'application de bureau). Quittez les autres instances DSH en cours d'exécution et réessayez.",
+ 'action.reload': 'Recharger',
+ 'warning.groupLoad': 'Échec du chargement de {name} : {message}',
+ 'search.placeholder': 'Rechercher des modèles…',
+ 'search.clear': 'Effacer la recherche',
+ 'search.empty': 'Aucun modèle correspondant.',
+ 'empty.models': 'Aucun modèle disponible.',
+ 'empty.efforts': 'Ce modèle ne fournit aucun niveau d’effort de raisonnement.',
+} satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = {
+ 'provider.account': 'DeepSeek-Konto',
+ 'command.label': 'Modell',
+ 'command.description': 'Wählen Sie das Modell für dieses Gespräch aus',
+ 'option.loadError': 'Katalog konnte nicht geladen werden: {message}',
+ 'trigger.fallback': 'Modell auswählen',
+ 'trigger.loading': 'Modelle werden geladen…',
+ 'trigger.selectAria': 'Modell auswählen',
+ 'trigger.aria': 'Modell auswählen, aktuell {model}',
+ 'trigger.ariaEffort': 'Modell auswählen, aktuell {model}, Argumentationsaufwand {effort}',
+ 'menu.aria': 'Modell- und Argumentationsaufwand',
+ 'menu.model': 'Modell',
+ 'menu.effort': 'Aufwand',
+ 'effort.providerDefault': 'Standard',
+ 'status.loading': 'Erfrischende Modellliste…',
+ 'error.action': 'Modellvorgang fehlgeschlagen: {message}',
+ 'error.sessionInUse': 'Diese Sitzung wird bereits verwendet, möglicherweise von einer anderen laufenden DSH-Instanz (z. B. dsh Web oder der Desktop-App). Beenden Sie andere laufende DSH-Instanzen und versuchen Sie es erneut.',
+ 'action.reload': 'Neu laden',
+ 'warning.groupLoad': '{name} konnte nicht geladen werden: {message}',
+ 'search.placeholder': 'Modelle suchen…',
+ 'search.clear': 'Suche löschen',
+ 'search.empty': 'Keine passenden Modelle.',
+ 'empty.models': 'Keine Modelle verfügbar.',
+ 'empty.efforts': 'Dieses Modell liefert keine Argumentationsaufwandsstufen.',
+} satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = {
+ 'provider.account': 'DeepSeek Аккаунт',
+ 'command.label': 'Модель',
+ 'command.description': 'Выберите модель для этого разговора',
+ 'option.loadError': 'Не удалось загрузить каталог: {message}.',
+ 'trigger.fallback': 'Выберите модель',
+ 'trigger.loading': 'Загрузка моделей…',
+ 'trigger.selectAria': 'Выберите модель',
+ 'trigger.aria': 'Выберите модель, текущую {model}',
+ 'trigger.ariaEffort': 'Выберите модель, текущую версию {model}, аргументацию {effort}',
+ 'menu.aria': 'Модель и рассуждения',
+ 'menu.model': 'Модель',
+ 'menu.effort': 'Усилие',
+ 'effort.providerDefault': 'По умолчанию',
+ 'status.loading': 'Обновление списка моделей…',
+ 'error.action': 'Не удалось выполнить операцию модели: {message}.',
+ 'error.sessionInUse': 'Этот сеанс уже используется, возможно, другим работающим экземпляром DSH (например, веб-сайтом dsh или настольным приложением). Закройте другие работающие экземпляры DSH и повторите попытку.',
+ 'action.reload': 'Перезагрузить',
+ 'warning.groupLoad': '{name} не удалось загрузить: {message}',
+ 'search.placeholder': 'Поиск моделей…',
+ 'search.clear': 'Очистить поиск',
+ 'search.empty': 'Нет подходящих моделей.',
+ 'empty.models': 'Нет доступных моделей.',
+ 'empty.efforts': 'Эта модель не обеспечивает обоснования уровней усилий.',
+} satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = {
+ 'provider.account': 'DeepSeek Conta',
+ 'command.label': 'Modelo',
+ 'command.description': 'Selecione o modelo para esta conversa',
+ 'option.loadError': 'Falha ao carregar o catálogo: {message}',
+ 'trigger.fallback': 'Selecione o modelo',
+ 'trigger.loading': 'Carregando modelos…',
+ 'trigger.selectAria': 'Selecione o modelo',
+ 'trigger.aria': 'Selecione o modelo, atual {model}',
+ 'trigger.ariaEffort': 'Selecione modelo, atual {model}, esforço de raciocínio {effort}',
+ 'menu.aria': 'Modelo e esforço de raciocínio',
+ 'menu.model': 'Modelo',
+ 'menu.effort': 'Esforço',
+ 'effort.providerDefault': 'Padrão',
+ 'status.loading': 'Atualizando lista de modelos…',
+ 'error.action': 'Falha na operação do modelo: {message}',
+ 'error.sessionInUse': 'Esta sessão já está em uso, possivelmente por outra instância DSH em execução (como dsh web ou aplicativo de desktop). Saia de outras instâncias DSH em execução e tente novamente.',
+ 'action.reload': 'Recarregar',
+ 'warning.groupLoad': '{name} falhou ao carregar: {message}',
+ 'search.placeholder': 'Pesquisar modelos…',
+ 'search.clear': 'Limpar pesquisa',
+ 'search.empty': 'Não há modelos correspondentes.',
+ 'empty.models': 'Nenhum modelo disponível.',
+ 'empty.efforts': 'Este modelo não fornece níveis de esforço de raciocínio.',
+} satisfies Record
diff --git a/packages/client/ui-open-in-app/locale/de.json b/packages/client/ui-open-in-app/locale/de.json
new file mode 100644
index 0000000000..f5b24225cb
--- /dev/null
+++ b/packages/client/ui-open-in-app/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Schnittstelle zum Öffnen externer Anwendungen",
+ "description": "Bietet den Eintrag „Öffnen mit“ in der Sitzungs- und Dokumentvorschau."
+ }
+}
diff --git a/packages/client/ui-open-in-app/locale/es.json b/packages/client/ui-open-in-app/locale/es.json
new file mode 100644
index 0000000000..1164cb3c6b
--- /dev/null
+++ b/packages/client/ui-open-in-app/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interfaz de apertura de aplicaciones externas",
+ "description": "Proporciona entrada \"Abrir con\" en la sesión y vista previa del documento."
+ }
+}
diff --git a/packages/client/ui-open-in-app/locale/fr.json b/packages/client/ui-open-in-app/locale/fr.json
new file mode 100644
index 0000000000..5f104625e3
--- /dev/null
+++ b/packages/client/ui-open-in-app/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface d'ouverture d'application externe",
+ "description": "Fournit une entrée « Ouvrir avec » dans la session et un aperçu du document."
+ }
+}
diff --git a/packages/client/ui-open-in-app/locale/pt.json b/packages/client/ui-open-in-app/locale/pt.json
new file mode 100644
index 0000000000..105759e7e0
--- /dev/null
+++ b/packages/client/ui-open-in-app/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface de abertura de aplicativo externo",
+ "description": "Fornece entrada \"Abrir com\" na sessão e visualização do documento."
+ }
+}
diff --git a/packages/client/ui-open-in-app/locale/ru.json b/packages/client/ui-open-in-app/locale/ru.json
new file mode 100644
index 0000000000..2655015ac7
--- /dev/null
+++ b/packages/client/ui-open-in-app/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Интерфейс открытия внешнего приложения",
+ "description": "Обеспечивает запись «Открыть с помощью» в сеансе и предварительном просмотре документа."
+ }
+}
diff --git a/packages/client/ui-open-in-app/src/client/index.ts b/packages/client/ui-open-in-app/src/client/index.ts
index db99a2a23e..8da9f15f9e 100644
--- a/packages/client/ui-open-in-app/src/client/index.ts
+++ b/packages/client/ui-open-in-app/src/client/index.ts
@@ -25,7 +25,7 @@ import { OpenInAppPathController } from './open-path.ts'
import { OpenPathAction, type OpenPathInjected } from './OpenPathAction.tsx'
import { FileRouteAction } from './FileRouteAction.tsx'
import { OpenPathEmptyAction } from './OpenPathEmptyAction.tsx'
-import { en, ja, NS, zh, type OpenInAppKey } from './locales.ts'
+import { en, ja, es, fr, de, ru, pt, NS, zh, type OpenInAppKey } from './locales.ts'
declare module '@deepseek-ai/dsh-client-ui-slots' {
interface LocaleNamespaceMap {
@@ -51,7 +51,7 @@ export function apply(ctx: ClientContext): void {
const controller = new OpenInAppController()
void controller.load()
const paths = new OpenInAppPathController(ctx.remote)
- ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'open-in-app: dictionaries')
+ ctx.effect(() => ctx.locale.register(NS, { zh, en, ja, es, fr, de, ru, pt }), 'open-in-app: dictionaries')
const t = ctx.locale.bind(NS)
const target = () => {
if (ctx.layout.panelInfo.getSnapshot().activePanelId !== null) return undefined
diff --git a/packages/client/ui-open-in-app/src/client/locales.ts b/packages/client/ui-open-in-app/src/client/locales.ts
index f43d4746bb..737b027d00 100644
--- a/packages/client/ui-open-in-app/src/client/locales.ts
+++ b/packages/client/ui-open-in-app/src/client/locales.ts
@@ -99,3 +99,103 @@ export const ja = {
'app.filemanager': 'ファイル',
'app.terminal': 'ターミナル',
} satisfies Record
+
+/** Spanish dictionary, checked against the English keys. */
+export const es = {
+ 'open.title': 'Abrir en {app}',
+ 'path.appDefault': '{app} (predeterminado)',
+ 'path.appsError': 'No se pudieron cargar aplicaciones',
+ 'shortcut.busy': 'Abrir espacio de trabajo',
+ 'shortcut.unavailable': 'El espacio de trabajo actual o la aplicación local no están disponibles',
+ 'open.tooltip': 'Abierto localmente',
+ 'path.open': 'Abrir',
+ 'path.more': 'Más formas de abrir',
+ 'path.reveal': 'Mostrar ubicación del archivo',
+ 'path.openError': 'No se pudo abrir. Intentar otra vez.',
+ 'path.revealError': 'No se pudo mostrar la ubicación del archivo. Intentar otra vez.',
+ ...PRODUCT_NAMES,
+ 'app.finder': 'Finder',
+ 'app.explorer': 'Explorador de archivos',
+ 'app.filemanager': 'Archivos',
+ 'app.terminal': 'terminales',
+} satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = {
+ 'open.title': 'Ouvrir dans {app}',
+ 'path.appDefault': '{app} (par défaut)',
+ 'path.appsError': 'Impossible de charger les applications',
+ 'shortcut.busy': "Ouverture de l'espace de travail",
+ 'shortcut.unavailable': 'Espace de travail actuel ou application locale indisponible',
+ 'open.tooltip': 'Ouvrir localement',
+ 'path.open': 'Ouvrir',
+ 'path.more': "Plus de façons d'ouvrir",
+ 'path.reveal': "Afficher l'emplacement du fichier",
+ 'path.openError': "Impossible d'ouvrir. Essayer à nouveau.",
+ 'path.revealError': "Impossible d'afficher l'emplacement du fichier. Essayer à nouveau.",
+ ...PRODUCT_NAMES,
+ 'app.finder': 'Finder',
+ 'app.explorer': 'Explorateur de fichiers',
+ 'app.filemanager': 'Fichiers',
+ 'app.terminal': 'Borne',
+} satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = {
+ 'open.title': 'In {app} öffnen',
+ 'path.appDefault': '{app} (Standard)',
+ 'path.appsError': 'Anwendungen konnten nicht geladen werden',
+ 'shortcut.busy': 'Arbeitsbereich öffnen',
+ 'shortcut.unavailable': 'Aktueller Arbeitsbereich oder lokale Anwendung nicht verfügbar',
+ 'open.tooltip': 'Vor Ort geöffnet',
+ 'path.open': 'Öffnen',
+ 'path.more': 'Mehr Möglichkeiten zum Öffnen',
+ 'path.reveal': 'Dateispeicherort anzeigen',
+ 'path.openError': 'Konnte nicht geöffnet werden. Versuchen Sie es erneut.',
+ 'path.revealError': 'Der Dateispeicherort konnte nicht angezeigt werden. Versuchen Sie es erneut.',
+ ...PRODUCT_NAMES,
+ 'app.finder': 'Finder',
+ 'app.explorer': 'Datei-Explorer',
+ 'app.filemanager': 'Dateien',
+ 'app.terminal': 'Terminal',
+} satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = {
+ 'open.title': 'Открыть в {app}',
+ 'path.appDefault': '{app} (по умолчанию)',
+ 'path.appsError': 'Не удалось загрузить приложения',
+ 'shortcut.busy': 'Открытие рабочей области',
+ 'shortcut.unavailable': 'Текущая рабочая область или локальное приложение недоступно.',
+ 'open.tooltip': 'Открыть локально',
+ 'path.open': 'Открыть',
+ 'path.more': 'Больше способов открыть',
+ 'path.reveal': 'Показать местоположение файла',
+ 'path.openError': 'Не удалось открыть. Попробуйте еще раз.',
+ 'path.revealError': 'Не удалось показать местоположение файла. Попробуйте еще раз.',
+ ...PRODUCT_NAMES,
+ 'app.finder': 'Finder',
+ 'app.explorer': 'Проводник файлов',
+ 'app.filemanager': 'Файлы',
+ 'app.terminal': 'Терминал',
+} satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = {
+ 'open.title': 'Abrir em {app}',
+ 'path.appDefault': '{app} (padrão)',
+ 'path.appsError': 'Não foi possível carregar aplicativos',
+ 'shortcut.busy': 'Abrindo espaço de trabalho',
+ 'shortcut.unavailable': 'Espaço de trabalho atual ou aplicativo local indisponível',
+ 'open.tooltip': 'Abrir localmente',
+ 'path.open': 'Abrir',
+ 'path.more': 'Mais maneiras de abrir',
+ 'path.reveal': 'Mostrar localização do arquivo',
+ 'path.openError': 'Não foi possível abrir. Tente novamente.',
+ 'path.revealError': 'Não foi possível mostrar o local do arquivo. Tente novamente.',
+ ...PRODUCT_NAMES,
+ 'app.finder': 'Finder',
+ 'app.explorer': 'Explorador de arquivos',
+ 'app.filemanager': 'Arquivos',
+ 'app.terminal': 'Terminais',
+} satisfies Record
diff --git a/packages/client/ui-permission-presets/locale/de.json b/packages/client/ui-permission-presets/locale/de.json
new file mode 100644
index 0000000000..0f2614b5c2
--- /dev/null
+++ b/packages/client/ui-permission-presets/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Schnittstelle für den Berechtigungsmodus",
+ "description": "Legt die Standardberechtigungen für neue Sitzungen fest und wechselt den Berechtigungsmodus für die aktuelle Sitzung."
+ }
+}
diff --git a/packages/client/ui-permission-presets/locale/es.json b/packages/client/ui-permission-presets/locale/es.json
new file mode 100644
index 0000000000..aed991a5c6
--- /dev/null
+++ b/packages/client/ui-permission-presets/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interfaz de modo de permiso",
+ "description": "Establece los permisos predeterminados para nuevas sesiones y cambia el modo de permisos para la sesión actual."
+ }
+}
diff --git a/packages/client/ui-permission-presets/locale/fr.json b/packages/client/ui-permission-presets/locale/fr.json
new file mode 100644
index 0000000000..8119ff4849
--- /dev/null
+++ b/packages/client/ui-permission-presets/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface du mode autorisation",
+ "description": "Définit les autorisations par défaut pour les nouvelles sessions et change le mode d'autorisations pour la session en cours."
+ }
+}
diff --git a/packages/client/ui-permission-presets/locale/pt.json b/packages/client/ui-permission-presets/locale/pt.json
new file mode 100644
index 0000000000..fc65c0a5d0
--- /dev/null
+++ b/packages/client/ui-permission-presets/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface do modo de permissão",
+ "description": "Define as permissões padrão para novas sessões e alterna o modo de permissões para a sessão atual."
+ }
+}
diff --git a/packages/client/ui-permission-presets/locale/ru.json b/packages/client/ui-permission-presets/locale/ru.json
new file mode 100644
index 0000000000..74ad24f1d4
--- /dev/null
+++ b/packages/client/ui-permission-presets/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Интерфейс режима разрешений",
+ "description": "Устанавливает разрешения по умолчанию для новых сеансов и переключает режим разрешений для текущего сеанса."
+ }
+}
diff --git a/packages/client/ui-permission-presets/src/client/index.ts b/packages/client/ui-permission-presets/src/client/index.ts
index 940b5baa3f..8f4d98c854 100644
--- a/packages/client/ui-permission-presets/src/client/index.ts
+++ b/packages/client/ui-permission-presets/src/client/index.ts
@@ -41,7 +41,7 @@ import type { PermissionSelectInjected } from './PermissionSelect.tsx'
import { PermissionRow } from './PermissionRow.tsx'
import type { PermissionRowInjected } from './PermissionRow.tsx'
import {
- accessEn, accessJa, accessZh, en, ja, PERMISSION_ACCESS_NS, zh,
+ accessEn, accessJa, accessEs, accessFr, accessDe, accessRu, accessPt, accessZh, en, ja, es, fr, de, ru, pt, PERMISSION_ACCESS_NS, zh,
} from './locales.ts'
import {
AUTO_REVIEW_PRESET, displayPermissionPreset, FULL_ACCESS_PRESET,
@@ -113,7 +113,16 @@ function optionsOf(
export function apply(ctx: ClientContext): void {
const command = ctx.get('commandUi') as CommandUiContract
const sessions = ctx.sessions
- const accessDictionaries = { zh: accessZh, en: accessEn, ja: accessJa }
+ const accessDictionaries = {
+ zh: accessZh,
+ en: accessEn,
+ ja: accessJa,
+ es: accessEs,
+ fr: accessFr,
+ de: accessDe,
+ ru: accessRu,
+ pt: accessPt,
+ }
ctx.effect(
() => ctx.locale.register(PERMISSION_ACCESS_NS, accessDictionaries),
'ui-permission: current-session dictionaries',
@@ -142,7 +151,7 @@ export function apply(ctx: ClientContext): void {
'ui-permission: dismiss stale slash choices',
)
- ctx.effect(() => ctx.locale.register('settings.permission', { zh, en, ja }), 'ui-permission: settings row dictionaries')
+ ctx.effect(() => ctx.locale.register('settings.permission', { zh, en, ja, es, fr, de, ru, pt }), 'ui-permission: settings row dictionaries')
// The shared ConfigForm mirror updates after document commits and reconnects.
const controller = new PermissionPresetSettingsController(
diff --git a/packages/client/ui-permission-presets/src/client/locales.ts b/packages/client/ui-permission-presets/src/client/locales.ts
index 792c1d5605..9fe5025f87 100644
--- a/packages/client/ui-permission-presets/src/client/locales.ts
+++ b/packages/client/ui-permission-presets/src/client/locales.ts
@@ -99,6 +99,86 @@ export const ja = {
'confirm.enable': 'フルアクセスを有効にする',
} satisfies Record
+/** Spanish dictionary, checked against the English keys. */
+export const es = {
+ 'title': 'Permiso',
+ 'description': 'Elija el modo de permiso predeterminado para nuevas sesiones',
+ 'loading': 'Cargando',
+ 'unavailable': 'No disponible',
+ 'preset.readOnly': 'Sólo lectura',
+ 'preset.workspaceWrite': 'Escritura en el espacio de trabajo',
+ 'preset.fullAccess': 'Acceso completo',
+ 'confirm.title': '¿Habilitar acceso completo?',
+ 'confirm.description': 'El acceso completo permite que las nuevas sesiones reduzcan los pasos de confirmación y realicen más acciones directamente, incluidas operaciones confidenciales, cambios de archivos o comandos externos. Úselo sólo cuando confíe en las tareas posteriores.',
+ 'confirm.acknowledge': 'Entiendo los riesgos y quiero continuar.',
+ 'confirm.cancel': 'Cancelar',
+ 'confirm.enable': 'Habilitar acceso completo',
+} satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = {
+ 'title': 'Autorisation',
+ 'description': "Choisissez le mode d'autorisation par défaut pour les nouvelles sessions",
+ 'loading': 'Chargement',
+ 'unavailable': 'Indisponible',
+ 'preset.readOnly': 'Lecture seule',
+ 'preset.workspaceWrite': "Écriture dans l'espace de travail",
+ 'preset.fullAccess': 'Accès complet',
+ 'confirm.title': "Activer l'accès complet ?",
+ 'confirm.description': "L'accès complet permet aux nouvelles sessions de réduire les étapes de confirmation et d'effectuer directement davantage d'actions, notamment des opérations sensibles, des modifications de fichiers ou des commandes externes. Utilisez-le uniquement lorsque vous faites confiance aux tâches suivantes.",
+ 'confirm.acknowledge': 'Je comprends les risques et je veux continuer',
+ 'confirm.cancel': 'Annuler',
+ 'confirm.enable': "Activer l'accès complet",
+} satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = {
+ 'title': 'Erlaubnis',
+ 'description': 'Wählen Sie den Standardberechtigungsmodus für neue Sitzungen',
+ 'loading': 'Laden',
+ 'unavailable': 'Nicht verfügbar',
+ 'preset.readOnly': 'Nur lesen',
+ 'preset.workspaceWrite': 'Arbeitsbereich schreiben',
+ 'preset.fullAccess': 'Voller Zugriff',
+ 'confirm.title': 'Vollzugriff aktivieren?',
+ 'confirm.description': 'Durch den vollständigen Zugriff können neue Sitzungen die Bestätigungsschritte reduzieren und mehr Aktionen direkt ausführen, einschließlich vertraulicher Vorgänge, Dateiänderungen oder externer Befehle. Verwenden Sie es nur, wenn Sie nachfolgenden Aufgaben vertrauen.',
+ 'confirm.acknowledge': 'Ich verstehe die Risiken und möchte weitermachen',
+ 'confirm.cancel': 'Abbrechen',
+ 'confirm.enable': 'Aktivieren Sie Vollzugriff',
+} satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = {
+ 'title': 'Разрешение',
+ 'description': 'Выберите режим разрешений по умолчанию для новых сеансов',
+ 'loading': 'Загрузка',
+ 'unavailable': 'Недоступно',
+ 'preset.readOnly': 'Только чтение',
+ 'preset.workspaceWrite': 'Запись в рабочей области',
+ 'preset.fullAccess': 'Полный доступ',
+ 'confirm.title': 'Включить полный доступ?',
+ 'confirm.description': 'Полный доступ позволяет новым сеансам сократить количество шагов подтверждения и напрямую выполнять больше действий, включая конфиденциальные операции, изменение файлов или внешние команды. Используйте его только в том случае, если вы доверяете последующим задачам.',
+ 'confirm.acknowledge': 'Я понимаю риски и хочу продолжить',
+ 'confirm.cancel': 'Отмена',
+ 'confirm.enable': 'Включить полный доступ',
+} satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = {
+ 'title': 'Permissão',
+ 'description': 'Escolha o modo de permissão padrão para novas sessões',
+ 'loading': 'Carregando',
+ 'unavailable': 'Indisponível',
+ 'preset.readOnly': 'Somente leitura',
+ 'preset.workspaceWrite': 'Gravação no espaço de trabalho',
+ 'preset.fullAccess': 'Acesso total',
+ 'confirm.title': 'Ativar acesso total?',
+ 'confirm.description': 'O acesso total permite que novas sessões reduzam as etapas de confirmação e executem mais ações diretamente, incluindo operações confidenciais, alterações de arquivos ou comandos externos. Use-o apenas quando confiar nas tarefas subsequentes.',
+ 'confirm.acknowledge': 'Entendo os riscos e quero continuar',
+ 'confirm.cancel': 'Cancelar',
+ 'confirm.enable': 'Ativar acesso total',
+} satisfies Record
+
/** Japanese copy, checked against the English keys. */
export const accessJa = {
'mode': 'アクセスモード、現在: {name}',
@@ -119,3 +199,108 @@ export const accessJa = {
'auto.confirm.acknowledge': 'これらのリスクを理解し、続行します',
'auto.confirm.enable': '自動認可レビューを有効にする',
} satisfies Record
+
+/** Spanish dictionary, checked against the English keys. */
+export const accessEs = {
+ 'mode': 'Modo de acceso, actual: {name}',
+ 'close': 'Cerrar',
+ 'preset.readOnly': 'Sólo lectura',
+ 'preset.workspaceWrite': 'Escritura en el espacio de trabajo',
+ 'preset.fullAccess': 'Acceso completo',
+ 'confirm.title': '¿Habilitar acceso completo?',
+ 'confirm.description': 'El acceso completo reduce los pasos de confirmación y permite al agente realizar más acciones directamente, incluidas operaciones confidenciales, cambios de archivos o comandos externos. Úselo solo cuando confíe en la tarea actual.',
+ 'confirm.acknowledge': 'Entiendo los riesgos y quiero continuar.',
+ 'confirm.cancel': 'Cancelar',
+ 'confirm.enable': 'Habilitar acceso completo',
+ 'auto.label': 'Auto review',
+ 'auto.badge': 'EXP',
+ 'auto.description': 'Ejecute sin una zona de pruebas después de una revisión experimental del mismo modelo de cada llamada de herramienta nativa y llamada interna PTC.',
+ 'auto.confirm.title': '¿Habilitar Auto review (experimental)?',
+ 'auto.confirm.description': 'Auto review se ejecuta sin zona de pruebas. Antes de cada llamada a la herramienta nativa y llamada interna PTC, el mismo modelo que el agente actual revisa si se permite; usted aprueba o rechaza cada llamada que rechaza. Esta característica es experimental, puede permitir o denegar acciones falsamente y utiliza tokens adicionales.',
+ 'auto.confirm.acknowledge': 'Entiendo estos riesgos y quiero continuar',
+ 'auto.confirm.enable': 'Habilitar Auto review',
+} satisfies typeof accessEn
+
+/** French dictionary, checked against the English keys. */
+export const accessFr = {
+ 'mode': "Mode d'accès, actuel : {name}",
+ 'close': 'Fermer',
+ 'preset.readOnly': 'Lecture seule',
+ 'preset.workspaceWrite': "Écriture dans l'espace de travail",
+ 'preset.fullAccess': 'Accès complet',
+ 'confirm.title': "Activer l'accès complet ?",
+ 'confirm.description': "L'accès complet réduit les étapes de confirmation et permet à l'agent d'effectuer directement davantage d'actions, notamment des opérations sensibles, des modifications de fichiers ou des commandes externes. Utilisez-le uniquement lorsque vous faites confiance à la tâche en cours.",
+ 'confirm.acknowledge': 'Je comprends les risques et je veux continuer',
+ 'confirm.cancel': 'Annuler',
+ 'confirm.enable': "Activer l'accès complet",
+ 'auto.label': 'Auto review',
+ 'auto.badge': 'EXP',
+ 'auto.description': "Exécutez sans bac à sable après un examen expérimental sur le même modèle de chaque appel d'outil natif et appel interne PTC.",
+ 'auto.confirm.title': 'Activer Auto review (expérimental) ?',
+ 'auto.confirm.description': "Auto review fonctionne sans bac à sable. Avant chaque appel d'outil natif et appel interne PTC, le même modèle que l'agent actuel vérifie s'il doit l'autoriser ; vous approuvez ou rejetez chaque appel qu'il refuse. Cette fonctionnalité est expérimentale, peut autoriser ou refuser à tort des actions et utilise des jetons supplémentaires.",
+ 'auto.confirm.acknowledge': 'Je comprends ces risques et je veux continuer',
+ 'auto.confirm.enable': 'Activer Auto review',
+} satisfies typeof accessEn
+
+/** German dictionary, checked against the English keys. */
+export const accessDe = {
+ 'mode': 'Zugriffsmodus, aktuell: {name}',
+ 'close': 'Schließen',
+ 'preset.readOnly': 'Nur lesen',
+ 'preset.workspaceWrite': 'Arbeitsbereich schreiben',
+ 'preset.fullAccess': 'Voller Zugriff',
+ 'confirm.title': 'Vollzugriff aktivieren?',
+ 'confirm.description': 'Durch den Vollzugriff werden Bestätigungsschritte reduziert und der Agent kann mehr Aktionen direkt ausführen, einschließlich vertraulicher Vorgänge, Dateiänderungen oder externer Befehle. Verwenden Sie es nur, wenn Sie der aktuellen Aufgabe vertrauen.',
+ 'confirm.acknowledge': 'Ich verstehe die Risiken und möchte weitermachen',
+ 'confirm.cancel': 'Abbrechen',
+ 'confirm.enable': 'Aktivieren Sie Vollzugriff',
+ 'auto.label': 'Auto review',
+ 'auto.badge': 'EXP',
+ 'auto.description': 'Ausführung ohne Sandbox nach einer experimentellen, modellgleichen Überprüfung jedes nativen Toolaufrufs und PTC-Innenaufrufs.',
+ 'auto.confirm.title': 'Auto review aktivieren (experimentell)?',
+ 'auto.confirm.description': 'Auto review läuft ohne Sandbox. Vor jedem nativen Tool-Aufruf und PTC-Innenaufruf überprüft dasselbe Modell wie der aktuelle Agent, ob dies zulässig ist. Sie genehmigen oder lehnen jeden abgelehnten Aufruf ab. Diese Funktion ist experimentell, kann Aktionen fälschlicherweise zulassen oder verweigern und verwendet zusätzliche Token.',
+ 'auto.confirm.acknowledge': 'Ich verstehe diese Risiken und möchte weitermachen',
+ 'auto.confirm.enable': 'Aktivieren Sie Auto review',
+} satisfies typeof accessEn
+
+/** Russian dictionary, checked against the English keys. */
+export const accessRu = {
+ 'mode': 'Режим доступа, текущий: {name}',
+ 'close': 'Закрыть',
+ 'preset.readOnly': 'Только чтение',
+ 'preset.workspaceWrite': 'Запись в рабочей области',
+ 'preset.fullAccess': 'Полный доступ',
+ 'confirm.title': 'Включить полный доступ?',
+ 'confirm.description': 'Полный доступ сокращает количество шагов подтверждения и позволяет агенту напрямую выполнять больше действий, включая конфиденциальные операции, изменения файлов или внешние команды. Используйте его только в том случае, если вы доверяете текущей задаче.',
+ 'confirm.acknowledge': 'Я понимаю риски и хочу продолжить',
+ 'confirm.cancel': 'Отмена',
+ 'confirm.enable': 'Включить полный доступ',
+ 'auto.label': 'Auto review',
+ 'auto.badge': 'EXP',
+ 'auto.description': 'Запуск без песочницы после экспериментальной проверки одной и той же модели каждого вызова встроенного инструмента и внутреннего вызова PTC.',
+ 'auto.confirm.title': 'Включить Auto review (экспериментально)?',
+ 'auto.confirm.description': 'Auto review работает без песочницы. Перед каждым вызовом собственного инструмента и внутренним вызовом PTC та же модель, что и текущий агент, проверяет, разрешить ли это; вы одобряете или отклоняете каждый вызов, который он отклоняет. Эта функция является экспериментальной, может ошибочно разрешать или запрещать действия и использует дополнительные токены.',
+ 'auto.confirm.acknowledge': 'Я понимаю эти риски и хочу продолжить',
+ 'auto.confirm.enable': 'Включить Auto review',
+} satisfies typeof accessEn
+
+/** Portuguese dictionary, checked against the English keys. */
+export const accessPt = {
+ 'mode': 'Modo de acesso, atual: {name}',
+ 'close': 'Fechar',
+ 'preset.readOnly': 'Somente leitura',
+ 'preset.workspaceWrite': 'Gravação no espaço de trabalho',
+ 'preset.fullAccess': 'Acesso total',
+ 'confirm.title': 'Ativar acesso total?',
+ 'confirm.description': 'O acesso total reduz as etapas de confirmação e permite que o agente execute mais ações diretamente, incluindo operações confidenciais, alterações de arquivos ou comandos externos. Use-o apenas quando confiar na tarefa atual.',
+ 'confirm.acknowledge': 'Entendo os riscos e quero continuar',
+ 'confirm.cancel': 'Cancelar',
+ 'confirm.enable': 'Ativar acesso total',
+ 'auto.label': 'Auto review',
+ 'auto.badge': 'EXP',
+ 'auto.description': 'Execute sem um sandbox após uma revisão experimental do mesmo modelo de cada chamada de ferramenta nativa e PTC chamada interna.',
+ 'auto.confirm.title': 'Ativar Auto review (experimental)?',
+ 'auto.confirm.description': 'Auto review é executado sem sandbox. Antes de cada chamada de ferramenta nativa e chamada interna PTC, o mesmo modelo do agente atual analisa se deve permitir isso; você aprova ou rejeita cada chamada negada. Este recurso é experimental, pode permitir ou negar ações falsamente e usa tokens adicionais.',
+ 'auto.confirm.acknowledge': 'Entendo esses riscos e quero continuar',
+ 'auto.confirm.enable': 'Ativar Auto review',
+} satisfies typeof accessEn
diff --git a/packages/client/ui-plan/locale/de.json b/packages/client/ui-plan/locale/de.json
new file mode 100644
index 0000000000..ce90df31f6
--- /dev/null
+++ b/packages/client/ui-plan/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Schnittstelle zur Planüberprüfung",
+ "description": "Zeigt Steuerelemente für den Planungsmodus, Planungskarten und eine Markdown-Vorschau an."
+ }
+}
diff --git a/packages/client/ui-plan/locale/es.json b/packages/client/ui-plan/locale/es.json
new file mode 100644
index 0000000000..1bc8dde649
--- /dev/null
+++ b/packages/client/ui-plan/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interfaz de revisión del plan",
+ "description": "Muestra controles del modo de planificación, tarjetas de planificación y vista previa de Markdown."
+ }
+}
diff --git a/packages/client/ui-plan/locale/fr.json b/packages/client/ui-plan/locale/fr.json
new file mode 100644
index 0000000000..165c62a645
--- /dev/null
+++ b/packages/client/ui-plan/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface de révision des plans",
+ "description": "Affiche les commandes du mode planification, les cartes de planification et l'aperçu Markdown."
+ }
+}
diff --git a/packages/client/ui-plan/locale/pt.json b/packages/client/ui-plan/locale/pt.json
new file mode 100644
index 0000000000..1ad0ee2f88
--- /dev/null
+++ b/packages/client/ui-plan/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface de revisão do plano",
+ "description": "Mostra controles do modo de planejamento, cartões de planejamento e visualização do Markdown."
+ }
+}
diff --git a/packages/client/ui-plan/locale/ru.json b/packages/client/ui-plan/locale/ru.json
new file mode 100644
index 0000000000..08eba25394
--- /dev/null
+++ b/packages/client/ui-plan/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Интерфейс проверки плана",
+ "description": "Показывает элементы управления режимом планирования, карты планирования и предварительный просмотр Markdown."
+ }
+}
diff --git a/packages/client/ui-plan/src/client/index.ts b/packages/client/ui-plan/src/client/index.ts
index 1b226f8a37..34196a00da 100644
--- a/packages/client/ui-plan/src/client/index.ts
+++ b/packages/client/ui-plan/src/client/index.ts
@@ -25,7 +25,7 @@ import { planAddress, parsePlanAddress } from './plan.ts'
import { isReviewPreviewAddress, reviewPreviewAddress } from './review-preview.ts'
import { createPlanReviewStore } from './review-store.ts'
import { PlanChip } from './PlanModeControl.tsx'
-import { en, ja, zh, type PlanKey } from './locales.ts'
+import { en, ja, es, fr, de, ru, pt, zh, type PlanKey } from './locales.ts'
export type { PlanKey } from './locales.ts'
@@ -56,7 +56,7 @@ export const inject = ['slots', 'remote', 'remote.commands', 'remote.session', '
* @param ctx - client root context.
*/
export function apply(ctx: ClientContext): void {
- ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-plan: dictionaries')
+ ctx.effect(() => ctx.locale.register(NS, { zh, en, ja, es, fr, de, ru, pt }), 'ui-plan: dictionaries')
const previewId = '@deepseek-ai/dsh-client-ui-plan'
const t = ctx.locale.bind(NS)
diff --git a/packages/client/ui-plan/src/client/locales.ts b/packages/client/ui-plan/src/client/locales.ts
index 331fdb5234..5940519fa2 100644
--- a/packages/client/ui-plan/src/client/locales.ts
+++ b/packages/client/ui-plan/src/client/locales.ts
@@ -65,3 +65,108 @@ export const ja = {
'chip.on.title': '計画モードはオンです — クリックでオフ (/plan off)',
'chip.exitFailed': '計画モードを終了できませんでした',
} satisfies Record
+
+/** Spanish dictionary, checked against the English keys. */
+export const es = {
+ 'chip.label': 'Planificar',
+ 'preview.title': 'Planificar',
+ 'preview.document': 'Plano · Markdown',
+ 'preview.action': 'Abrir',
+ 'preview.open': 'Plan abierto en la barra lateral',
+ 'preview.full': 'Ver plano completo',
+ 'preview.openNamed': 'Plan abierto: {title}',
+ 'preview.loading': 'Cargando plan…',
+ 'preview.failed': 'No se pudo cargar el plan',
+ 'preview.invalidAddress': 'Dirección del plan no válida',
+ 'preview.historyUnavailable': 'El historial de sesiones no está disponible',
+ 'preview.notFound': 'Este plan no fue encontrado.',
+ 'preview.unavailable': 'La vista previa del plan no está disponible',
+ 'preview.expired': 'Esta vista previa del plan temporal ha caducado. Vuelva a abrirlo desde la tarjeta de revisión pendiente.',
+ 'chip.on.aria': 'Modo plan activado, presione para apagar',
+ 'chip.on.title': 'Modo de planificación activado: haga clic para desactivar (/plan off)',
+ 'chip.exitFailed': 'No se pudo salir del modo de plan',
+} satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = {
+ 'chip.label': 'Planifier',
+ 'preview.title': 'Planifier',
+ 'preview.document': 'Plan · Markdown',
+ 'preview.action': 'Ouvrir',
+ 'preview.open': 'Plan ouvert dans la barre latérale',
+ 'preview.full': 'Voir le forfait complet',
+ 'preview.openNamed': 'Plan ouvert : {title}',
+ 'preview.loading': 'Plan de chargement…',
+ 'preview.failed': 'Impossible de charger le plan',
+ 'preview.invalidAddress': 'Adresse du forfait invalide',
+ 'preview.historyUnavailable': "L'historique des sessions n'est pas disponible",
+ 'preview.notFound': "Ce plan n'a pas été trouvé",
+ 'preview.unavailable': "L'aperçu du plan n'est pas disponible",
+ 'preview.expired': 'Cet aperçu du plan temporaire a expiré. Rouvrez-le à partir de la carte de révision en attente.',
+ 'chip.on.aria': 'Mode Plan activé, appuyez pour désactiver',
+ 'chip.on.title': 'Mode Plan activé — cliquez pour désactiver (/plan off)',
+ 'chip.exitFailed': 'Échec de la sortie du mode Plan',
+} satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = {
+ 'chip.label': 'Planen',
+ 'preview.title': 'Planen',
+ 'preview.document': 'Plan · Markdown',
+ 'preview.action': 'Öffnen',
+ 'preview.open': 'Plan in der Seitenleiste öffnen',
+ 'preview.full': 'Vollständigen Plan anzeigen',
+ 'preview.openNamed': 'Offener Grundriss: {title}',
+ 'preview.loading': 'Ladeplan…',
+ 'preview.failed': 'Plan konnte nicht geladen werden',
+ 'preview.invalidAddress': 'Ungültige Planadresse',
+ 'preview.historyUnavailable': 'Der Sitzungsverlauf ist nicht verfügbar',
+ 'preview.notFound': 'Dieser Plan wurde nicht gefunden',
+ 'preview.unavailable': 'Die Planvorschau ist nicht verfügbar',
+ 'preview.expired': 'Diese temporäre Planvorschau ist abgelaufen. Öffnen Sie es erneut über die ausstehende Überprüfungskarte.',
+ 'chip.on.aria': 'Planmodus ein, zum Ausschalten drücken',
+ 'chip.on.title': 'Planmodus ein – zum Ausschalten klicken (/plan off)',
+ 'chip.exitFailed': 'Der Planmodus konnte nicht beendet werden',
+} satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = {
+ 'chip.label': 'План',
+ 'preview.title': 'План',
+ 'preview.document': 'План · Markdown',
+ 'preview.action': 'Открыть',
+ 'preview.open': 'Открытая планировка в боковой панели',
+ 'preview.full': 'Посмотреть полный план',
+ 'preview.openNamed': 'Открытая планировка: {title}',
+ 'preview.loading': 'План загрузки…',
+ 'preview.failed': 'Не удалось загрузить план',
+ 'preview.invalidAddress': 'Неверный адрес плана',
+ 'preview.historyUnavailable': 'История сеансов недоступна',
+ 'preview.notFound': 'Этот план не найден',
+ 'preview.unavailable': 'Предварительный просмотр плана недоступен.',
+ 'preview.expired': 'Срок действия этой временной предварительной версии плана истек. Откройте его снова из карточки ожидающего рассмотрения.',
+ 'chip.on.aria': 'Режим планирования включен. Нажмите, чтобы выключить.',
+ 'chip.on.title': 'Режим планирования включен — нажмите, чтобы выключить (/plan off)',
+ 'chip.exitFailed': 'Не удалось выйти из режима планирования.',
+} satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = {
+ 'chip.label': 'Plano',
+ 'preview.title': 'Plano',
+ 'preview.document': 'Plano · Markdown',
+ 'preview.action': 'Abrir',
+ 'preview.open': 'Abrir plano na barra lateral',
+ 'preview.full': 'Ver plano completo',
+ 'preview.openNamed': 'Plano aberto: {title}',
+ 'preview.loading': 'Carregando plano…',
+ 'preview.failed': 'Não foi possível carregar o plano',
+ 'preview.invalidAddress': 'Endereço do plano inválido',
+ 'preview.historyUnavailable': 'O histórico da sessão não está disponível',
+ 'preview.notFound': 'Este plano não foi encontrado',
+ 'preview.unavailable': 'A visualização do plano não está disponível',
+ 'preview.expired': 'Esta visualização temporária do plano expirou. Reabra-o no cartão de revisão pendente.',
+ 'chip.on.aria': 'Modo de planejamento ativado, pressione para desligar',
+ 'chip.on.title': 'Modo de planejamento ativado – clique para desligar (/plan off)',
+ 'chip.exitFailed': 'Falha ao sair do modo de plano',
+} satisfies Record
diff --git a/packages/client/ui-plugin-manager/locale/de.json b/packages/client/ui-plugin-manager/locale/de.json
new file mode 100644
index 0000000000..16a03e8049
--- /dev/null
+++ b/packages/client/ui-plugin-manager/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Plug-in-Verwaltungsschnittstelle",
+ "description": "Plug-in-Pakete anzeigen und installieren sowie Vorgänge aktivieren, deaktivieren und wiederholen."
+ }
+}
diff --git a/packages/client/ui-plugin-manager/locale/es.json b/packages/client/ui-plugin-manager/locale/es.json
new file mode 100644
index 0000000000..3ca20e033d
--- /dev/null
+++ b/packages/client/ui-plugin-manager/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interfaz de gestión de complementos",
+ "description": "Vea e instale paquetes de complementos y habilite, deshabilite y reintente operaciones."
+ }
+}
diff --git a/packages/client/ui-plugin-manager/locale/fr.json b/packages/client/ui-plugin-manager/locale/fr.json
new file mode 100644
index 0000000000..48bf4551cd
--- /dev/null
+++ b/packages/client/ui-plugin-manager/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface de gestion des plug-ins",
+ "description": "Affichez et installez les packages de plug-ins, puis activez, désactivez et réessayez les opérations."
+ }
+}
diff --git a/packages/client/ui-plugin-manager/locale/pt.json b/packages/client/ui-plugin-manager/locale/pt.json
new file mode 100644
index 0000000000..f1e246b3a2
--- /dev/null
+++ b/packages/client/ui-plugin-manager/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Interface de gerenciamento de plug-ins",
+ "description": "Visualize e instale pacotes de plug-ins e ative, desative e repita as operações."
+ }
+}
diff --git a/packages/client/ui-plugin-manager/locale/ru.json b/packages/client/ui-plugin-manager/locale/ru.json
new file mode 100644
index 0000000000..155d905c69
--- /dev/null
+++ b/packages/client/ui-plugin-manager/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Интерфейс управления плагинами",
+ "description": "Просмотрите и установите пакеты подключаемых модулей, а также включите, отключите и повторите операции."
+ }
+}
diff --git a/packages/client/ui-plugin-manager/src/client/index.ts b/packages/client/ui-plugin-manager/src/client/index.ts
index 54dce2da8b..f5908227a4 100644
--- a/packages/client/ui-plugin-manager/src/client/index.ts
+++ b/packages/client/ui-plugin-manager/src/client/index.ts
@@ -30,7 +30,7 @@ import { PluginsPanelIcon } from './PluginsPanelIcon.tsx'
import { configLedgerSource } from './config-ledger.ts'
import { tabLedgerSource } from './tab-ledger.ts'
import { PluginManagerController } from './manager-store.ts'
-import { en, ja, zh, type PluginManagerLocaleKey } from './locales.ts'
+import { en, ja, es, fr, de, ru, pt, zh, type PluginManagerLocaleKey } from './locales.ts'
import { createNavigationStore } from './navigation-store.ts'
import type {} from './slot-contract.ts'
@@ -92,7 +92,7 @@ export const inject = ['slots', 'locale', 'remote', 'remote.pluginManager', 'rem
* @param ctx - the browser plugin context.
*/
export function apply(ctx: ClientContext): void {
- ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-plugin-manager: dictionaries')
+ ctx.effect(() => ctx.locale.register(NS, { zh, en, ja, es, fr, de, ru, pt }), 'ui-plugin-manager: dictionaries')
const t = ctx.locale.bind(NS)
const controller = new PluginManagerController(ctx)
ctx.effect(() => () => { controller.dispose() }, 'ui-plugin-manager: controller')
diff --git a/packages/client/ui-plugin-manager/src/client/locales.ts b/packages/client/ui-plugin-manager/src/client/locales.ts
index 912baa2c35..a871ab3692 100644
--- a/packages/client/ui-plugin-manager/src/client/locales.ts
+++ b/packages/client/ui-plugin-manager/src/client/locales.ts
@@ -880,3 +880,1458 @@ export const ja = {
'detailFailed': '詳細を読み込めませんでした。',
'detailFootnotes': '脚注',
} satisfies Record
+
+/** Spanish dictionary, checked against the English keys. */
+export const es = {
+ panel: 'Complementos',
+ title: 'Complementos',
+ intro: 'Instalar, habilitar y configurar complementos',
+ infoLabel: 'Acerca de los complementos',
+ infoDescription: 'Configure complementos oficiales e instale o administre otros complementos aquí. Vea la lista de complementos integrados y el estado del tiempo de ejecución en Configuración → Complementos integrados.',
+ loading: 'Leyendo complementos…',
+ error: 'No se pudieron leer todos los complementos, posiblemente debido a un problema de red',
+ unavailable: 'Esta implementación se ejecuta sin un perfil manejable, por lo que los complementos no se pueden instalar ni cambiar aquí.',
+ retry: 'Reintentar',
+ refresh: 'Actualizar',
+ refreshError: 'Error al actualizar. Por favor inténtalo de nuevo.',
+ empty: 'Aún no hay complementos instalados.',
+ addPlugin: 'Agregar complemento',
+ chooseAddMethod: 'Elija cómo agregar un complemento',
+ installExisting: 'Instalar un complemento de terceros',
+ installExistingDescription: 'Admite paquetes npm, repositorios Git y directorios locales',
+ restartNotice: 'Reinicie DSH para aplicar el cambio. {reason}',
+ restartReasonNoHmr: 'Este perfil aplica cambios solo al inicio.',
+ restartReasonPackageMoved: 'Un paquete instalado ahora se resuelve en otro directorio o el cambio reemplaza un paquete que proporciona la instalación de DSH. Desinstale un paquete antes de instalarlo desde otra ubicación.',
+ restartReasonHostModule: 'El cambio llega a los módulos DSH propiamente cargados.',
+ restartReasonUnmanagedPlugin: 'El cambio afecta a un complemento que se ejecuta fuera de la lista de complementos.',
+ restartReasonModuleComparison: 'DSH no pudo comparar los complementos cargados con sus archivos.',
+ overriddenNotice: '{name} se guardó, pero una configuración de mayor prioridad lo anula, por lo que no está vigente',
+ bundlesTitle: 'Complementos instalados',
+ basicTitle: 'Configuración del complemento incorporado',
+ extensionsTitle: 'Complementos experimentales',
+ moreTitle: 'Complementos experimentales',
+ moreAction: 'Más',
+ tabsLabel: 'Vistas de complementos',
+ installedTab: 'Instalado',
+ statusProblem: 'problema',
+ statusBeta: 'Experimental',
+ reasonLabel: 'Razón',
+ metadataError: 'Error de metadatos del paquete: {error}',
+ versionTag: 'v{version}',
+ partsLabel: 'Componentes',
+ sourceTitle: 'Fuente',
+ sourceSpec: 'fuente del código',
+ sourceBuiltIn: 'Construido en',
+ sourceVersion: 'Versión actual',
+ partsEmpty: 'Este paquete de complementos no contiene componentes.',
+ partsCountTotal: '{count} total',
+ partsCountRunning: '{count} corriendo',
+ partsCountOff: '{count} apagado',
+ partOff: 'Apagado',
+ partsCountFailed: '{count} falló',
+ partsFilter: 'Componentes del filtro',
+ partsFilterEmpty: 'Ningún componente coincide.',
+ partToggle: 'Habilitar componente {name}',
+ rowPhasePending: 'Esperando dependencias',
+ rowPhaseLoading: 'Cargando',
+ rowPhaseActive: 'corriendo',
+ rowPhaseFailed: 'problema',
+ rowPhaseUnloading: 'Descarga',
+ enableToggle: 'Habilitar {name}',
+ applyingChange: 'Aplicar cambios a {name}',
+ openDetail: 'Ver {name}',
+ backToList: 'Volver a complementos',
+ crumbRoot: 'Complementos',
+ backToPackage: 'Volver a {name}',
+ configureRow: 'Configurar {name}',
+ rowStateIdle: 'no corriendo',
+ uninstall: 'Desinstalar',
+ uninstallLabel: 'Desinstalar {name}',
+ installTitle: 'Agregar complemento',
+ installDescription: 'Ingrese el nombre del paquete del complemento, la dirección del repositorio GitHub o la ruta del directorio local.',
+ installSpecLabel: 'Nombre o dirección del paquete',
+ installSpecPlaceholder: 'por ejemplo dsh-plugin-ballena-mascota',
+ installGuideToggle: 'Guía de instalación y ejemplos.',
+ installGuideHide: 'Ocultar la guía',
+ installGuideIdTitle: 'Ingrese el nombre del paquete npm del complemento',
+ installGuideIdExample: 'dsh-plugin-ballena-mascota',
+ installGuideIdHint: 'El nombre del paquete del complemento es el nombre del paquete npm (como dsh-xxx o @author/plugin): la parte después de dsh plugin add o pnpm add en el comando de instalación README de un complemento comunitario.',
+ installGuideExampleLabel: 'Ejemplo: ',
+ installGitTemplateHint: 'Reemplace esto con la dirección real del repositorio de Git.',
+ installPathTemplateHint: 'Reemplace esto con la ruta real a su directorio de complementos local.',
+ installGuideFill: 'ejemplo de uso',
+ installGuideFillAria: 'Utilice el ejemplo {example}',
+ installGuideSafety: 'Instale sólo complementos en los que confíe: se ejecutan con sus permisos y pueden dañar DeepSeek Harness o filtrar sus datos.',
+ installUpgradeNotice: 'Los complementos oficiales bajo demanda ofrecen actualización cuando su versión difiere de DSH. Para actualizar otros complementos, desinstálelos y vuelva a instalarlos.',
+ registryToggle: 'Registro',
+ registryLegend: 'El registro npm desde el que se descarga el complemento',
+ registryDefault: 'Registro predeterminado',
+ registryOfficial: 'Registro oficial de npm',
+ registryNpmmirror: 'Espejo de China continental',
+ registryCustom: 'Dirección personalizada',
+ registryCustomPlaceholder: 'https://npm.example.com/',
+ registryCustomHint: 'Ingrese una dirección de registro npm interna o privada que comience con http:// o https://.. Si requiere iniciar sesión, almacene las credenciales en ~/.npmrc en esta máquina.',
+ registryCustomInvalid: 'Introduzca una dirección que comience con http:// o https://',
+ registryListSeparator: ', ',
+ sentenceSeparator: ' ',
+ installRun: 'Instalar',
+ installChecking: 'Comprobando…',
+ installConfirmedTitle: 'Instalar complemento',
+ installConfirmedRefused: 'Este complemento no se puede instalar',
+ installProblemInvalid: 'Este no es un nombre o dirección de paquete que se pueda instalar: {reason}',
+ installProblemInstalled: 'Este complemento ya está instalado. Para actualizarlo, desinstálelo e instálelo nuevamente.',
+ installProblemShipped: 'Este complemento se envía con DSH; actualizar DSH lo actualiza',
+ installBundledTitle: 'Este complemento se envía con DSH',
+ installBundledDescription: 'Habilítelo sin descargar otra copia',
+ installProblemNotFound: 'No se encontró tal complemento',
+ installProblemNotPackage: 'La ruta no existe o no es un paquete de complemento válido',
+ installProblemNotBundle: 'Este paquete no declara ningún paquete, por lo que no se puede instalar como complemento: {reason}',
+ installProblemNetwork: 'No se pudo acceder al registro del complemento; comprueba la red y vuelve a intentarlo',
+ installProblemNetworkAll: 'No se pudo acceder a ningún registro (probado: {registries}); verifique la configuración de red o proxy, o cambie el registro',
+ installProblemUnknown: 'No se pudo buscar el complemento: {reason}',
+ installingTitle: 'Instalando el complemento…',
+ installedTitle: 'Instalado',
+ statusNotInstalled: 'No instalado',
+ statusMissingFiles: 'Paquete no disponible',
+ statusUpdateAvailable: 'Actualización disponible',
+ sourceNotInstalled: 'No instalado',
+ sourceTargetSpec: 'Fuente de este DSH',
+ sourceTargetVersion: 'Versión para este DSH',
+ update: 'Actualizar',
+ updateLabel: 'Actualización {name}',
+ versionsTitle: 'Versiones',
+ versionsRecheck: 'comprobar de nuevo',
+ versionsChecking: 'Buscando actualizaciones',
+ versionsAvailable: 'Actualización a {version} disponible',
+ versionsPublished: 'Publicado {date}',
+ versionsLatest: 'actualizado',
+ versionsCheckedAt: 'Última comprobación en {time}',
+ versionsBlocked: 'Las versiones más nuevas necesitan una actualización DSH',
+ versionsBlockedDetail: '{version} necesita {peers}; este DSH es {runtime}',
+ versionsNoneInstallable: 'No hay una versión más nueva para instalar directamente',
+ versionsNoneInstallableDetail: 'Las versiones más recientes están obsoletas, son versiones preliminares o no declaran ningún paquete. Ampliar para verlos',
+ versionsFailedNetwork: 'No se pudo acceder al registro',
+ versionsFailedOther: 'No se pudo buscar actualizaciones',
+ versionsDetails: 'Detalles',
+ versionsNewerCount: '{count} más nuevo',
+ versionsCollapse: 'Contraer',
+ versionsInstalledTag: 'Instalado',
+ versionsPrereleaseTag: 'Presentación',
+ versionsDeprecatedTag: 'En desuso',
+ versionsNeedsDsh: 'Necesita {peers}',
+ versionsNotBundle: 'Esta versión no declara ningún paquete y no se puede instalar.',
+ versionsUpdateLabel: 'Actualización a {version}',
+ versionsRegistry: 'Registro',
+ versionsCheckedAtLabel: 'Comprobado en',
+ versionsFallbackTag: 'Respaldo',
+ versionsFallbackHint: 'No se pudo alcanzar {registry}; la lista proviene de un registro alternativo',
+ versionsPrimaryTag: 'primero',
+ versionsShowPrerelease: 'Mostrar presentaciones preliminares',
+ versionsOmitted: '{count} no se enumeran más versiones más recientes que la instalada',
+ versionsProblemNetwork: 'Inalcanzable',
+ versionsProblemNotFound: 'No encontrado',
+ versionsProblemUnknown: 'Fallido',
+ updatesAvailable: 'Actualizaciones disponibles: {count}',
+ updatesNone: 'Todos los complementos están actualizados.',
+ updatesFailed: 'Algunos complementos no se pudieron verificar',
+ updateTitle: 'Actualización {name}',
+ updateSource: 'Instalar desde',
+ updateDescription: 'La actualización reemplaza {from} por {to}; el complemento puede detenerse brevemente. Si la instalación falla, {from} se conserva o se vuelve a instalar.',
+ updateRunning: 'Actualizando',
+ updateStepChecking: 'Comprobar {version}',
+ updateStepRemoving: 'Detener y eliminar {version}',
+ updateStepInstalling: 'Instale {version} desde {registry}',
+ updateStepRestoring: 'Instale {version} nuevamente',
+ updateDone: 'Actualizado a {version}',
+ updateDoneDetail: 'La nueva versión se está ejecutando.',
+ updateRestart: '{version} instalado; entra en vigor después de un reinicio',
+ updateRefused: 'No se puede actualizar a {version}',
+ updateRemoveFailed: 'No se pudo eliminar {version}',
+ updateRestored: 'La actualización falló; restaurado {version}',
+ updateRestoredDetail: '{to} no pudo instalarse; {from} se instaló nuevamente.',
+ updateKept: 'No se pudo actualizar a {version}',
+ updateKeptDetail: '{from} no se eliminó y permanece en uso. {reason}',
+ updateBroken: 'La actualización falló; el complemento no está instalado',
+ updateBrokenDetail: '{to} no se pudo instalar y la instalación de {from} nuevamente también falló. Instale {name} nuevamente desde Agregar complemento.',
+ updateLost: 'Se desconoce el resultado de la actualización.',
+ updateLostDetail: 'Se interrumpió la conexión con DSH: {reason}. Actualice la lista de complementos para ver la versión instalada.',
+ updateOutput: 'Mostrar resultado de instalación',
+ updateFinish: 'hecho',
+ installFailedTitle: 'No se pudo instalar el complemento',
+ installGithubFailedTitle: 'No se puede acceder a GitHub',
+ installGithubTimeoutTitle: 'Se agotó el tiempo de espera de la conexión GitHub',
+ installGithubFailedDescription: 'Pruebe con otra fuente de instalación.',
+ installUseGithubMirror: 'Utilice el espejo de China continental',
+ installTryAnotherWay: 'Prueba de otra manera',
+ installPackageLabel: 'Nombre del paquete del complemento',
+ installEdit: 'Editar',
+ installEditAria: 'Volver a editar',
+ installCancelAndEdit: 'Cancelar la instalación y volver a editar',
+ installApplyingCancellationError: 'La cancelación no fue confirmada. Se está aplicando la instalación; espera su resultado. {reason}',
+ installReconcile: 'Verificar el estado de la instalación',
+ installUnknownTitle: 'El resultado de la instalación no está disponible',
+ installUnknownDescription: 'El Host no tiene ninguna instalación activa con esta identificación de solicitud. Consulte la lista de complementos antes de volver a intentarlo.',
+ installResultUnconfirmed: 'No se recibió el resultado de la instalación. Verificar el estado de la instalación. {reason}',
+ installAwaitingAcceptance: 'Esperando que el Host acepte la instalación. La cancelación se volverá a intentar automáticamente después de la confirmación.',
+ installBackgroundUnknown: 'El resultado de la instalación no está disponible. Consulte la lista de complementos.',
+ installCancel: 'Cancelar instalación',
+ installCloseCancels: 'Cancelar instalación y cerrar',
+ installViewTask: 'Ver instalación',
+ installUnconfirmedTitle: 'Estado de instalación no confirmado',
+ installBackgroundDone: 'Instalación finalizada. Ver detalles de instalación.',
+ installBackgroundFailed: 'La instalación falló. Ver detalles de instalación.',
+ installBackgroundUnconfirmed: 'El estado de la instalación no está confirmado. Vea la instalación para más detalles.',
+ installBackgroundApplying: 'La instalación se está aplicando y no se puede cancelar. Ver el progreso de la instalación.',
+ installStarting: 'Preparando la instalación…',
+ installCancelling: 'Deteniendo la instalación…',
+ installApplying: 'Aplicando configuración, espere...',
+ installCancelledShort: 'Cancelado',
+ installCancelled: 'Instalación cancelada; el complemento no está habilitado y los archivos descargados pueden permanecer',
+ installCancelUnconfirmed: 'No se ha confirmado que la instalación se haya detenido. Vuelva a intentar la cancelación o espere el resultado de la instalación. {reason}',
+ installEnableNow: 'Habilitar ahora',
+ installDetailsShow: 'Mostrar detalles de instalación',
+ installDetailsHide: 'Ocultar detalles de instalación',
+ installVersion: 'Versión {version}',
+ installSubjectPath: 'directorio local',
+ installSubjectGit: 'repositorio git',
+ installSubjectTarball: 'bola de tar',
+ installLocation: 'Se instala en {dir}',
+ installRetry: 'Reintentar',
+ installChangeRegistry: 'Cambiar registro',
+ installAttempt: '{previous} no pudo entregar el paquete; reintentando a través de {registry} (registro {index} de {total})',
+ installAttemptBadge: 'Intento {index} · {registry}',
+ installFailureNetwork: 'La conexión de red falló',
+ installFailureNetworkAll: 'No se pudo acceder a ningún registro (probado: {registries}). Verifique la configuración de red o proxy, o cambie el registro y vuelva a intentarlo.',
+ installFailureNetworkHost: 'No se pudo contactar con {host}. Una dirección GitHub o un enlace .tgz no se recupera a través del registro: esta máquina debe llegar directamente o mediante un proxy. Si el complemento también está publicado en npm, ingrese el nombre del paquete.',
+ installFailureNotFound: 'No se encontró tal complemento',
+ installFailureNoMatchingVersion: 'Ninguna versión coincide con la solicitud',
+ installFailureDiskFull: 'El disco está lleno; la instalación se detuvo',
+ installFailurePermission: 'Sin permiso de escritura; el complemento no se puede instalar',
+ installFailureBuildBlocked: 'Los scripts de instalación de una dependencia necesitan su permiso antes de que la instalación pueda continuar',
+ installFailureBuildBlockedManual: 'pnpm bloqueó los scripts de instalación; permítalos en enableBuilds en pnpm-workspace.yaml y vuelva a intentarlo',
+ installFailureIntegrity: 'El paquete descargado no pasó la verificación de integridad',
+ installFailureTimeout: 'La instalación expiró',
+ installFailurePnpmMissing: 'No se encontró pnpm, por lo que no se puede instalar nada.',
+ installFailureGeneric: 'Algo salió mal durante la instalación; los detalles dicen lo que',
+ terminalCommandLine: 'Línea de comando {n}',
+ terminalRunning: 'corriendo',
+ terminalFailed: 'Fallido',
+ terminalDone: 'hecho',
+ terminalCopy: 'Copiar',
+ terminalCopied: 'Copiado',
+ terminalNoOutput: 'Sin salida',
+ terminalCollapseAria: 'Contraer salida',
+ terminalCollapse: 'Contraer',
+ terminalExpandAria: 'Expanda las líneas de salida {n} restantes',
+ terminalExpand: '… {n} más líneas',
+ terminalExitCode: 'código de salida {code}',
+ terminalSignal: 'señal {signal}',
+ terminalNoExitCode: 'sin código de salida',
+ installDoneNothing: 'Instalación finalizada sin nuevas dependencias.',
+ installDoneRestart: 'Instalado; reinicie DSH para cargarlo. {reason}',
+ installDoneApproved: 'Instalar scripts permitidos para {names}',
+ installDoneOtherVersion: 'Según la política de seguridad de la cadena de suministro de pnpm (minimumReleaseAge), las versiones publicadas más recientemente que la antigüedad requerida no se instalan automáticamente, por lo que esta instalación utilizó {installed} en lugar de la última versión {version}. Una versión recién publicada no ha completado este período de espera y puede contener códigos maliciosos o defectos no detectados. Si necesita {version}, desinstale la versión actual y luego instale {exact} para solicitar esa versión.',
+ installApprovalTitle: 'Instalar scripts necesita permiso',
+ installApprovalDescription: 'Estos paquetes tienen scripts de instalación que pnpm no ejecutó.',
+ installApprovalConsequence: 'Una vez permitidos, los scripts se ejecutan aquí con sus permisos y el permiso se guarda en este perfil.',
+ installApprovalCaution: 'Permita solo paquetes en los que confíe.',
+ installApproveAndRetry: 'Permita estos scripts y vuelva a intentarlo',
+ installClose: 'hecho',
+ close: 'Cerrar',
+ cancel: 'Cancelar',
+ confirmUninstallTitle: '¿Desinstalar "{name}"?',
+ confirmUninstallDescription: 'Lo que proporciona desaparece una vez que se desinstala.',
+ confirmUninstall: 'Desinstalar',
+ failedEnable: 'No se pudo habilitar: {reason}',
+ failedDisable: 'No se pudo desactivar: {reason}',
+ failedUninstall: 'No se pudo desinstalar: {reason}',
+ failedRowEnable: 'No se pudo habilitar el componente: {reason}',
+ failedRowDisable: 'No se pudo desactivar el componente: {reason}',
+ reasonManagementRequired: 'La gestión de complementos lo necesita; no se puede apagar ni desinstalar.',
+ reasonUnaddressable: 'El parche de perfil no puede abordar este de forma única.',
+ reasonUnknownPlugin: 'No existe tal complemento.',
+ reasonInvalidSpec: 'Ingrese un nombre o dirección de paquete válido.',
+ reasonAlreadyInstalled: 'Este paquete ya está instalado; desinstálelo antes de instalar otra versión.',
+ reasonAmbiguousInstall: 'No se puede saber qué paquete se instaló a partir del cambio de dependencia.',
+ reasonNotBundle: 'Este paquete no declara ningún paquete, por lo que no se puede administrar como un complemento.',
+ reasonNotRemovable: 'Este paquete no es propiedad del perfil o la administración del complemento lo necesita.',
+ reasonStopProfile: 'Este perfil se ejecuta sin HMR; deténgalo y desinstale el paquete con el complemento dsh.',
+ reasonBundleInUse: 'Otra configuración todavía usa los componentes de este paquete; apáguelos primero.',
+ reasonStaleApproval: 'Las aprobaciones de guiones pendientes cambiaron; instálelo nuevamente para actualizarlos.',
+ reasonIncompatibleVersion: '{plugin} es incompatible con DSH {runtime} (requiere {requirements}); ejecutarlo puede causar fallas o pérdida de datos.',
+ reasonIncompatibleVersionUnnamed: 'Este complemento es incompatible con la versión DSH en ejecución; ejecutarlo puede causar fallas o pérdida de datos.',
+ reasonIncompatibleInstall: 'Instale una versión del complemento compatible con este DSH.',
+ reasonIncompatibleInstalled: 'Desinstálalo e instala una versión compatible con este DSH.',
+ reasonOperationError: 'El anfitrión informó un error.',
+ detailTitle: 'Detalles',
+ detailLoading: 'Detalles de lectura',
+ detailTooLarge: 'Los detalles exceden el límite de tamaño y no se cargaron.',
+ detailNetwork: 'No se pudo llegar a la fuente de los detalles.',
+ detailInvalid: 'El complemento declara un archivo de detalles no válido.',
+ detailFailed: 'No se pudieron leer los detalles.',
+ detailFootnotes: 'Notas a pie de página',
+} satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = {
+ panel: 'Plugins',
+ title: 'Plugins',
+ intro: 'Installer, activer et configurer les plugins',
+ infoLabel: 'À propos des plugins',
+ infoDescription: "Configurez les plugins officiels et installez ou gérez d'autres plugins ici. Affichez la liste des plugins intégrés et l'état d'exécution dans Paramètres → Plugins intégrés.",
+ loading: 'Plugins de lecture…',
+ error: "Impossible de lire tous les plugins, probablement à cause d'un problème de réseau",
+ unavailable: "Ce déploiement s'exécute sans profil gérable, les plugins ne peuvent donc pas être installés ou commutés ici.",
+ retry: 'Réessayer',
+ refresh: 'Actualiser',
+ refreshError: "L'actualisation a échoué. Veuillez réessayer.",
+ empty: "Aucun plugin n'est encore installé.",
+ addPlugin: 'Ajouter un plugin',
+ chooseAddMethod: 'Choisissez comment ajouter un plugin',
+ installExisting: 'Installer un plugin tiers',
+ installExistingDescription: 'Prend en charge les packages npm, les référentiels Git et les répertoires locaux',
+ restartNotice: 'Redémarrez DSH pour appliquer la modification. {reason}',
+ restartReasonNoHmr: 'Ce profil applique les modifications uniquement au démarrage.',
+ restartReasonPackageMoved: "Un package installé est désormais résolu dans un autre répertoire ou la modification remplace un package fourni par l'installation de DSH. Désinstallez un package avant de l’installer à partir d’un autre emplacement.",
+ restartReasonHostModule: 'Le changement atteint les modules DSH lui-même chargés.',
+ restartReasonUnmanagedPlugin: 'La modification atteint un plugin exécuté en dehors de la liste des plugins.',
+ restartReasonModuleComparison: "DSH n'a pas pu comparer les plugins chargés avec leurs fichiers.",
+ overriddenNotice: "{name} a été enregistré, mais une configuration de priorité plus élevée l'écrase, elle n'est donc pas effective",
+ bundlesTitle: 'Plugins installés',
+ basicTitle: 'Paramètres du plugin intégré',
+ extensionsTitle: 'Plugins expérimentaux',
+ moreTitle: 'Plugins expérimentaux',
+ moreAction: 'Plus',
+ tabsLabel: 'Vues des plugins',
+ installedTab: 'Installé',
+ statusProblem: 'Problème',
+ statusBeta: 'Expérimental',
+ reasonLabel: 'Raison',
+ metadataError: 'Erreur de métadonnées du package : {error}',
+ versionTag: 'v{version}',
+ partsLabel: 'Composants',
+ sourceTitle: 'Source',
+ sourceSpec: 'Source du code',
+ sourceBuiltIn: 'Intégré',
+ sourceVersion: 'Version actuelle',
+ partsEmpty: 'Ce pack de plugins ne contient aucun composant.',
+ partsCountTotal: 'Total {count}',
+ partsCountRunning: "{count} en cours d'exécution",
+ partsCountOff: '{count} désactivé',
+ partOff: 'Désactivé',
+ partsCountFailed: 'Échec du {count}',
+ partsFilter: 'Composants du filtre',
+ partsFilterEmpty: 'Aucun composant ne correspond.',
+ partToggle: 'Activer le composant {name}',
+ rowPhasePending: 'En attente de dépendances',
+ rowPhaseLoading: 'Chargement',
+ rowPhaseActive: 'Courir',
+ rowPhaseFailed: 'Problème',
+ rowPhaseUnloading: 'Déchargement',
+ enableToggle: 'Activer {name}',
+ applyingChange: 'Application des modifications à {name}',
+ openDetail: 'Voir {name}',
+ backToList: 'Retour aux plugins',
+ crumbRoot: 'Plugins',
+ backToPackage: 'Retour à {name}',
+ configureRow: 'Configurer {name}',
+ rowStateIdle: 'Ne fonctionne pas',
+ uninstall: 'Désinstaller',
+ uninstallLabel: 'Désinstaller {name}',
+ installTitle: 'Ajouter un plugin',
+ installDescription: "Entrez le nom du package du plugin, l'adresse du référentiel GitHub ou le chemin du répertoire local.",
+ installSpecLabel: 'Nom ou adresse du colis',
+ installSpecPlaceholder: 'par exemple dsh-plugin-whale-pet',
+ installGuideToggle: "Guide d'installation et exemples",
+ installGuideHide: 'Masquer le guide',
+ installGuideIdTitle: 'Entrez le nom du package npm du plugin',
+ installGuideIdExample: 'dsh-plugin-baleine-animal de compagnie',
+ installGuideIdHint: "Le nom du package du plugin est le nom du package npm (comme dsh-xxx ou @author/plugin) : la partie après dsh plugin add ou pnpm add dans la commande d'installation README d'un plugin communautaire.",
+ installGuideExampleLabel: 'Exemple : ',
+ installGitTemplateHint: "Remplacez-le par l'adresse réelle du référentiel Git.",
+ installPathTemplateHint: 'Remplacez-le par le chemin réel vers votre répertoire de plugin local.',
+ installGuideFill: "Utiliser l'exemple",
+ installGuideFillAria: "Utilisez l'exemple {example}",
+ installGuideSafety: "Installez uniquement les plugins en qui vous avez confiance : ils s'exécutent avec vos autorisations et peuvent endommager DeepSeek Harness ou divulguer vos données.",
+ installUpgradeNotice: "Les plugins officiels à la demande proposent la mise à jour lorsque leur version diffère de DSH. Pour mettre à jour d'autres plugins, désinstallez-les et réinstallez-les.",
+ registryToggle: 'Registre',
+ registryLegend: 'Le registre npm à partir duquel le plugin est téléchargé',
+ registryDefault: 'Registre par défaut',
+ registryOfficial: 'Registre officiel npm',
+ registryNpmmirror: 'Miroir de Chine continentale',
+ registryCustom: 'Adresse personnalisée',
+ registryCustomPlaceholder: 'https://npm.example.com/',
+ registryCustomHint: "Entrez une adresse de registre npm interne ou privée commençant par http:// ou https://.. Si cela nécessite une connexion, stockez les informations d'identification dans ~/.npmrc sur cette machine.",
+ registryCustomInvalid: 'Saisissez une adresse commençant par http:// ou https://',
+ registryListSeparator: ', ',
+ sentenceSeparator: ' ',
+ installRun: 'Installer',
+ installChecking: 'Vérification…',
+ installConfirmedTitle: 'Installer le plug-in',
+ installConfirmedRefused: 'Ce plugin ne peut pas être installé',
+ installProblemInvalid: "Il ne s'agit pas d'un nom ou d'une adresse de package pouvant être installé : {reason}",
+ installProblemInstalled: 'Ce plugin est déjà installé. Pour le mettre à niveau, désinstallez-le et réinstallez-le',
+ installProblemShipped: 'Ce plugin est livré avec DSH ; la mise à niveau de DSH le met à jour',
+ installBundledTitle: 'Ce plugin est livré avec DSH',
+ installBundledDescription: 'Activez-le sans télécharger une autre copie',
+ installProblemNotFound: "Aucun plugin de ce type n'a été trouvé",
+ installProblemNotPackage: "Le chemin n'existe pas ou n'est pas un package de plugin valide",
+ installProblemNotBundle: 'Ce package ne déclare aucun bundle, il ne peut donc pas être installé en tant que plugin : {reason}',
+ installProblemNetwork: "Le registre du plugin n'a pas pu être atteint ; vérifie le réseau et réessaye",
+ installProblemNetworkAll: "Aucun registre n'a pu être atteint (essayé : {registries}) ; vérifiez les paramètres réseau ou proxy, ou modifiez le registre",
+ installProblemUnknown: "Le plugin n'a pas pu être recherché : {reason}",
+ installingTitle: 'Installation du plugin…',
+ installedTitle: 'Installé',
+ statusNotInstalled: 'Non installé',
+ statusMissingFiles: 'Forfait indisponible',
+ statusUpdateAvailable: 'Mise à jour disponible',
+ sourceNotInstalled: 'Non installé',
+ sourceTargetSpec: 'Source pour ce DSH',
+ sourceTargetVersion: 'Version pour ce DSH',
+ update: 'Mise à jour',
+ updateLabel: 'Mettre à jour {name}',
+ versionsTitle: 'Versions',
+ versionsRecheck: 'Vérifiez à nouveau',
+ versionsChecking: 'Vérification des mises à jour',
+ versionsAvailable: 'Mise à jour vers {version} disponible',
+ versionsPublished: 'Publié {date}',
+ versionsLatest: 'À jour',
+ versionsCheckedAt: 'Dernière vérification à {time}',
+ versionsBlocked: 'Les versions plus récentes nécessitent une mise à jour DSH',
+ versionsBlockedDetail: '{version} a besoin de {peers} ; ce DSH est {runtime}',
+ versionsNoneInstallable: 'Aucune version plus récente à installer directement',
+ versionsNoneInstallableDetail: 'Les versions les plus récentes sont obsolètes, sont des versions préliminaires ou ne déclarent aucun bundle. Agrandissez pour les voir',
+ versionsFailedNetwork: "Impossible d'accéder au registre",
+ versionsFailedOther: 'Impossible de vérifier les mises à jour',
+ versionsDetails: 'Détails',
+ versionsNewerCount: '{count} plus récent',
+ versionsCollapse: 'Réduire',
+ versionsInstalledTag: 'Installé',
+ versionsPrereleaseTag: 'Avant-première',
+ versionsDeprecatedTag: 'Obsolète',
+ versionsNeedsDsh: 'Besoins {peers}',
+ versionsNotBundle: 'Cette version ne déclare aucun bundle et ne peut pas être installée',
+ versionsUpdateLabel: 'Mise à jour vers {version}',
+ versionsRegistry: 'Registre',
+ versionsCheckedAtLabel: 'Vérifié à',
+ versionsFallbackTag: 'Repli',
+ versionsFallbackHint: "{registry} n'a pas pu être atteint ; la liste provient d'un registre de secours",
+ versionsPrimaryTag: "D'abord",
+ versionsShowPrerelease: 'Afficher les avant-premières',
+ versionsOmitted: '{count} et les versions plus récentes que celle installée ne sont pas répertoriées',
+ versionsProblemNetwork: 'Injoignable',
+ versionsProblemNotFound: 'Pas trouvé',
+ versionsProblemUnknown: 'Échec',
+ updatesAvailable: 'Mises à jour disponibles : {count}',
+ updatesNone: 'Tous les plugins sont à jour',
+ updatesFailed: "Certains plugins n'ont pas pu être vérifiés",
+ updateTitle: 'Mettre à jour {name}',
+ updateSource: 'Installer à partir de',
+ updateDescription: "La mise à jour remplace {from} par {to} ; le plugin peut s'arrêter brièvement. Si l'installation échoue, {from} est conservé ou réinstallé.",
+ updateRunning: 'Mise à jour',
+ updateStepChecking: 'Vérifiez {version}',
+ updateStepRemoving: 'Arrêtez et supprimez {version}',
+ updateStepInstalling: 'Installez {version} à partir de {registry}',
+ updateStepRestoring: 'Installez à nouveau {version}',
+ updateDone: 'Mis à jour vers {version}',
+ updateDoneDetail: "La nouvelle version est en cours d'exécution.",
+ updateRestart: '{version} installé ; cela prend effet après un redémarrage',
+ updateRefused: 'Impossible de mettre à jour vers {version}',
+ updateRemoveFailed: 'Impossible de supprimer {version}',
+ updateRestored: 'La mise à jour a échoué ; restauré {version}',
+ updateRestoredDetail: "Échec de l'installation de {to} ; {from} a été réinstallé.",
+ updateKept: 'Impossible de mettre à jour vers {version}',
+ updateKeptDetail: '{from} n’a pas été supprimé et reste utilisé. {reason}',
+ updateBroken: "La mise à jour a échoué ; le plugin n'est pas installé",
+ updateBrokenDetail: "L'installation de {to} a échoué et l'installation de {from} a également échoué. Installez à nouveau {name} à partir d'Ajouter un plugin.",
+ updateLost: 'Le résultat de la mise à jour est inconnu',
+ updateLostDetail: 'La connexion à DSH a été interrompue : {reason}. Actualisez la liste des plugins pour voir la version installée.',
+ updateOutput: "Afficher le résultat de l'installation",
+ updateFinish: 'Terminé',
+ installFailedTitle: "Le plugin n'a pas pu être installé",
+ installGithubFailedTitle: "Impossible d'accéder à GitHub",
+ installGithubTimeoutTitle: 'La connexion GitHub a expiré',
+ installGithubFailedDescription: "Essayez une autre source d'installation.",
+ installUseGithubMirror: 'Utiliser un miroir de Chine continentale',
+ installTryAnotherWay: 'Essayez une autre façon',
+ installPackageLabel: 'Nom du package du plugin',
+ installEdit: 'Modifier',
+ installEditAria: "Retour à l'édition",
+ installCancelAndEdit: "Annuler l'installation et revenir à l'édition",
+ installApplyingCancellationError: "L'annulation n'a pas été confirmée. L'installation est en cours d'application ; attendre son résultat. {reason}",
+ installReconcile: "Vérifier l'état de l'installation",
+ installUnknownTitle: "Résultat de l'installation indisponible",
+ installUnknownDescription: "L'hôte n'a aucune installation active avec cet identifiant de demande. Vérifiez la liste des plugins avant de réessayer.",
+ installResultUnconfirmed: "Le résultat de l'installation n'a pas été reçu. Vérifiez l'état de l'installation. {reason}",
+ installAwaitingAcceptance: "En attente que l'hôte accepte l'installation. L'annulation sera réessayée automatiquement après confirmation.",
+ installBackgroundUnknown: "Résultat de l'installation indisponible. Vérifiez la liste des plugins.",
+ installCancel: "Annuler l'installation",
+ installCloseCancels: "Annuler l'installation et fermer",
+ installViewTask: "Voir l'installation",
+ installUnconfirmedTitle: "État de l'installation non confirmé",
+ installBackgroundDone: "Installation terminée. Afficher les détails de l'installation.",
+ installBackgroundFailed: "L'installation a échoué. Afficher les détails de l'installation.",
+ installBackgroundUnconfirmed: "L'état de l'installation n'est pas confirmé. Consultez l'installation pour plus de détails.",
+ installBackgroundApplying: "L'installation est en cours d'application et ne peut pas être annulée. Afficher la progression de l'installation.",
+ installStarting: "Préparation de l'installation…",
+ installCancelling: "Arrêt de l'installation…",
+ installApplying: 'Application de la configuration, veuillez patienter…',
+ installCancelledShort: 'Annulé',
+ installCancelled: "Installation annulée ; le plugin n'est pas activé et les fichiers téléchargés peuvent rester",
+ installCancelUnconfirmed: "L'installation n'a pas été confirmée arrêtée. Réessayez l'annulation ou attendez le résultat de l'installation. {reason}",
+ installEnableNow: 'Activer maintenant',
+ installDetailsShow: "Afficher les détails de l'installation",
+ installDetailsHide: "Masquer les détails de l'installation",
+ installVersion: 'Version {version}',
+ installSubjectPath: 'Annuaire local',
+ installSubjectGit: 'Dépôt Git',
+ installSubjectTarball: 'Tarball',
+ installLocation: "S'installe dans {dir}",
+ installRetry: 'Réessayer',
+ installChangeRegistry: 'Changer de registre',
+ installAttempt: "{previous} n'a pas pu servir le package ; nouvelle tentative via {registry} (registre {index} de {total})",
+ installAttemptBadge: 'Tentative {index} · {registry}',
+ installFailureNetwork: 'La connexion réseau a échoué',
+ installFailureNetworkAll: "Aucun registre n'a pu être atteint (essayé : {registries}). Vérifiez les paramètres réseau ou proxy, ou modifiez le registre et réessayez.",
+ installFailureNetworkHost: "{host} n'a pas pu être atteint. Une adresse GitHub ou un lien .tgz ne sont pas récupérés via le registre : cette machine doit y accéder directement ou via un proxy. Si le plugin est également publié sur npm, saisissez plutôt le nom de son package.",
+ installFailureNotFound: "Aucun plugin de ce type n'a été trouvé",
+ installFailureNoMatchingVersion: 'Aucune version ne correspond à la demande',
+ installFailureDiskFull: "Le disque est plein ; l'installation s'est arrêtée",
+ installFailurePermission: "Aucune autorisation d'écriture ; le plugin ne peut pas être installé",
+ installFailureBuildBlocked: "Les scripts d'installation d'une dépendance ont besoin de votre autorisation avant que l'installation puisse continuer",
+ installFailureBuildBlockedManual: "pnpm a bloqué les scripts d'installation ; autorisez-les sous AllowBuilds dans pnpm-workspace.yaml et réessayez",
+ installFailureIntegrity: "Le package téléchargé a échoué à sa vérification d'intégrité",
+ installFailureTimeout: "L'installation a expiré",
+ installFailurePnpmMissing: "pnpm n'a pas été trouvé, donc rien ne peut être installé",
+ installFailureGeneric: "Quelque chose s'est mal passé lors de l'installation ; les détails disent quoi",
+ terminalCommandLine: 'Ligne de commande {n}',
+ terminalRunning: 'Courir',
+ terminalFailed: 'Échec',
+ terminalDone: 'Terminé',
+ terminalCopy: 'Copier',
+ terminalCopied: 'Copié',
+ terminalNoOutput: 'Aucune sortie',
+ terminalCollapseAria: 'Réduire la sortie',
+ terminalCollapse: 'Réduire',
+ terminalExpandAria: 'Développez les lignes de sortie {n} restantes',
+ terminalExpand: '… {n} plus de lignes',
+ terminalExitCode: 'code de sortie {code}',
+ terminalSignal: 'signal {signal}',
+ terminalNoExitCode: 'pas de code de sortie',
+ installDoneNothing: 'Installation terminée sans nouvelle dépendance.',
+ installDoneRestart: 'Installé ; redémarrez DSH pour le charger. {reason}',
+ installDoneApproved: "Scripts d'installation autorisés pour {names}",
+ installDoneOtherVersion: "Conformément à la politique de sécurité de la chaîne d'approvisionnement de pnpm (minimumReleaseAge), les versions publiées plus récemment que l'âge requis ne sont pas installées automatiquement. Cette installation a donc utilisé {installed} au lieu de la dernière version {version}. Une version nouvellement publiée n'a pas dépassé cette période d'attente et peut contenir du code malveillant ou des défauts non détectés. Si vous avez besoin de {version}, désinstallez la version actuelle, puis installez {exact} pour demander cette version.",
+ installApprovalTitle: "Les scripts d'installation nécessitent une autorisation",
+ installApprovalDescription: "Ces packages contiennent des scripts d'installation que pnpm n'a pas exécutés.",
+ installApprovalConsequence: "Une fois autorisés, les scripts s'exécutent ici avec vos autorisations et l'autorisation est enregistrée dans ce profil.",
+ installApprovalCaution: 'Autorisez uniquement les packages auxquels vous faites confiance.',
+ installApproveAndRetry: 'Autoriser ces scripts et réessayer',
+ installClose: 'Terminé',
+ close: 'Fermer',
+ cancel: 'Annuler',
+ confirmUninstallTitle: 'Désinstaller "{name}" ?',
+ confirmUninstallDescription: "Ce qu'il fournit disparaît une fois désinstallé.",
+ confirmUninstall: 'Désinstaller',
+ failedEnable: "Impossible d'activer : {reason}",
+ failedDisable: 'Impossible de désactiver : {reason}',
+ failedUninstall: 'Impossible de désinstaller : {reason}',
+ failedRowEnable: "Impossible d'activer le composant : {reason}",
+ failedRowDisable: 'Impossible de désactiver le composant : {reason}',
+ reasonManagementRequired: 'La gestion des plugins en a besoin ; il ne peut pas être éteint ou désinstallé.',
+ reasonUnaddressable: 'Le correctif de profil ne peut pas résoudre ce problème de manière unique.',
+ reasonUnknownPlugin: 'Aucun plugin de ce type.',
+ reasonInvalidSpec: 'Entrez un nom ou une adresse de package valide.',
+ reasonAlreadyInstalled: "Ce package est déjà installé ; désinstallez-le avant d'installer une autre version.",
+ reasonAmbiguousInstall: 'Le changement de dépendance ne permet pas de déterminer quel package a été installé.',
+ reasonNotBundle: 'Ce package ne déclare aucun bundle, il ne peut donc pas être géré comme un plugin.',
+ reasonNotRemovable: "Ce package n'appartient pas au profil, ou la gestion des plugins en a besoin.",
+ reasonStopProfile: "Ce profil s'exécute sans HMR ; arrêtez-le et désinstallez le package avec le plugin dsh.",
+ reasonBundleInUse: "Une autre configuration utilise toujours les composants de ce bundle ; éteignez-les d'abord.",
+ reasonStaleApproval: 'Les approbations de script en attente ont changé ; réinstallez-les pour les actualiser.',
+ reasonIncompatibleVersion: '{plugin} est incompatible avec DSH {runtime} (nécessite {requirements}) ; son exécution peut provoquer des plantages ou une perte de données.',
+ reasonIncompatibleVersionUnnamed: "Ce plugin est incompatible avec la version DSH en cours d'exécution ; son exécution peut provoquer des plantages ou une perte de données.",
+ reasonIncompatibleInstall: 'Installez une version du plugin compatible avec ce DSH.',
+ reasonIncompatibleInstalled: 'Désinstallez-le et installez une version compatible avec ce DSH.',
+ reasonOperationError: "L'hôte a signalé une erreur.",
+ detailTitle: 'Détails',
+ detailLoading: 'Détails de lecture',
+ detailTooLarge: "Les détails dépassent la limite de taille et n'ont pas été chargés.",
+ detailNetwork: "Impossible d'atteindre la source des détails.",
+ detailInvalid: 'Le plugin déclare un fichier de détails invalide.',
+ detailFailed: 'Impossible de lire les détails.',
+ detailFootnotes: 'Notes de bas de page',
+} satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = {
+ panel: 'Plugins',
+ title: 'Plugins',
+ intro: 'Plugins installieren, aktivieren und konfigurieren',
+ infoLabel: 'Über Plugins',
+ infoDescription: 'Konfigurieren Sie hier offizielle Plugins und installieren oder verwalten Sie andere Plugins. Sehen Sie sich die Liste der integrierten Plugins und den Laufzeitstatus unter Einstellungen → Integrierte Plugins an.',
+ loading: 'Plugins lesen…',
+ error: 'Es konnten nicht alle Plugins gelesen werden, möglicherweise aufgrund eines Netzwerkproblems',
+ unavailable: 'Diese Bereitstellung läuft ohne verwaltbares Profil, daher können hier keine Plugins installiert oder gewechselt werden.',
+ retry: 'Erneut versuchen',
+ refresh: 'Aktualisieren',
+ refreshError: 'Aktualisierung fehlgeschlagen. Bitte versuchen Sie es erneut.',
+ empty: 'Es sind noch keine Plugins installiert.',
+ addPlugin: 'Plugin hinzufügen',
+ chooseAddMethod: 'Wählen Sie aus, wie Sie ein Plugin hinzufügen möchten',
+ installExisting: 'Installieren Sie ein Drittanbieter-Plugin',
+ installExistingDescription: 'Unterstützt npm-Pakete, Git-Repositorys und lokale Verzeichnisse',
+ restartNotice: 'Starten Sie DSH neu, um die Änderung zu übernehmen. {reason}',
+ restartReasonNoHmr: 'Dieses Profil wendet Änderungen nur beim Start an.',
+ restartReasonPackageMoved: 'Ein installiertes Paket wird nun in ein anderes Verzeichnis aufgelöst, oder die Änderung ersetzt ein Paket, das die DSH-Installation bereitstellt. Deinstallieren Sie ein Paket, bevor Sie es von einem anderen Speicherort installieren.',
+ restartReasonHostModule: 'Die Änderung erreicht die geladenen Module DSH selbst.',
+ restartReasonUnmanagedPlugin: 'Die Änderung betrifft ein Plugin, das außerhalb der Plugin-Liste ausgeführt wird.',
+ restartReasonModuleComparison: 'DSH konnte die geladenen Plugins nicht mit ihren Dateien vergleichen.',
+ overriddenNotice: '{name} wurde gespeichert, aber eine Konfiguration mit höherer Priorität überschreibt es, sodass es nicht wirksam ist',
+ bundlesTitle: 'Installierte Plugins',
+ basicTitle: 'Integrierte Plugin-Einstellungen',
+ extensionsTitle: 'Experimentelle Plugins',
+ moreTitle: 'Experimentelle Plugins',
+ moreAction: 'Mehr',
+ tabsLabel: 'Plugin-Ansichten',
+ installedTab: 'Installiert',
+ statusProblem: 'Problem',
+ statusBeta: 'Experimentell',
+ reasonLabel: 'Grund',
+ metadataError: 'Paketmetadatenfehler: {error}',
+ versionTag: 'v{version}',
+ partsLabel: 'Komponenten',
+ sourceTitle: 'Quelle',
+ sourceSpec: 'Codequelle',
+ sourceBuiltIn: 'Eingebaut',
+ sourceVersion: 'Aktuelle Version',
+ partsEmpty: 'Dieses Plugin-Paket enthält keine Komponenten.',
+ partsCountTotal: '{count} insgesamt',
+ partsCountRunning: '{count} läuft',
+ partsCountOff: '{count} aus',
+ partOff: 'Aus',
+ partsCountFailed: '{count} ist fehlgeschlagen',
+ partsFilter: 'Komponenten filtern',
+ partsFilterEmpty: 'Keine Komponentenübereinstimmungen.',
+ partToggle: 'Aktivieren Sie die Komponente {name}',
+ rowPhasePending: 'Warten auf Abhängigkeiten',
+ rowPhaseLoading: 'Laden',
+ rowPhaseActive: 'Laufen',
+ rowPhaseFailed: 'Problem',
+ rowPhaseUnloading: 'Entladen',
+ enableToggle: 'Aktivieren Sie {name}',
+ applyingChange: 'Anwenden von Änderungen auf {name}',
+ openDetail: 'Siehe {name}',
+ backToList: 'Zurück zu Plugins',
+ crumbRoot: 'Plugins',
+ backToPackage: 'Zurück zu {name}',
+ configureRow: 'Konfigurieren Sie {name}',
+ rowStateIdle: 'Läuft nicht',
+ uninstall: 'Deinstallieren',
+ uninstallLabel: 'Deinstallieren Sie {name}',
+ installTitle: 'Plugin hinzufügen',
+ installDescription: 'Geben Sie den Paketnamen des Plugins, die GitHub-Repository-Adresse oder den lokalen Verzeichnispfad ein.',
+ installSpecLabel: 'Name oder Adresse des Pakets',
+ installSpecPlaceholder: 'zum Beispiel dsh-plugin-whale-pet',
+ installGuideToggle: 'Installationsanleitung und Beispiele',
+ installGuideHide: 'Verstecke den Leitfaden',
+ installGuideIdTitle: 'Geben Sie den NPM-Paketnamen des Plugins ein',
+ installGuideIdExample: 'dsh-plugin-whale-pet',
+ installGuideIdHint: 'Der Plugin-Paketname ist der npm-Paketname (wie dsh-xxx oder @author/plugin): der Teil nach dshplugin add oder pnpm add im README-Installationsbefehl eines Community-Plugins.',
+ installGuideExampleLabel: 'Beispiel: ',
+ installGitTemplateHint: 'Ersetzen Sie dies durch die tatsächliche Git-Repository-Adresse.',
+ installPathTemplateHint: 'Ersetzen Sie dies durch den tatsächlichen Pfad zu Ihrem lokalen Plugin-Verzeichnis.',
+ installGuideFill: 'Beispiel verwenden',
+ installGuideFillAria: 'Verwenden Sie das Beispiel {example}',
+ installGuideSafety: 'Installieren Sie nur Plugins, denen Sie vertrauen: Sie werden mit Ihren Berechtigungen ausgeführt und können DeepSeek Harness beschädigen oder Ihre Daten preisgeben.',
+ installUpgradeNotice: 'Offizielle On-Demand-Plugins bieten Updates an, wenn ihre Version von DSH abweicht. Um andere Plugins zu aktualisieren, deinstallieren Sie sie und installieren Sie sie erneut.',
+ registryToggle: 'Registrierung',
+ registryLegend: 'Die NPM-Registrierung, aus der das Plugin heruntergeladen wird',
+ registryDefault: 'Standardregistrierung',
+ registryOfficial: 'Offizielle npm-Registrierung',
+ registryNpmmirror: 'Spiegel vom chinesischen Festland',
+ registryCustom: 'Benutzerdefinierte Adresse',
+ registryCustomPlaceholder: 'https://npm.example.com/',
+ registryCustomHint: 'Geben Sie eine interne oder private NPM-Registrierungsadresse ein, beginnend mit http:// oder https://.. Wenn eine Anmeldung erforderlich ist, speichern Sie die Anmeldeinformationen in ~/.npmrc auf diesem Computer.',
+ registryCustomInvalid: 'Geben Sie eine Adresse ein, die mit http:// oder https:// beginnt.',
+ registryListSeparator: ', ',
+ sentenceSeparator: ' ',
+ installRun: 'Installieren',
+ installChecking: 'Überprüfen…',
+ installConfirmedTitle: 'Plugin installieren',
+ installConfirmedRefused: 'Dieses Plugin kann nicht installiert werden',
+ installProblemInvalid: 'Dies ist kein Paketname oder keine Adresse, die installiert werden kann: {reason}',
+ installProblemInstalled: 'Dieses Plugin ist bereits installiert. Um es zu aktualisieren, deinstallieren Sie es und installieren Sie es erneut',
+ installProblemShipped: 'Dieses Plugin wird mit DSH geliefert; Durch das Aktualisieren von DSH wird es aktualisiert',
+ installBundledTitle: 'Dieses Plugin wird mit DSH geliefert',
+ installBundledDescription: 'Aktivieren Sie es, ohne eine weitere Kopie herunterzuladen',
+ installProblemNotFound: 'Es wurde kein solches Plugin gefunden',
+ installProblemNotPackage: 'Der Pfad existiert nicht oder ist kein gültiges Plugin-Paket',
+ installProblemNotBundle: 'Dieses Paket deklariert kein Bundle und kann daher nicht als Plugin installiert werden: {reason}',
+ installProblemNetwork: 'Die Plugin-Registrierung konnte nicht erreicht werden; Überprüfen Sie das Netzwerk und versuchen Sie es erneut',
+ installProblemNetworkAll: 'Es konnte keine Registry erreicht werden (versucht: {registries}); Überprüfen Sie die Netzwerk- oder Proxy-Einstellungen oder ändern Sie die Registrierung',
+ installProblemUnknown: 'Das Plugin konnte nicht gefunden werden: {reason}',
+ installingTitle: 'Plugin installieren…',
+ installedTitle: 'Installiert',
+ statusNotInstalled: 'Nicht installiert',
+ statusMissingFiles: 'Paket nicht verfügbar',
+ statusUpdateAvailable: 'Update verfügbar',
+ sourceNotInstalled: 'Nicht installiert',
+ sourceTargetSpec: 'Quelle hierfür DSH',
+ sourceTargetVersion: 'Version für dieses DSH',
+ update: 'Aktualisieren',
+ updateLabel: 'Aktualisieren Sie {name}',
+ versionsTitle: 'Versionen',
+ versionsRecheck: 'Überprüfen Sie es noch einmal',
+ versionsChecking: 'Suche nach Updates',
+ versionsAvailable: 'Update auf {version} verfügbar',
+ versionsPublished: 'Veröffentlicht {date}',
+ versionsLatest: 'Auf dem neuesten Stand',
+ versionsCheckedAt: 'Zuletzt überprüft bei {time}',
+ versionsBlocked: 'Neuere Versionen benötigen ein DSH-Update',
+ versionsBlockedDetail: '{version} benötigt {peers}; dieser DSH ist {runtime}',
+ versionsNoneInstallable: 'Keine neuere Version zur direkten Installation',
+ versionsNoneInstallableDetail: 'Die neueren Versionen sind veraltet, Vorabversionen oder deklarieren kein Bundle. Erweitern Sie, um sie zu sehen',
+ versionsFailedNetwork: 'Die Registrierung konnte nicht erreicht werden',
+ versionsFailedOther: 'Es konnte nicht nach Updates gesucht werden',
+ versionsDetails: 'Details',
+ versionsNewerCount: '{count} neuer',
+ versionsCollapse: 'Einklappen',
+ versionsInstalledTag: 'Installiert',
+ versionsPrereleaseTag: 'Vorabversion',
+ versionsDeprecatedTag: 'Veraltet',
+ versionsNeedsDsh: 'Benötigt {peers}',
+ versionsNotBundle: 'Diese Version deklariert kein Bundle und kann nicht installiert werden',
+ versionsUpdateLabel: 'Update auf {version}',
+ versionsRegistry: 'Registrierung',
+ versionsCheckedAtLabel: 'Geprüft bei',
+ versionsFallbackTag: 'Rückfall',
+ versionsFallbackHint: '{registry} konnte nicht erreicht werden; Die Liste stammt aus einer Fallback-Registrierung',
+ versionsPrimaryTag: 'Erstens',
+ versionsShowPrerelease: 'Vorabversionen anzeigen',
+ versionsOmitted: '{count} Weitere Versionen, die neuer als die installierte sind, werden nicht aufgeführt',
+ versionsProblemNetwork: 'Unerreichbar',
+ versionsProblemNotFound: 'Nicht gefunden',
+ versionsProblemUnknown: 'Fehlgeschlagen',
+ updatesAvailable: 'Verfügbare Updates: {count}',
+ updatesNone: 'Alle Plugins sind auf dem neuesten Stand',
+ updatesFailed: 'Einige Plugins konnten nicht überprüft werden',
+ updateTitle: 'Aktualisieren Sie {name}',
+ updateSource: 'Installieren von',
+ updateDescription: 'Das Update ersetzt {from} durch {to}; Das Plugin stoppt möglicherweise kurzzeitig. Wenn die Installation fehlschlägt, bleibt {from} erhalten oder wird erneut installiert.',
+ updateRunning: 'Aktualisierung',
+ updateStepChecking: 'Überprüfen Sie {version}',
+ updateStepRemoving: 'Stoppen und entfernen Sie {version}',
+ updateStepInstalling: 'Installieren Sie {version} von {registry}',
+ updateStepRestoring: 'Installieren Sie {version} erneut',
+ updateDone: 'Aktualisiert auf {version}',
+ updateDoneDetail: 'Die neue Version läuft.',
+ updateRestart: '{version} installiert; es wird erst nach einem Neustart wirksam',
+ updateRefused: 'Aktualisierung auf {version} nicht möglich',
+ updateRemoveFailed: '{version} konnte nicht entfernt werden',
+ updateRestored: 'Aktualisierung fehlgeschlagen; wiederhergestellt {version}',
+ updateRestoredDetail: '{to} konnte nicht installiert werden; {from} wurde erneut installiert.',
+ updateKept: 'Konnte nicht auf {version} aktualisiert werden',
+ updateKeptDetail: '{from} wurde nicht entfernt und wird weiterhin verwendet. {reason}',
+ updateBroken: 'Aktualisierung fehlgeschlagen; Das Plugin ist nicht installiert',
+ updateBrokenDetail: 'Die Installation von {to} ist fehlgeschlagen, und auch die erneute Installation von {from} schlug fehl. Installieren Sie {name} erneut über Plugin hinzufügen.',
+ updateLost: 'Das Update-Ergebnis ist unbekannt',
+ updateLostDetail: 'Die Verbindung zu DSH wurde unterbrochen: {reason}. Aktualisieren Sie die Plugin-Liste, um die installierte Version anzuzeigen.',
+ updateOutput: 'Installationsausgabe anzeigen',
+ updateFinish: 'Fertig',
+ installFailedTitle: 'Das Plugin konnte nicht installiert werden',
+ installGithubFailedTitle: 'Auf GitHub kann nicht zugegriffen werden',
+ installGithubTimeoutTitle: 'Zeitüberschreitung bei der GitHub-Verbindung',
+ installGithubFailedDescription: 'Versuchen Sie es mit einer anderen Installationsquelle.',
+ installUseGithubMirror: 'Verwenden Sie einen Spiegel vom chinesischen Festland',
+ installTryAnotherWay: 'Versuchen Sie es anders',
+ installPackageLabel: 'Name des Plugin-Pakets',
+ installEdit: 'Bearbeiten',
+ installEditAria: 'Zurück zur Bearbeitung',
+ installCancelAndEdit: 'Brechen Sie die Installation ab und kehren Sie zur Bearbeitung zurück',
+ installApplyingCancellationError: 'Die Stornierung wurde nicht bestätigt. Installation wird angewendet; warte auf das Ergebnis. {reason}',
+ installReconcile: 'Überprüfen Sie den Installationsstatus',
+ installUnknownTitle: 'Installationsergebnis nicht verfügbar',
+ installUnknownDescription: 'Der Host verfügt über keine aktive Installation mit dieser Anforderungs-ID. Überprüfen Sie die Plugin-Liste, bevor Sie es erneut versuchen.',
+ installResultUnconfirmed: 'Das Installationsergebnis wurde nicht empfangen. Überprüfen Sie den Installationsstatus. {reason}',
+ installAwaitingAcceptance: 'Warten darauf, dass der Host die Installation akzeptiert. Die Stornierung wird nach der Bestätigung automatisch wiederholt.',
+ installBackgroundUnknown: 'Installationsergebnis nicht verfügbar. Überprüfen Sie die Plugin-Liste.',
+ installCancel: 'Installation abbrechen',
+ installCloseCancels: 'Installation abbrechen und schließen',
+ installViewTask: 'Installation ansehen',
+ installUnconfirmedTitle: 'Installationsstatus unbestätigt',
+ installBackgroundDone: 'Installation abgeschlossen. Installationsdetails anzeigen.',
+ installBackgroundFailed: 'Die Installation ist fehlgeschlagen. Installationsdetails anzeigen.',
+ installBackgroundUnconfirmed: 'Der Installationsstatus ist unbestätigt. Weitere Informationen finden Sie in der Installation.',
+ installBackgroundApplying: 'Die Installation wird ausgeführt und kann nicht abgebrochen werden. Installationsfortschritt anzeigen.',
+ installStarting: 'Installation vorbereiten…',
+ installCancelling: 'Installation wird gestoppt…',
+ installApplying: 'Konfiguration wird angewendet, bitte warten…',
+ installCancelledShort: 'Abgesagt',
+ installCancelled: 'Installation abgebrochen; Das Plugin ist nicht aktiviert und heruntergeladene Dateien bleiben möglicherweise bestehen',
+ installCancelUnconfirmed: 'Die Installation wurde nicht bestätigt und gestoppt. Versuchen Sie den Abbruch erneut oder warten Sie auf das Installationsergebnis. {reason}',
+ installEnableNow: 'Jetzt aktivieren',
+ installDetailsShow: 'Installationsdetails anzeigen',
+ installDetailsHide: 'Installationsdetails ausblenden',
+ installVersion: 'Version {version}',
+ installSubjectPath: 'Lokales Verzeichnis',
+ installSubjectGit: 'Git-Repository',
+ installSubjectTarball: 'Tarball',
+ installLocation: 'Wird in {dir} installiert',
+ installRetry: 'Erneut versuchen',
+ installChangeRegistry: 'Registrierung ändern',
+ installAttempt: '{previous} konnte das Paket nicht bereitstellen; erneuter Versuch über {registry} (Registrierung {index} von {total})',
+ installAttemptBadge: 'Versuchen Sie {index} · {registry}',
+ installFailureNetwork: 'Die Netzwerkverbindung ist fehlgeschlagen',
+ installFailureNetworkAll: 'Es konnte keine Registry erreicht werden (versucht: {registries}). Überprüfen Sie die Netzwerk- oder Proxy-Einstellungen oder ändern Sie die Registrierung und versuchen Sie es erneut.',
+ installFailureNetworkHost: '{host} konnte nicht erreicht werden. Eine GitHub-Adresse oder ein .tgz-Link wird nicht über die Registrierung abgerufen: Dieser Computer muss sie direkt oder über einen Proxy erreichen. Wenn das Plugin auch auf npm veröffentlicht wird, geben Sie stattdessen seinen Paketnamen ein.',
+ installFailureNotFound: 'Es wurde kein solches Plugin gefunden',
+ installFailureNoMatchingVersion: 'Keine Version entspricht der Anfrage',
+ installFailureDiskFull: 'Die Festplatte ist voll; Die Installation wurde gestoppt',
+ installFailurePermission: 'Keine Schreibberechtigung; Das Plugin kann nicht installiert werden',
+ installFailureBuildBlocked: 'Die Installationsskripte einer Abhängigkeit benötigen Ihre Erlaubnis, bevor die Installation fortgesetzt werden kann',
+ installFailureBuildBlockedManual: 'pnpm blockierte Installationsskripte; Erlauben Sie sie unter „allowBuilds“ in „pnpm-workspace.yaml“ und versuchen Sie es erneut',
+ installFailureIntegrity: 'Das heruntergeladene Paket hat die Integritätsprüfung nicht bestanden',
+ installFailureTimeout: 'Bei der Installation ist eine Zeitüberschreitung aufgetreten',
+ installFailurePnpmMissing: 'pnpm wurde nicht gefunden, daher kann nichts installiert werden',
+ installFailureGeneric: 'Bei der Installation ist ein Fehler aufgetreten. Die Details sagen was',
+ terminalCommandLine: 'Befehlszeile {n}',
+ terminalRunning: 'Laufen',
+ terminalFailed: 'Fehlgeschlagen',
+ terminalDone: 'Fertig',
+ terminalCopy: 'Kopieren',
+ terminalCopied: 'Kopiert',
+ terminalNoOutput: 'Keine Ausgabe',
+ terminalCollapseAria: 'Ausgabe reduzieren',
+ terminalCollapse: 'Einklappen',
+ terminalExpandAria: 'Erweitern Sie die verbleibenden {n}-Ausgabezeilen',
+ terminalExpand: '… {n} weitere Zeilen',
+ terminalExitCode: 'Exit-Code {code}',
+ terminalSignal: 'Signal {signal}',
+ terminalNoExitCode: 'kein Exit-Code',
+ installDoneNothing: 'Die Installation ist ohne neue Abhängigkeit abgeschlossen.',
+ installDoneRestart: 'Installiert; Starten Sie DSH neu, um es zu laden. {reason}',
+ installDoneApproved: 'Für {names} zulässige Installationsskripte',
+ installDoneOtherVersion: 'Gemäß der Supply-Chain-Sicherheitsrichtlinie von pnpm (minimumReleaseAge) werden Versionen, die jünger als das erforderliche Alter veröffentlicht wurden, nicht automatisch installiert. Daher wurde bei dieser Installation {installed} anstelle der neuesten Version {version} verwendet. Eine neu veröffentlichte Version hat diese Wartezeit nicht durchlaufen und kann unerkannten Schadcode oder Mängel enthalten. Wenn Sie {version} benötigen, deinstallieren Sie die aktuelle Version und installieren Sie dann {exact}, um diese Version anzufordern.',
+ installApprovalTitle: 'Installationsskripte benötigen eine Genehmigung',
+ installApprovalDescription: 'Diese Pakete verfügen über Installationsskripte, die pnpm nicht ausgeführt hat.',
+ installApprovalConsequence: 'Sobald sie zugelassen sind, werden die Skripte hier mit Ihren Berechtigungen ausgeführt und die Berechtigung wird in diesem Profil gespeichert.',
+ installApprovalCaution: 'Lassen Sie nur Pakete zu, denen Sie vertrauen.',
+ installApproveAndRetry: 'Lassen Sie diese Skripte zu und versuchen Sie es erneut',
+ installClose: 'Fertig',
+ close: 'Schließen',
+ cancel: 'Abbrechen',
+ confirmUninstallTitle: '„{name}“ deinstallieren?',
+ confirmUninstallDescription: 'Was es bietet, verschwindet, sobald es deinstalliert wird.',
+ confirmUninstall: 'Deinstallieren',
+ failedEnable: 'Konnte nicht aktiviert werden: {reason}',
+ failedDisable: 'Konnte nicht deaktiviert werden: {reason}',
+ failedUninstall: 'Konnte nicht deinstalliert werden: {reason}',
+ failedRowEnable: 'Die Komponente {reason} konnte nicht aktiviert werden',
+ failedRowDisable: 'Die Komponente {reason} konnte nicht deaktiviert werden',
+ reasonManagementRequired: 'Das Plugin-Management braucht es; Es kann nicht ausgeschaltet oder deinstalliert werden.',
+ reasonUnaddressable: 'Der Profil-Patch kann dieses Problem nicht eindeutig beheben.',
+ reasonUnknownPlugin: 'Kein solches Plugin.',
+ reasonInvalidSpec: 'Geben Sie einen gültigen Paketnamen oder eine gültige Paketadresse ein.',
+ reasonAlreadyInstalled: 'Dieses Paket ist bereits installiert; Deinstallieren Sie es, bevor Sie eine andere Version installieren.',
+ reasonAmbiguousInstall: 'Welches Paket installiert wurde, lässt sich anhand der Abhängigkeitsänderung nicht erkennen.',
+ reasonNotBundle: 'Dieses Paket deklariert kein Bundle und kann daher nicht als Plugin verwaltet werden.',
+ reasonNotRemovable: 'Dieses Paket gehört nicht dem Profil oder die Plugin-Verwaltung benötigt es.',
+ reasonStopProfile: 'Dieses Profil läuft ohne HMR; Stoppen Sie es und deinstallieren Sie das Paket mit dem dsh-Plugin.',
+ reasonBundleInUse: 'Andere Konfigurationen verwenden weiterhin die Komponenten dieses Bundles; Schalten Sie sie zuerst aus.',
+ reasonStaleApproval: 'Die ausstehenden Drehbuchgenehmigungen haben sich geändert; Installieren Sie es erneut, um sie zu aktualisieren.',
+ reasonIncompatibleVersion: '{plugin} ist nicht kompatibel mit DSH {runtime} (erfordert {requirements}); Die Ausführung kann zu Abstürzen oder Datenverlust führen.',
+ reasonIncompatibleVersionUnnamed: 'Dieses Plugin ist nicht mit der laufenden DSH-Version kompatibel. Die Ausführung kann zu Abstürzen oder Datenverlust führen.',
+ reasonIncompatibleInstall: 'Installieren Sie eine mit diesem DSH kompatible Plugin-Version.',
+ reasonIncompatibleInstalled: 'Deinstallieren Sie es und installieren Sie eine mit diesem DSH kompatible Version.',
+ reasonOperationError: 'Der Host hat einen Fehler gemeldet.',
+ detailTitle: 'Details',
+ detailLoading: 'Details lesen',
+ detailTooLarge: 'Die Details überschreiten die Größenbeschränkung und wurden nicht geladen.',
+ detailNetwork: 'Die Quelle der Details konnte nicht erreicht werden.',
+ detailInvalid: 'Das Plugin deklariert eine ungültige Detaildatei.',
+ detailFailed: 'Die Details konnten nicht gelesen werden.',
+ detailFootnotes: 'Fußnoten',
+} satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = {
+ panel: 'Плагины',
+ title: 'Плагины',
+ intro: 'Установите, включите и настройте плагины',
+ infoLabel: 'О плагинах',
+ infoDescription: 'Настройте официальные плагины, а также установите или управляйте другими плагинами здесь. Посмотреть список встроенных плагинов и статус выполнения можно в разделе «Настройки» → «Встроенные плагины».',
+ loading: 'Чтение плагинов…',
+ error: 'Не удалось прочитать все плагины, возможно, из-за проблем с сетью.',
+ unavailable: 'Это развертывание выполняется без управляемого профиля, поэтому здесь нельзя устанавливать или переключать плагины.',
+ retry: 'Повторить',
+ refresh: 'Обновить',
+ refreshError: 'Обновить не удалось. Пожалуйста, попробуйте еще раз.',
+ empty: 'Плагины пока не установлены.',
+ addPlugin: 'Добавить плагин',
+ chooseAddMethod: 'Выберите, как добавить плагин',
+ installExisting: 'Установите сторонний плагин',
+ installExistingDescription: 'Поддерживает пакеты npm, репозитории Git и локальные каталоги.',
+ restartNotice: 'Перезапустите DSH, чтобы применить изменения. {reason}',
+ restartReasonNoHmr: 'Этот профиль применяет изменения только при запуске.',
+ restartReasonPackageMoved: 'Установленный пакет теперь разрешается в другой каталог, или изменение заменяет пакет, предоставляемый установкой DSH. Удалите пакет перед установкой его из другого места.',
+ restartReasonHostModule: 'Изменение доходит до загруженных модулей DSH.',
+ restartReasonUnmanagedPlugin: 'Изменение достигает плагина, работающего за пределами списка плагинов.',
+ restartReasonModuleComparison: 'DSH не смог сравнить загруженные плагины с их файлами.',
+ overriddenNotice: '{name} был сохранен, но конфигурация с более высоким приоритетом переопределяет его, поэтому он не действует.',
+ bundlesTitle: 'Установленные плагины',
+ basicTitle: 'Встроенные настройки плагина',
+ extensionsTitle: 'Экспериментальные плагины',
+ moreTitle: 'Экспериментальные плагины',
+ moreAction: 'Ещё',
+ tabsLabel: 'Просмотры плагинов',
+ installedTab: 'Установлено',
+ statusProblem: 'Проблема',
+ statusBeta: 'Экспериментальный',
+ reasonLabel: 'Причина',
+ metadataError: 'Ошибка метаданных пакета: {error}.',
+ versionTag: 'v{version}',
+ partsLabel: 'Компоненты',
+ sourceTitle: 'Источник',
+ sourceSpec: 'Источник кода',
+ sourceBuiltIn: 'Встроенный',
+ sourceVersion: 'Текущая версия',
+ partsEmpty: 'Этот пакет плагинов не содержит компонентов.',
+ partsCountTotal: '{count} всего',
+ partsCountRunning: '{count} работает',
+ partsCountOff: '{count} выкл.',
+ partOff: 'Выкл.',
+ partsCountFailed: '{count} не удалось',
+ partsFilter: 'Компоненты фильтра',
+ partsFilterEmpty: 'Ни один компонент не соответствует.',
+ partToggle: 'Включить компонент {name}',
+ rowPhasePending: 'Ожидание зависимостей',
+ rowPhaseLoading: 'Загрузка',
+ rowPhaseActive: 'Бег',
+ rowPhaseFailed: 'Проблема',
+ rowPhaseUnloading: 'Разгрузка',
+ enableToggle: 'Включить {name}',
+ applyingChange: 'Применение изменений в {name}',
+ openDetail: 'Посмотреть {name}',
+ backToList: 'Вернуться к плагинам',
+ crumbRoot: 'Плагины',
+ backToPackage: 'Вернуться к {name}',
+ configureRow: 'Настроить {name}',
+ rowStateIdle: 'Не работает',
+ uninstall: 'Удалить',
+ uninstallLabel: 'Удалить {name}',
+ installTitle: 'Добавить плагин',
+ installDescription: 'Введите имя пакета плагина, адрес репозитория GitHub или путь к локальному каталогу.',
+ installSpecLabel: 'Название или адрес пакета',
+ installSpecPlaceholder: 'например dsh-plugin-whale-pet',
+ installGuideToggle: 'Руководство по установке и примеры',
+ installGuideHide: 'Скрыть руководство',
+ installGuideIdTitle: 'Введите имя пакета npm плагина.',
+ installGuideIdExample: 'dsh-плагин-кит-питомец',
+ installGuideIdHint: 'Имя пакета плагина — это имя пакета npm (например, dsh-xxx или @author/plugin): часть после добавления плагина dsh или добавления pnpm в команде установки README плагина сообщества.',
+ installGuideExampleLabel: 'Пример: ',
+ installGitTemplateHint: 'Замените это фактическим адресом репозитория Git.',
+ installPathTemplateHint: 'Замените это фактическим путем к вашему локальному каталогу плагина.',
+ installGuideFill: 'Используйте пример',
+ installGuideFillAria: 'Используйте пример {example}',
+ installGuideSafety: 'Устанавливайте только те плагины, которым вы доверяете: они запускаются с вашими разрешениями и могут повредить DeepSeek Harness или привести к утечке ваших данных.',
+ installUpgradeNotice: 'Официальные плагины по требованию предлагают обновление, если их версия отличается от DSH. Чтобы обновить другие плагины, удалите и переустановите их.',
+ registryToggle: 'Реестр',
+ registryLegend: 'Реестр npm, из которого загружается плагин.',
+ registryDefault: 'Реестр по умолчанию',
+ registryOfficial: 'Официальный реестр npm',
+ registryNpmmirror: 'Зеркало материкового Китая',
+ registryCustom: 'Пользовательский адрес',
+ registryCustomPlaceholder: 'https://npm.example.com/',
+ registryCustomHint: 'Введите внутренний или частный адрес реестра npm, начинающийся с http:// или https://.. Если требуется вход в систему, сохраните учетные данные в ~/.npmrc на этом компьютере.',
+ registryCustomInvalid: 'Введите адрес, начинающийся с http:// или https://.',
+ registryListSeparator: ', ',
+ sentenceSeparator: ' ',
+ installRun: 'Установить',
+ installChecking: 'Проверка…',
+ installConfirmedTitle: 'Установить плагин',
+ installConfirmedRefused: 'Этот плагин не может быть установлен',
+ installProblemInvalid: 'Это не имя или адрес пакета, который можно установить: {reason}.',
+ installProblemInstalled: 'Этот плагин уже установлен. Чтобы обновить его, удалите его и установите снова.',
+ installProblemShipped: 'Этот плагин поставляется с DSH; обновление DSH обновляет его',
+ installBundledTitle: 'Этот плагин поставляется с DSH.',
+ installBundledDescription: 'Включите его, не загружая другую копию.',
+ installProblemNotFound: 'Такой плагин не найден',
+ installProblemNotPackage: 'Путь не существует или не является допустимым пакетом плагина.',
+ installProblemNotBundle: 'Этот пакет не декларирует отсутствие пакета, поэтому его нельзя установить как плагин: {reason}',
+ installProblemNetwork: 'Не удалось получить доступ к реестру плагинов; проверьте сеть и попробуйте еще раз',
+ installProblemNetworkAll: 'Не удалось получить доступ к реестру (пробовал: {registries}); проверьте настройки сети или прокси или измените реестр',
+ installProblemUnknown: 'Не удалось найти плагин: {reason}.',
+ installingTitle: 'Установка плагина…',
+ installedTitle: 'Установлено',
+ statusNotInstalled: 'Не установлено',
+ statusMissingFiles: 'Пакет недоступен',
+ statusUpdateAvailable: 'Доступно обновление',
+ sourceNotInstalled: 'Не установлено',
+ sourceTargetSpec: 'Источник для этого DSH',
+ sourceTargetVersion: 'Версия для этого DSH',
+ update: 'Обновить',
+ updateLabel: 'Обновление {name}',
+ versionsTitle: 'Версии',
+ versionsRecheck: 'Проверьте еще раз',
+ versionsChecking: 'Проверка обновлений',
+ versionsAvailable: 'Доступно обновление до {version}.',
+ versionsPublished: 'Опубликовано {date}',
+ versionsLatest: 'Актуально',
+ versionsCheckedAt: 'Последняя проверка на {time}.',
+ versionsBlocked: 'Для более новых версий требуется обновление DSH.',
+ versionsBlockedDetail: '{version} нужен {peers}; этот DSH - это {runtime}',
+ versionsNoneInstallable: 'Нет более новой версии для установки напрямую',
+ versionsNoneInstallableDetail: 'Более новые версии устарели, являются предварительными выпусками или не содержат пакетов. Разверните, чтобы увидеть их',
+ versionsFailedNetwork: 'Не удалось войти в реестр',
+ versionsFailedOther: 'Не удалось проверить наличие обновлений',
+ versionsDetails: 'Подробности',
+ versionsNewerCount: '{count} новее',
+ versionsCollapse: 'Свернуть',
+ versionsInstalledTag: 'Установлено',
+ versionsPrereleaseTag: 'Пререлиз',
+ versionsDeprecatedTag: 'Устарело',
+ versionsNeedsDsh: 'Требуется {peers}',
+ versionsNotBundle: 'Эта версия не декларирует отсутствие пакета и не может быть установлена.',
+ versionsUpdateLabel: 'Обновление до {version}',
+ versionsRegistry: 'Реестр',
+ versionsCheckedAtLabel: 'Проверено в',
+ versionsFallbackTag: 'Резервный вариант',
+ versionsFallbackHint: '{registry} не удалось связаться; список взят из резервного реестра',
+ versionsPrimaryTag: 'Первый',
+ versionsShowPrerelease: 'Показать пререлизы',
+ versionsOmitted: '{count} больше версий новее установленной не указано',
+ versionsProblemNetwork: 'недостижимый',
+ versionsProblemNotFound: 'Не найден',
+ versionsProblemUnknown: 'Не удалось',
+ updatesAvailable: 'Доступные обновления: {count}',
+ updatesNone: 'Все плагины актуальны',
+ updatesFailed: 'Некоторые плагины не удалось проверить',
+ updateTitle: 'Обновление {name}',
+ updateSource: 'Установить из',
+ updateDescription: 'Обновление заменяет {from} на {to}; плагин может ненадолго остановиться. Если установка не удалась, {from} сохраняется или устанавливается снова.',
+ updateRunning: 'Обновление',
+ updateStepChecking: 'Проверьте {version}',
+ updateStepRemoving: 'Остановите и удалите {version}.',
+ updateStepInstalling: 'Установите {version} из {registry}.',
+ updateStepRestoring: 'Установите {version} еще раз.',
+ updateDone: 'Обновлено до {version}.',
+ updateDoneDetail: 'Новая версия работает.',
+ updateRestart: '{version} установлен; оно вступит в силу после перезагрузки',
+ updateRefused: 'Не могу обновиться до {version}.',
+ updateRemoveFailed: 'Не удалось удалить {version}.',
+ updateRestored: 'Обновление не удалось; восстановлен {version}',
+ updateRestoredDetail: '{to} не удалось установить; {from} был установлен снова.',
+ updateKept: 'Не удалось обновиться до {version}.',
+ updateKeptDetail: '{from} не был удален и продолжает использоваться. {reason}',
+ updateBroken: 'Обновление не удалось; плагин не установлен',
+ updateBrokenDetail: '{to} не удалось установить, и повторная установка {from} также не удалась. Установите {name} снова из меню «Добавить плагин».',
+ updateLost: 'Результат обновления неизвестен',
+ updateLostDetail: 'Соединение с DSH было прервано: {reason}. Обновите список плагинов, чтобы увидеть установленную версию.',
+ updateOutput: 'Показать выходные данные установки',
+ updateFinish: 'Готово',
+ installFailedTitle: 'Плагин не удалось установить',
+ installGithubFailedTitle: 'Не могу получить доступ к GitHub',
+ installGithubTimeoutTitle: 'Время ожидания соединения GitHub истекло',
+ installGithubFailedDescription: 'Попробуйте другой источник установки.',
+ installUseGithubMirror: 'Используйте зеркало материкового Китая.',
+ installTryAnotherWay: 'Попробуйте другой способ',
+ installPackageLabel: 'Название пакета плагина',
+ installEdit: 'Редактировать',
+ installEditAria: 'Вернуться к редактированию',
+ installCancelAndEdit: 'Отменить установку и вернуться к редактированию',
+ installApplyingCancellationError: 'Отмена не подтвердилась. Установка применяется; дождитесь его результата. {reason}',
+ installReconcile: 'Проверьте статус установки',
+ installUnknownTitle: 'Результат установки недоступен',
+ installUnknownDescription: 'На хосте нет активной установки с этим идентификатором запроса. Прежде чем повторить попытку, проверьте список плагинов.',
+ installResultUnconfirmed: 'Результат установки не получен. Проверьте статус установки. {reason}',
+ installAwaitingAcceptance: 'Ожидание, пока хост примет установку. Отмена будет повторена автоматически после подтверждения.',
+ installBackgroundUnknown: 'Результат установки недоступен. Проверьте список плагинов.',
+ installCancel: 'Отменить установку',
+ installCloseCancels: 'Отменить установку и закрыть',
+ installViewTask: 'Посмотреть установку',
+ installUnconfirmedTitle: 'Статус установки не подтвержден',
+ installBackgroundDone: 'Установка завершена. Посмотреть подробности установки.',
+ installBackgroundFailed: 'Установка не удалась. Посмотреть подробности установки.',
+ installBackgroundUnconfirmed: 'Статус установки не подтвержден. Подробности смотрите в установке.',
+ installBackgroundApplying: 'Установка применяется и не может быть отменена. Просмотр хода установки.',
+ installStarting: 'Подготовка к установке…',
+ installCancelling: 'Остановка установки…',
+ installApplying: 'Применяется конфигурация, пожалуйста, подождите…',
+ installCancelledShort: 'Отменено',
+ installCancelled: 'Установка отменена; плагин не включен и скачанные файлы могут остаться',
+ installCancelUnconfirmed: 'Установка не подтверждена, остановлена. Повторите попытку отмены или дождитесь результата установки. {reason}',
+ installEnableNow: 'Включить сейчас',
+ installDetailsShow: 'Показать детали установки',
+ installDetailsHide: 'Скрыть сведения об установке',
+ installVersion: 'Версия {version}',
+ installSubjectPath: 'Локальный каталог',
+ installSubjectGit: 'Git-репозиторий',
+ installSubjectTarball: 'Тарбол',
+ installLocation: 'Устанавливается в {dir}',
+ installRetry: 'Повторить',
+ installChangeRegistry: 'Изменить реестр',
+ installAttempt: '{previous} не смог обслужить пакет; повторная попытка через {registry} (реестр {index} из {total})',
+ installAttemptBadge: 'Попытка {index} · {registry}',
+ installFailureNetwork: 'Сетевое подключение не удалось',
+ installFailureNetworkAll: 'Не удалось достичь реестра (пробовал: {registries}). Проверьте настройки сети или прокси-сервера или измените реестр и повторите попытку.',
+ installFailureNetworkHost: 'Не удалось связаться с {host}. Адрес GitHub или ссылка .tgz не извлекаются через реестр: этот компьютер должен получить доступ к ним напрямую или через прокси. Если плагин также опубликован в npm, вместо этого введите имя его пакета.',
+ installFailureNotFound: 'Такой плагин не найден',
+ installFailureNoMatchingVersion: 'Ни одна версия не соответствует запросу',
+ installFailureDiskFull: 'Диск заполнен; установка остановлена',
+ installFailurePermission: 'Нет разрешения на запись; плагин не может быть установлен',
+ installFailureBuildBlocked: 'Для продолжения установки сценариям установки зависимости требуется ваше разрешение.',
+ installFailureBuildBlockedManual: 'pnpm блокировал сценарии установки; разрешите их в разделеallowBuilds в pnpm-workspace.yaml и повторите попытку.',
+ installFailureIntegrity: 'Загруженный пакет не прошел проверку целостности.',
+ installFailureTimeout: 'Время установки истекло',
+ installFailurePnpmMissing: 'pnpm не найден, поэтому ничего установить невозможно',
+ installFailureGeneric: 'Во время установки что-то пошло не так; детали говорят о чем',
+ terminalCommandLine: 'Командная строка {n}',
+ terminalRunning: 'Бег',
+ terminalFailed: 'Не удалось',
+ terminalDone: 'Готово',
+ terminalCopy: 'Копировать',
+ terminalCopied: 'Скопировано',
+ terminalNoOutput: 'Нет вывода',
+ terminalCollapseAria: 'Свернуть вывод',
+ terminalCollapse: 'Свернуть',
+ terminalExpandAria: 'Разверните оставшиеся выходные строки {n}.',
+ terminalExpand: '… {n} еще строки',
+ terminalExitCode: 'код выхода {code}',
+ terminalSignal: 'сигнал {signal}',
+ terminalNoExitCode: 'нет кода выхода',
+ installDoneNothing: 'Установка завершена без новых зависимостей.',
+ installDoneRestart: 'Установлен; перезапустите DSH, чтобы загрузить его. {reason}',
+ installDoneApproved: 'Скрипты установки разрешены для {names}',
+ installDoneOtherVersion: 'В соответствии с политикой безопасности цепочки поставок pnpm (minimumReleaseAge) версии, опубликованные позже требуемого возраста, не устанавливаются автоматически, поэтому в этой установке использовалась {installed} вместо последней версии {version}. Недавно опубликованная версия не завершила этот период ожидания и может содержать необнаруженный вредоносный код или дефекты. Если вам нужен {version}, удалите текущую версию, а затем установите {exact}, чтобы запросить эту версию.',
+ installApprovalTitle: 'Для установки скриптов требуется разрешение',
+ installApprovalDescription: 'Эти пакеты содержат сценарии установки, которые pnpm не запускал.',
+ installApprovalConsequence: 'После разрешения сценарии запускаются здесь с вашими разрешениями, а разрешение сохраняется в этом профиле.',
+ installApprovalCaution: 'Разрешите только те пакеты, которым вы доверяете.',
+ installApproveAndRetry: 'Разрешите эти сценарии и повторите попытку.',
+ installClose: 'Готово',
+ close: 'Закрыть',
+ cancel: 'Отмена',
+ confirmUninstallTitle: 'Удалить "{name}"?',
+ confirmUninstallDescription: 'То, что он предоставляет, исчезает после его удаления.',
+ confirmUninstall: 'Удалить',
+ failedEnable: 'Не удалось включить: {reason}',
+ failedDisable: 'Не удалось отключить: {reason}',
+ failedUninstall: 'Не удалось удалить: {reason}',
+ failedRowEnable: 'Не удалось включить компонент: {reason}.',
+ failedRowDisable: 'Не удалось отключить компонент: {reason}.',
+ reasonManagementRequired: 'Управление плагинами нуждается в этом; его нельзя отключить или удалить.',
+ reasonUnaddressable: 'Патч профиля не может решить эту проблему однозначно.',
+ reasonUnknownPlugin: 'Нет такого плагина.',
+ reasonInvalidSpec: 'Введите действительное имя или адрес пакета.',
+ reasonAlreadyInstalled: 'Этот пакет уже установлен; удалите его перед установкой другой версии.',
+ reasonAmbiguousInstall: 'Какой пакет был установлен, невозможно определить по изменению зависимостей.',
+ reasonNotBundle: 'В этом пакете не объявлен пакет, поэтому им нельзя управлять как плагином.',
+ reasonNotRemovable: 'Этот пакет не принадлежит профилю, или он нужен управлению плагинами.',
+ reasonStopProfile: 'Этот профиль работает без HMR; остановите его и удалите пакет с плагином dsh.',
+ reasonBundleInUse: 'Другая конфигурация по-прежнему использует компоненты этого пакета; сначала выключите их.',
+ reasonStaleApproval: 'Ожидаемые утверждения сценария изменились; установите еще раз, чтобы обновить их.',
+ reasonIncompatibleVersion: '{plugin} несовместим с DSH {runtime} (требуется {requirements}); его запуск может привести к сбоям или потере данных.',
+ reasonIncompatibleVersionUnnamed: 'Этот плагин несовместим с работающей версией DSH; его запуск может привести к сбоям или потере данных.',
+ reasonIncompatibleInstall: 'Установите версию плагина, совместимую с этим DSH.',
+ reasonIncompatibleInstalled: 'Удалите его и установите версию, совместимую с этим DSH.',
+ reasonOperationError: 'Хост сообщил об ошибке.',
+ detailTitle: 'Подробности',
+ detailLoading: 'Читаем подробности',
+ detailTooLarge: 'Детали превышают лимит размера и не были загружены.',
+ detailNetwork: 'До источника подробностей добраться не удалось.',
+ detailInvalid: 'Плагин объявляет неверный файл сведений.',
+ detailFailed: 'Подробности прочитать не удалось.',
+ detailFootnotes: 'Сноски',
+} satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = {
+ panel: 'Plug-ins',
+ title: 'Plug-ins',
+ intro: 'Instalar, habilitar e configurar plug-ins',
+ infoLabel: 'Sobre plug-ins',
+ infoDescription: 'Configure plugins oficiais e instale ou gerencie outros plugins aqui. Veja a lista de plug-ins integrados e o status do tempo de execução em Configurações → Plug-ins integrados.',
+ loading: 'Lendo plug-ins…',
+ error: 'Não foi possível ler todos os plugins, possivelmente devido a um problema de rede',
+ unavailable: 'Esta implantação é executada sem um perfil gerenciável, portanto, os plug-ins não podem ser instalados ou trocados aqui.',
+ retry: 'Tentar novamente',
+ refresh: 'Atualizar',
+ refreshError: 'Falha na atualização. Por favor, tente novamente.',
+ empty: 'Nenhum plug-in está instalado ainda.',
+ addPlugin: 'Adicionar plug-in',
+ chooseAddMethod: 'Escolha como adicionar um plugin',
+ installExisting: 'Instale um plugin de terceiros',
+ installExistingDescription: 'Suporta pacotes npm, repositórios Git e diretórios locais',
+ restartNotice: 'Reinicie DSH para aplicar a alteração. {reason}',
+ restartReasonNoHmr: 'Este perfil aplica alterações somente na inicialização.',
+ restartReasonPackageMoved: 'Um pacote instalado agora é resolvido para outro diretório ou a alteração substitui um pacote fornecido pela instalação DSH. Desinstale um pacote antes de instalá-lo de outro local.',
+ restartReasonHostModule: 'A mudança atinge os módulos DSH carregados.',
+ restartReasonUnmanagedPlugin: 'A mudança atinge um plugin rodando fora da lista de plugins.',
+ restartReasonModuleComparison: 'DSH não conseguiu comparar os plug-ins carregados com seus arquivos.',
+ overriddenNotice: '{name} foi salvo, mas uma configuração de prioridade mais alta o substitui, portanto não está em vigor',
+ bundlesTitle: 'Plug-ins instalados',
+ basicTitle: 'Configurações de plug-in integradas',
+ extensionsTitle: 'Plug-ins experimentais',
+ moreTitle: 'Plug-ins experimentais',
+ moreAction: 'Mais',
+ tabsLabel: 'Visualizações de plug-in',
+ installedTab: 'Instalado',
+ statusProblem: 'Problema',
+ statusBeta: 'Experimental',
+ reasonLabel: 'Razão',
+ metadataError: 'Erro de metadados do pacote: {error}',
+ versionTag: 'v{version}',
+ partsLabel: 'Componentes',
+ sourceTitle: 'Fonte',
+ sourceSpec: 'Fonte do código',
+ sourceBuiltIn: 'Construído em',
+ sourceVersion: 'Versão atual',
+ partsEmpty: 'Este pacote de plugins não contém componentes.',
+ partsCountTotal: '{count} total',
+ partsCountRunning: '{count} em execução',
+ partsCountOff: '{count} desativado',
+ partOff: 'Desligado',
+ partsCountFailed: '{count} falhou',
+ partsFilter: 'Filtrar componentes',
+ partsFilterEmpty: 'Nenhum componente corresponde.',
+ partToggle: 'Ativar componente {name}',
+ rowPhasePending: 'Esperando por dependências',
+ rowPhaseLoading: 'Carregando',
+ rowPhaseActive: 'Correndo',
+ rowPhaseFailed: 'Problema',
+ rowPhaseUnloading: 'Descarregando',
+ enableToggle: 'Ativar {name}',
+ applyingChange: 'Aplicando alterações em {name}',
+ openDetail: 'Ver {name}',
+ backToList: 'Voltar aos plug-ins',
+ crumbRoot: 'Plug-ins',
+ backToPackage: 'Voltar para {name}',
+ configureRow: 'Configurar {name}',
+ rowStateIdle: 'Não está em execução',
+ uninstall: 'Desinstalar',
+ uninstallLabel: 'Desinstalar {name}',
+ installTitle: 'Adicionar plug-in',
+ installDescription: 'Insira o nome do pacote do plug-in, o endereço do repositório GitHub ou o caminho do diretório local.',
+ installSpecLabel: 'Nome ou endereço do pacote',
+ installSpecPlaceholder: 'por exemplo dsh-plugin-baleia-animal de estimação',
+ installGuideToggle: 'Guia de instalação e exemplos',
+ installGuideHide: 'Ocultar o guia',
+ installGuideIdTitle: 'Digite o nome do pacote npm do plugin',
+ installGuideIdExample: 'dsh-plugin-baleia-animal de estimação',
+ installGuideIdHint: 'O nome do pacote do plug-in é o nome do pacote npm (como dsh-xxx ou @author/plugin): a parte após dsh plugin add ou pnpm add no comando de instalação README de um plug-in da comunidade.',
+ installGuideExampleLabel: 'Exemplo: ',
+ installGitTemplateHint: 'Substitua pelo endereço real do repositório Git.',
+ installPathTemplateHint: 'Substitua isso pelo caminho real para o diretório local do plugin.',
+ installGuideFill: 'Exemplo de uso',
+ installGuideFillAria: 'Use o exemplo {example}',
+ installGuideSafety: 'Instale apenas plug-ins em que você confia: eles são executados com suas permissões e podem danificar DeepSeek Harness ou vazar seus dados.',
+ installUpgradeNotice: 'Plug-ins oficiais sob demanda oferecem atualização quando sua versão é diferente de DSH. Para atualizar outros plug-ins, desinstale-os e reinstale-os.',
+ registryToggle: 'Registro',
+ registryLegend: 'O registro npm do qual o plugin é baixado',
+ registryDefault: 'Registro padrão',
+ registryOfficial: 'Registro oficial do npm',
+ registryNpmmirror: 'Espelho da China Continental',
+ registryCustom: 'Endereço personalizado',
+ registryCustomPlaceholder: 'https://npm.example.com/',
+ registryCustomHint: 'Insira um endereço de registro npm interno ou privado começando com http:// ou https://. Se for necessário um login, armazene as credenciais em ~/.npmrc nesta máquina.',
+ registryCustomInvalid: 'Insira um endereço começando com http:// ou https://',
+ registryListSeparator: ', ',
+ sentenceSeparator: ' ',
+ installRun: 'Instalar',
+ installChecking: 'Verificando…',
+ installConfirmedTitle: 'Instalar plug-in',
+ installConfirmedRefused: 'Este plugin não pode ser instalado',
+ installProblemInvalid: 'Este não é um nome ou endereço de pacote que pode ser instalado: {reason}',
+ installProblemInstalled: 'Este plugin já está instalado. Para atualizá-lo, desinstale-o e instale-o novamente',
+ installProblemShipped: 'Este plug-in é fornecido com DSH; atualizar DSH atualiza-o',
+ installBundledTitle: 'Este plug-in é fornecido com DSH',
+ installBundledDescription: 'Habilite-o sem baixar outra cópia',
+ installProblemNotFound: 'Nenhum plugin desse tipo foi encontrado',
+ installProblemNotPackage: 'O caminho não existe ou não é um pacote de plugin válido',
+ installProblemNotBundle: 'Este pacote não declara nenhum pacote, portanto não pode ser instalado como um plug-in: {reason}',
+ installProblemNetwork: 'O registro do plugin não pôde ser acessado; verifique a rede e tente novamente',
+ installProblemNetworkAll: 'Nenhum registro pôde ser acessado (tentativa: {registries}); verifique as configurações de rede ou proxy ou altere o registro',
+ installProblemUnknown: 'Não foi possível pesquisar o plug-in: {reason}',
+ installingTitle: 'Instalando o plug-in…',
+ installedTitle: 'Instalado',
+ statusNotInstalled: 'Não instalado',
+ statusMissingFiles: 'Pacote indisponível',
+ statusUpdateAvailable: 'Atualização disponível',
+ sourceNotInstalled: 'Não instalado',
+ sourceTargetSpec: 'Fonte para este DSH',
+ sourceTargetVersion: 'Versão para este DSH',
+ update: 'Atualizar',
+ updateLabel: 'Atualizar {name}',
+ versionsTitle: 'Versões',
+ versionsRecheck: 'Verifique novamente',
+ versionsChecking: 'Verificando atualizações',
+ versionsAvailable: 'Atualização para {version} disponível',
+ versionsPublished: 'Publicado {date}',
+ versionsLatest: 'Atualizado',
+ versionsCheckedAt: 'Última verificação em {time}',
+ versionsBlocked: 'Versões mais recentes precisam de uma atualização DSH',
+ versionsBlockedDetail: '{version} precisa de {peers}; este DSH é {runtime}',
+ versionsNoneInstallable: 'Nenhuma versão mais recente para instalar diretamente',
+ versionsNoneInstallableDetail: 'As versões mais recentes estão obsoletas, são pré-lançamentos ou não declaram nenhum pacote. Expanda para vê-los',
+ versionsFailedNetwork: 'Não foi possível acessar o registro',
+ versionsFailedOther: 'Não foi possível verificar atualizações',
+ versionsDetails: 'Detalhes',
+ versionsNewerCount: '{count} mais recente',
+ versionsCollapse: 'Recolher',
+ versionsInstalledTag: 'Instalado',
+ versionsPrereleaseTag: 'Pré-lançamento',
+ versionsDeprecatedTag: 'Obsoleto',
+ versionsNeedsDsh: 'Precisa de {peers}',
+ versionsNotBundle: 'Esta versão não declara nenhum pacote e não pode ser instalada',
+ versionsUpdateLabel: 'Atualizar para {version}',
+ versionsRegistry: 'Registro',
+ versionsCheckedAtLabel: 'Verificado em',
+ versionsFallbackTag: 'Alternativa',
+ versionsFallbackHint: '{registry} não pôde ser contatado; a lista vem de um registro substituto',
+ versionsPrimaryTag: 'Primeiro',
+ versionsShowPrerelease: 'Mostrar pré-lançamentos',
+ versionsOmitted: '{count} mais versões mais recentes que a instalada não estão listadas',
+ versionsProblemNetwork: 'Inacessível',
+ versionsProblemNotFound: 'Não encontrado',
+ versionsProblemUnknown: 'Falha',
+ updatesAvailable: 'Atualizações disponíveis: {count}',
+ updatesNone: 'Todos os plug-ins estão atualizados',
+ updatesFailed: 'Alguns plug-ins não puderam ser verificados',
+ updateTitle: 'Atualizar {name}',
+ updateSource: 'Instalar de',
+ updateDescription: 'A atualização substitui {from} por {to}; o plugin pode parar brevemente. Se a instalação falhar, {from} será mantido ou instalado novamente.',
+ updateRunning: 'Atualizando',
+ updateStepChecking: 'Verifique {version}',
+ updateStepRemoving: 'Pare e remova {version}',
+ updateStepInstalling: 'Instalar {version} de {registry}',
+ updateStepRestoring: 'Instale {version} novamente',
+ updateDone: 'Atualizado para {version}',
+ updateDoneDetail: 'A nova versão está em execução.',
+ updateRestart: '{version} instalado; entra em vigor após uma reinicialização',
+ updateRefused: 'Não é possível atualizar para {version}',
+ updateRemoveFailed: 'Não foi possível remover {version}',
+ updateRestored: 'Falha na atualização; restaurado {version}',
+ updateRestoredDetail: '{to} falhou ao instalar; {from} foi instalado novamente.',
+ updateKept: 'Não foi possível atualizar para {version}',
+ updateKeptDetail: '{from} não foi removido e permanece em uso. {reason}',
+ updateBroken: 'Falha na atualização; o plugin não está instalado',
+ updateBrokenDetail: '{to} falhou ao instalar e instalar {from} novamente também falhou. Instale {name} novamente em Adicionar plug-in.',
+ updateLost: 'O resultado da atualização é desconhecido',
+ updateLostDetail: 'A conexão com DSH foi interrompida: {reason}. Atualize a lista de plugins para ver a versão instalada.',
+ updateOutput: 'Mostrar resultado da instalação',
+ updateFinish: 'Concluído',
+ installFailedTitle: 'O plugin não pôde ser instalado',
+ installGithubFailedTitle: 'Não é possível acessar GitHub',
+ installGithubTimeoutTitle: 'GitHub conexão expirou',
+ installGithubFailedDescription: 'Tente outra fonte de instalação.',
+ installUseGithubMirror: 'Use o espelho da China continental',
+ installTryAnotherWay: 'Tente outra maneira',
+ installPackageLabel: 'Nome do pacote de plug-ins',
+ installEdit: 'Editar',
+ installEditAria: 'Voltar para edição',
+ installCancelAndEdit: 'Cancele a instalação e retorne à edição',
+ installApplyingCancellationError: 'O cancelamento não foi confirmado. A instalação está sendo aplicada; espere pelo seu resultado. {reason}',
+ installReconcile: 'Verifique o status da instalação',
+ installUnknownTitle: 'Resultado da instalação indisponível',
+ installUnknownDescription: 'O Host não possui instalação ativa com este ID de solicitação. Verifique a lista de plugins antes de tentar novamente.',
+ installResultUnconfirmed: 'O resultado da instalação não foi recebido. Verifique o status da instalação. {reason}',
+ installAwaitingAcceptance: 'Aguardando o Host aceitar a instalação. O cancelamento será tentado novamente automaticamente após a confirmação.',
+ installBackgroundUnknown: 'Resultado da instalação indisponível. Verifique a lista de plugins.',
+ installCancel: 'Cancelar instalação',
+ installCloseCancels: 'Cancelar instalação e fechar',
+ installViewTask: 'Ver instalação',
+ installUnconfirmedTitle: 'Status de instalação não confirmado',
+ installBackgroundDone: 'Instalação concluída. Veja detalhes da instalação.',
+ installBackgroundFailed: 'A instalação falhou. Veja detalhes da instalação.',
+ installBackgroundUnconfirmed: 'O status da instalação não foi confirmado. Veja a instalação para obter detalhes.',
+ installBackgroundApplying: 'A instalação está sendo aplicada e não pode ser cancelada. Veja o progresso da instalação.',
+ installStarting: 'Preparando a instalação…',
+ installCancelling: 'Parando a instalação…',
+ installApplying: 'Aplicando configuração, aguarde…',
+ installCancelledShort: 'Cancelado',
+ installCancelled: 'Instalação cancelada; o plugin não está habilitado e os arquivos baixados podem permanecer',
+ installCancelUnconfirmed: 'A instalação não foi confirmada interrompida. Tente novamente o cancelamento ou aguarde o resultado da instalação. {reason}',
+ installEnableNow: 'Ativar agora',
+ installDetailsShow: 'Mostrar detalhes da instalação',
+ installDetailsHide: 'Ocultar detalhes de instalação',
+ installVersion: 'Versão {version}',
+ installSubjectPath: 'Diretório local',
+ installSubjectGit: 'Repositório Git',
+ installSubjectTarball: 'Tarball',
+ installLocation: 'Instala em {dir}',
+ installRetry: 'Tentar novamente',
+ installChangeRegistry: 'Alterar registro',
+ installAttempt: '{previous} não pôde entregar o pacote; tentando novamente através de {registry} (registro {index} de {total})',
+ installAttemptBadge: 'Tentativa {index} · {registry}',
+ installFailureNetwork: 'A conexão de rede falhou',
+ installFailureNetworkAll: 'Nenhum registro pôde ser acessado (tentativa: {registries}). Verifique as configurações de rede ou proxy ou altere o registro e tente novamente.',
+ installFailureNetworkHost: '{host} não pôde ser alcançado. Um endereço GitHub ou um link .tgz não é obtido através do registro: esta máquina deve acessá-lo diretamente ou através de um proxy. Se o plugin também for publicado no npm, insira o nome do pacote.',
+ installFailureNotFound: 'Nenhum plugin desse tipo foi encontrado',
+ installFailureNoMatchingVersion: 'Nenhuma versão corresponde à solicitação',
+ installFailureDiskFull: 'O disco está cheio; a instalação parou',
+ installFailurePermission: 'Sem permissão de gravação; o plugin não pode ser instalado',
+ installFailureBuildBlocked: 'Os scripts de instalação de uma dependência precisam da sua permissão antes que a instalação possa continuar',
+ installFailureBuildBlockedManual: 'scripts de instalação bloqueados por pnpm; permita-os em permitBuilds em pnpm-workspace.yaml e tente novamente',
+ installFailureIntegrity: 'O pacote baixado falhou na verificação de integridade',
+ installFailureTimeout: 'A instalação expirou',
+ installFailurePnpmMissing: 'pnpm não foi encontrado, então nada pode ser instalado',
+ installFailureGeneric: 'Algo deu errado durante a instalação; os detalhes dizem o que',
+ terminalCommandLine: 'Linha de comando {n}',
+ terminalRunning: 'Correndo',
+ terminalFailed: 'Falha',
+ terminalDone: 'Concluído',
+ terminalCopy: 'Copiar',
+ terminalCopied: 'Copiado',
+ terminalNoOutput: 'Sem saída',
+ terminalCollapseAria: 'Recolher saída',
+ terminalCollapse: 'Recolher',
+ terminalExpandAria: 'Expanda as {n} linhas de saída restantes',
+ terminalExpand: '… mais {n} linhas',
+ terminalExitCode: 'código de saída {code}',
+ terminalSignal: 'sinal {signal}',
+ terminalNoExitCode: 'sem código de saída',
+ installDoneNothing: 'A instalação foi concluída sem nenhuma nova dependência.',
+ installDoneRestart: 'Instalado; reinicie DSH para carregá-lo. {reason}',
+ installDoneApproved: 'Instalar scripts permitidos para {names}',
+ installDoneOtherVersion: 'De acordo com a política de segurança da cadeia de fornecimento do pnpm (minimumReleaseAge), as versões publicadas mais recentemente do que a idade exigida não são instaladas automaticamente, portanto, esta instalação usou {installed} em vez da versão mais recente {version}. Uma versão recém-publicada não completou esse período de espera e pode conter códigos maliciosos ou defeitos não detectados. Se precisar de {version}, desinstale a versão atual e instale {exact} para solicitar essa versão.',
+ installApprovalTitle: 'Os scripts de instalação precisam de permissão',
+ installApprovalDescription: 'Esses pacotes possuem scripts de instalação que o pnpm não executou.',
+ installApprovalConsequence: 'Uma vez permitido, os scripts são executados aqui com suas permissões, e a permissão é salva neste perfil.',
+ installApprovalCaution: 'Permita apenas pacotes em que você confia.',
+ installApproveAndRetry: 'Permita esses scripts e tente novamente',
+ installClose: 'Concluído',
+ close: 'Fechar',
+ cancel: 'Cancelar',
+ confirmUninstallTitle: 'Desinstalar "{name}"?',
+ confirmUninstallDescription: 'O que ele fornece desaparece quando é desinstalado.',
+ confirmUninstall: 'Desinstalar',
+ failedEnable: 'Não foi possível ativar: {reason}',
+ failedDisable: 'Não foi possível desativar: {reason}',
+ failedUninstall: 'Não foi possível desinstalar: {reason}',
+ failedRowEnable: 'Não foi possível ativar o componente: {reason}',
+ failedRowDisable: 'Não foi possível desativar o componente: {reason}',
+ reasonManagementRequired: 'O gerenciamento de plug-ins precisa disso; ele não pode ser desligado ou desinstalado.',
+ reasonUnaddressable: 'O patch de perfil não pode resolver este problema exclusivamente.',
+ reasonUnknownPlugin: 'Não existe tal plugin.',
+ reasonInvalidSpec: 'Insira um nome ou endereço de pacote válido.',
+ reasonAlreadyInstalled: 'Este pacote já está instalado; desinstale-o antes de instalar outra versão.',
+ reasonAmbiguousInstall: 'Qual pacote foi instalado não pode ser informado a partir da mudança de dependência.',
+ reasonNotBundle: 'Este pacote não declara nenhum pacote, portanto não pode ser gerenciado como um plugin.',
+ reasonNotRemovable: 'Este pacote não pertence ao perfil ou o gerenciamento do plugin precisa dele.',
+ reasonStopProfile: 'Este perfil é executado sem HMR; pare e desinstale o pacote com o plugin dsh.',
+ reasonBundleInUse: 'Outras configurações ainda usam os componentes deste pacote configurável; desligue-os primeiro.',
+ reasonStaleApproval: 'As aprovações de script pendentes foram alteradas; instale novamente para atualizá-los.',
+ reasonIncompatibleVersion: '{plugin} é incompatível com DSH {runtime} (requer {requirements}); executá-lo pode causar travamentos ou perda de dados.',
+ reasonIncompatibleVersionUnnamed: 'Este plug-in é incompatível com a versão DSH em execução; executá-lo pode causar travamentos ou perda de dados.',
+ reasonIncompatibleInstall: 'Instale uma versão de plug-in compatível com este DSH.',
+ reasonIncompatibleInstalled: 'Desinstale-o e instale uma versão compatível com este DSH.',
+ reasonOperationError: 'O Host relatou um erro.',
+ detailTitle: 'Detalhes',
+ detailLoading: 'Lendo detalhes',
+ detailTooLarge: 'Os detalhes excedem o limite de tamanho e não foram carregados.',
+ detailNetwork: 'Não foi possível acessar a fonte dos detalhes.',
+ detailInvalid: 'O plugin declara um arquivo de detalhes inválido.',
+ detailFailed: 'Não foi possível ler os detalhes.',
+ detailFootnotes: 'Notas de rodapé',
+} satisfies Record
diff --git a/packages/client/ui-plugin-market/README.i18n.yaml b/packages/client/ui-plugin-market/README.i18n.yaml
index 4ca8e13747..1fa39181d9 100644
--- a/packages/client/ui-plugin-market/README.i18n.yaml
+++ b/packages/client/ui-plugin-market/README.i18n.yaml
@@ -3,23 +3,23 @@
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/ui-plugin-market/README.md
/:
- en: af3fe4b70bddbc1b
- zh: ac1c4326165266d9
+ en: b77d6d729562a88f
+ zh: b1634cc1d027424c
/deepseek-ai-dsh-client-ui-plugin-market:
en: 62972eb0dff8c145
zh: 682cc03b492cd730
/deepseek-ai-dsh-client-ui-plugin-market/summary:
- en: 55a3d1b12cd7be61
- zh: a961cf0212b3fda8
+ en: 71328d505b984fe5
+ zh: 9281e7dbaa01ef9a
/deepseek-ai-dsh-client-ui-plugin-market/table-of-contents:
en: 76368b6b1da79c80
zh: 52384aee991ac542
/deepseek-ai-dsh-client-ui-plugin-market/use-this-package:
- en: f873bf36195df821
- zh: e13c78ba3f3e29f1
+ en: 868b100459e30aad
+ zh: 8a3a01199dc3c14c
/deepseek-ai-dsh-client-ui-plugin-market/understand-the-implementation:
- en: 4107a6967934f6b6
- zh: 46acec8bedb86329
+ en: e1a480caaff8efea
+ zh: 0b725fa5bc929f22
/deepseek-ai-dsh-client-ui-plugin-market/model-experience:
en: 08d25ab9cbdd7567
zh: 26e6e75c123951ee
diff --git a/packages/client/ui-plugin-market/README.md b/packages/client/ui-plugin-market/README.md
index 7a2bf9ffa8..283992a8c6 100644
--- a/packages/client/ui-plugin-market/README.md
+++ b/packages/client/ui-plugin-market/README.md
@@ -1,5 +1,5 @@
---
-description: "Discover tab of the Web Plugins page: search configured plugin market indexes, read an entry, and install it."
+description: "Discover tab of the Web Plugins page: search configured plugin indexes, read an entry, and install it."
kind: "package-reference"
---
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
## Summary
-Discover is the second tab of the **Plugins** page, after **Installed**. It lists the bundles that plugin market indexes offer; users manage index sources, switch between markets, search, filter by category and label, sort, open an entry's page, and install an entry after confirming it. Entry pages show each entry's long description as Markdown with images. The Host [`pluginMarket`](../../boot/plugin-market/README.md) service fetches and validates indexes; this package only presents them.
+Discover is the second tab of the **Plugins** page, after **Installed**. It lists the bundles that plugin indexes offer; users manage index sources, switch between indexes, search, filter by category and label, sort, open an entry's page, and install an entry after confirming it. Entry pages show each entry's long description as Markdown with images. The Host [`pluginMarket`](../../boot/plugin-market/README.md) service fetches and validates indexes; this package only presents them.
## Table of Contents
@@ -24,11 +24,11 @@ Discover is the second tab of the **Plugins** page, after **Installed**. It list
## Use this package
-Open **Plugins** and select **Discover**. With no index source, the tab shows an empty state whose **Add index source** button opens the **Index sources** dialog. The dialog lists each source by market title, or by host before the first read, with its URL, the error of its last failed read, and a remove button, and keeps a notice that indexes come from third parties. A refused URL keeps the draft and says why: an unsupported scheme, credentials, or fragment; a URL already listed; or a deployment that cannot save sources. A newly added source becomes the selected market.
+Open **Plugins** and select **Discover**. With no index source, the tab shows an empty state whose **Add index source** button opens the **Index sources** dialog. The dialog lists each source by index title, or by host before the first read, with its URL, the error of its last failed read, and a remove button, and keeps a notice that indexes come from third parties. A refused URL keeps the draft and says why: an unsupported scheme, credentials, or fragment; a URL already listed; or a deployment that cannot save sources. A newly added source becomes the selected index.
-The toolbar holds the market menu, which switches markets and ends with **Manage index sources…**; the search field; a **Labels** menu with multi-select checkmarks when the market declares labels; the **Sort** menu; and a refresh button. Category pills, with **All** first, follow when the market declares categories.
+The toolbar holds the index menu, which switches indexes and ends with **Manage index sources…**; the search field; a **Labels** menu with multi-select checkmarks when the index declares labels; the **Sort** menu; and a refresh button. Category pills, with **All** first, follow when the index declares categories.
-Each card shows the icon, localized title, label tags, a one-line description, and the default version, author, and publish date, with one action: **Install** for an installable entry; a disabled **Install** whose tooltip says no version is compatible with the running DSH when the plugin has no default version and its highest version is neither compatible nor exempted; or **Installed**, **Update available**, or **Provided by DSH**, which switches to the Installed tab and opens that bundle's page when that tab lists it. Selecting a card opens the entry's page with its full description, compatibility notice, and, under the source heading, the default version's facts: author; homepage and source code repository, as links that open in a new tab, with the package directory beside the repository; the npm package and registry, or the Git repository, full commit, and directory; publish date; category; labels; and market. The page shows the long description first, without a heading of its own, then **Versions**, then the source facts. The **Versions** list is in descending SemVer order; each row tags the recommended, prerelease, installed, incompatible, and exempted versions and offers **Install** for any other version installation accepts, except for a bundle DSH provides. The page's back link returns to the list with its search, filters, and sort intact.
+Each card shows the icon, localized title, label tags, a one-line description, and the default version, author, and publish date, with one action: **Install** for an installable entry; a disabled **Install** whose tooltip says no version is compatible with the running DSH when the plugin has no default version and its highest version is neither compatible nor exempted; or **Installed**, **Update available**, or **Provided by DSH**, which switches to the Installed tab and opens that bundle's page when that tab lists it. Selecting a card opens the entry's page with its full description, compatibility notice, and, under the source heading, the default version's facts: author; homepage and source code repository, as links that open in a new tab, with the package directory beside the repository; the npm package and registry, or the Git repository, full commit, and directory; publish date; category; labels; and index title. The page shows the long description first, without a heading of its own, then **Versions**, then the source facts. The **Versions** list is in descending SemVer order; each row tags the recommended, prerelease, installed, incompatible, and exempted versions and offers **Install** for any other version installation accepts, except for a bundle DSH provides. The page's back link returns to the list with its search, filters, and sort intact.
**Install** chooses the default version and opens a confirmation dialog with the entry's icon, title, that version, the author, its source facts, an exemption notice when one applies, and a trust notice. Confirming opens the Plugins install dialog at its check screen with the entry's exact spec and registry; there is no spec form, and a refusal offers **Retry**. The registry an entry names applies to that install only.
@@ -36,7 +36,7 @@ The Host [`pluginMarket.search`](../../boot/plugin-market/README.md#use-this-pac
The description section shows the default version's long description in the active UI language: the version's locale `meta.detail` file, otherwise its package README, otherwise its repository README, in the [order `pluginMarket` documents](../../boot/plugin-market/README.md#plugin-detail-documents). While the document loads, the section shows three skeleton lines; an entry without a document shows no section; a failed read names its reason, such as a size limit, an unreachable source, or a refused repository, with **Retry**. Raw HTML is omitted, and a document taller than the section's height limit scrolls inside it. Relative image paths load from the Host beside the document, absolute HTTP(S) images load directly without a referrer, and relative links stay text. The section is the shared [`DetailDocument`](../ui-primitives/README.md#component-catalog) primitive.
-Reading a market shows card skeletons. A failed read shows a short sentence with **Retry**, the Host's reason appears under the source in **Index sources**, and keeps the market's last listing; a failed read of the source list shows **Retry** in place of the tab. Discover re-reads sources on `plugin-market/changed` and connection reset, and recomputes installation status on `plugin-manager/changed`.
+Reading an index shows card skeletons. A failed read shows a short sentence with **Retry**, the Host's reason appears under the source in **Index sources**, and keeps the index's last listing; a failed read of the source list shows **Retry** in place of the tab. Discover re-reads sources on `plugin-market/changed` and connection reset, and recomputes installation status on `plugin-manager/changed`.
-----
@@ -45,7 +45,7 @@ Reading a market shows card skeletons. A failed read shows a short sentence with
Detail reads are kept in a second snapshot store keyed by index URL, entry, version, and language; a section starts its read when it renders, an npm version's read first asks the Host's registry comparison and passes `npmFileSources: ['npmmirror']` when npmmirror answered fastest, only the latest read of a key settles it, **Retry** asks the Host to read again instead of returning its cached result, and a connection reset forgets every read, because a restarted Host no longer knows the document ids its images use, so each shown section reads its document again. Image URLs resolve `api/plugin-market/detail-file` against `document.baseURI`, so they keep a deployment prefix and use the Desktop `dsh-app://app/` origin, which [`MarkdownText`](../ui-primitives/README.md) accepts for that route.
-The plugin registers the `discover` entry, labelled **Discover**, into `plugins.tab`, the tab slot that [`ui-plugin-manager`](../ui-plugin-manager/README.md) declares; the owner props provide `install({ spec, registry?, title, version })` and `openPackage(name)`; Discover passes the entry's localized title and the chosen version. A React-free controller owns source reads, source edits, and the selected index's query and loaded pages in a snapshot store; selecting another market starts from the initial query. Later pages send the first page's `fetchedAt`, and a page from a replaced document reloads the list once; a second replacement fails the read. The listing keeps the query its results answer, so the empty-state wording follows the shown results while a new query loads or after it fails, and **Retry** repeats the read that failed: the first pages of a query or the next page. A change to the profile's bundles reloads every loaded page to refresh installation status, and a filter or sort key the index no longer declares leaves the query. The unsent search text, the open entry, and the confirmation are component state. Pure helpers in `presentation.ts` derive the query order from the text, drop undeclared ids, order declarations with `Intl.Collator` in the active UI language, and list source facts. Icons load lazily with `referrerpolicy="no-referrer"` and fall back to the default artwork.
+The plugin registers the `discover` entry, labelled **Discover**, into `plugins.tab`, the tab slot that [`ui-plugin-manager`](../ui-plugin-manager/README.md) declares; the owner props provide `install({ spec, registry?, title, version })` and `openPackage(name)`; Discover passes the entry's localized title and the chosen version. A React-free controller owns source reads, source edits, and the selected index's query and loaded pages in a snapshot store; selecting another index starts from the initial query. Later pages send the first page's `fetchedAt`, and a page from a replaced document reloads the list once; a second replacement fails the read. The listing keeps the query its results answer, so the empty-state wording follows the shown results while a new query loads or after it fails, and **Retry** repeats the read that failed: the first pages of a query or the next page. A change to the profile's bundles reloads every loaded page to refresh installation status, and a filter or sort key the index no longer declares leaves the query. The unsent search text, the open entry, and the confirmation are component state. Pure helpers in `presentation.ts` derive the query order from the text, drop undeclared ids, order declarations with `Intl.Collator` in the active UI language, and list source facts. Icons load lazily with `referrerpolicy="no-referrer"` and fall back to the default artwork.
-----
diff --git a/packages/client/ui-plugin-market/README.zh.md b/packages/client/ui-plugin-market/README.zh.md
index 40b98ec314..276c1a5556 100644
--- a/packages/client/ui-plugin-market/README.zh.md
+++ b/packages/client/ui-plugin-market/README.zh.md
@@ -1,5 +1,5 @@
---
-description: "Web 插件页的「发现」标签页:搜索已配置的插件市场索引,查看条目并安装。"
+description: "Web 插件页的「发现」标签页:搜索已配置的插件索引,查看条目并安装。"
kind: "package-reference"
---
@@ -9,7 +9,7 @@ kind: "package-reference"
## 概述
-「发现」是**插件**页的第二个标签页,位于**已安装**之后。它列出插件市场索引提供的组合包(bundle);用户可以管理索引源、在插件市场之间切换、搜索、按分类和标签筛选、排序、打开条目页面,并在确认后安装条目。条目页面以带图片的 Markdown 显示每个条目的详细介绍。Host 的 [`pluginMarket`](../../boot/plugin-market/README.zh.md) 服务负责获取和校验索引;本包只负责展示。
+「发现」是**插件**页的第二个标签页,位于**已安装**之后。它列出插件索引提供的组合包(bundle);用户可以管理索引源、在插件索引之间切换、搜索、按分类和标签筛选、排序、打开条目页面,并在确认后安装条目。条目页面以带图片的 Markdown 显示每个条目的详细介绍。Host 的 [`pluginMarket`](../../boot/plugin-market/README.zh.md) 服务负责获取和校验索引;本包只负责展示。
## 目录
@@ -24,11 +24,11 @@ kind: "package-reference"
## 使用本包
-打开**插件**并选择**发现**。没有索引源时,该标签页显示空状态,其中的**添加索引源**按钮打开**索引源**对话框。对话框按插件市场标题列出每个索引源(首次读取前按主机名),并显示其 URL、最近一次读取失败的错误和删除按钮,同时保留一条提示,说明索引由第三方提供。URL 被拒绝时保留草稿并说明原因:不支持的协议、包含凭据或片段、URL 已在列表中,或当前部署无法保存索引源。新添加的索引源成为所选插件市场。
+打开**插件**并选择**发现**。没有索引源时,该标签页显示空状态,其中的**添加索引源**按钮打开**索引源**对话框。对话框按插件索引标题列出每个索引源(首次读取前按主机名),并显示其 URL、最近一次读取失败的错误和删除按钮,同时保留一条提示,说明索引由第三方提供。URL 被拒绝时保留草稿并说明原因:不支持的协议、包含凭据或片段、URL 已在列表中,或当前部署无法保存索引源。新添加的索引源成为所选插件索引。
-工具栏包含:插件市场菜单,用于切换插件市场,最后一项为**管理索引源…**;搜索框;插件市场声明了标签时显示的**标签**菜单,以勾选标记支持多选;**排序**菜单;以及刷新按钮。插件市场声明了分类时,其后显示分类按钮,**全部**排在第一位。
+工具栏包含:插件索引菜单,用于切换插件索引,最后一项为**管理索引源…**;搜索框;插件索引声明了标签时显示的**标签**菜单,以勾选标记支持多选;**排序**菜单;以及刷新按钮。插件索引声明了分类时,其后显示分类按钮,**全部**排在第一位。
-每张卡片显示图标、本地化标题、标签、一行描述,以及默认版本、作者和发布日期,并只有一个操作:可安装的条目显示**安装**;没有默认版本、且最高版本既不兼容也未豁免的条目显示禁用的**安装**,其提示框说明没有与当前运行的 DSH 兼容的版本;其余条目显示**已安装**、**有新版本**或**随 DSH 提供**,点击后切换到「已安装」标签页,并在该标签页列出该组合包时打开其页面。选择卡片会打开条目页面,显示其完整描述、兼容性提示,并在来源标题下显示默认版本的信息:作者;主页和源代码仓库(在新标签页中打开的链接,仓库旁显示包所在目录);npm 包和注册表,或 Git 仓库、完整提交和目录;发布日期;分类;标签;以及插件市场。页面先显示详细介绍,不带单独的标题,然后是**版本**,然后是来源信息。**版本**列表按 SemVer 降序排列;每行标出推荐、预发布、已安装、不兼容和已豁免的版本,并为安装检查接受的其他版本提供**安装**,DSH 随附的组合包除外。页面的返回链接回到列表,搜索、筛选和排序保持不变。
+每张卡片显示图标、本地化标题、标签、一行描述,以及默认版本、作者和发布日期,并只有一个操作:可安装的条目显示**安装**;没有默认版本、且最高版本既不兼容也未豁免的条目显示禁用的**安装**,其提示框说明没有与当前运行的 DSH 兼容的版本;其余条目显示**已安装**、**有新版本**或**随 DSH 提供**,点击后切换到「已安装」标签页,并在该标签页列出该组合包时打开其页面。选择卡片会打开条目页面,显示其完整描述、兼容性提示,并在来源标题下显示默认版本的信息:作者;主页和源代码仓库(在新标签页中打开的链接,仓库旁显示包所在目录);npm 包和注册表,或 Git 仓库、完整提交和目录;发布日期;分类;标签;以及插件索引标题。页面先显示详细介绍,不带单独的标题,然后是**版本**,然后是来源信息。**版本**列表按 SemVer 降序排列;每行标出推荐、预发布、已安装、不兼容和已豁免的版本,并为安装检查接受的其他版本提供**安装**,DSH 随附的组合包除外。页面的返回链接回到列表,搜索、筛选和排序保持不变。
**安装**选择默认版本并打开确认对话框,显示条目的图标、标题、该版本、作者、来源信息、适用时的豁免提示,以及信任提示。确认后,插件页的安装对话框以该条目的精确 spec 和注册表在检查界面打开;不显示 spec 表单,被拒绝时提供**重试**。条目指定的注册表只用于这一次安装。
@@ -36,7 +36,7 @@ Host 的 [`pluginMarket.search`](../../boot/plugin-market/README.zh.md#use-this-
详细介绍部分以当前界面语言显示默认版本的详细介绍:按 [`pluginMarket` 说明的顺序](../../boot/plugin-market/README.zh.md#plugin-detail-documents),依次为该版本 locale 中 `meta.detail` 指定的文件、其包 README、其仓库 README。文档加载期间,该部分显示三行骨架屏;没有文档的条目不显示该部分;读取失败时说明原因,例如超过大小限制、来源无法访问或仓库拒绝访问,并提供**重试**。原始 HTML 会被省略;文档高度超过该部分的高度上限时,在该部分内滚动。相对路径的图片从 Host 上该文档旁的位置加载,绝对 HTTP(S) 地址的图片不带 referrer 直接加载,相对链接保持为文本。该部分使用共享的 [`DetailDocument`](../ui-primitives/README.zh.md#component-catalog) 原语。
-读取插件市场时显示卡片骨架屏。读取失败时显示一句简短说明和**重试**,Host 给出的原因显示在**索引源**中对应索引源的下方,并保留该插件市场上一次的列表;读取索引源列表失败时,在该标签页的位置显示**重试**。「发现」在收到 `plugin-market/changed` 和连接重置时重新读取索引源,在收到 `plugin-manager/changed` 时重新计算安装状态。
+读取插件索引时显示卡片骨架屏。读取失败时显示一句简短说明和**重试**,Host 给出的原因显示在**索引源**中对应索引源的下方,并保留该插件索引上一次的列表;读取索引源列表失败时,在该标签页的位置显示**重试**。「发现」在收到 `plugin-market/changed` 和连接重置时重新读取索引源,在收到 `plugin-manager/changed` 时重新计算安装状态。
-----
@@ -45,7 +45,7 @@ Host 的 [`pluginMarket.search`](../../boot/plugin-market/README.zh.md#use-this-
详细介绍的读取结果保存在第二个快照存储中,以索引 URL、条目、版本和语言为键;该部分在渲染时开始读取;npm 版本的读取先询问 Host 的注册表比较,npmmirror 响应最快时传入 `npmFileSources: ['npmmirror']`;只有某个键最新一次的读取才会让该部分得到结果;**重试**要求 Host 重新读取,而不是返回其缓存的结果;连接重置时丢弃所有读取结果,因为重启后的 Host 不再知道其图片使用的文档 id,所以每个已显示的部分会重新读取其文档。图片 URL 基于 `document.baseURI` 解析 `api/plugin-market/detail-file`,因此保留部署前缀,并在 Desktop 中使用 `dsh-app://app/` origin;[`MarkdownText`](../ui-primitives/README.zh.md) 对该路由接受此 origin。
-本插件把标签为**发现**的 `discover` 条目注册到 `plugins.tab`,即 [`ui-plugin-manager`](../ui-plugin-manager/README.zh.md) 声明的标签页 slot;owner props 提供 `install({ spec, registry?, title, version })` 和 `openPackage(name)`;发现页传入条目的本地化标题和所选版本。一个不依赖 React 的控制器在快照存储中拥有索引源读取、索引源编辑,以及所选索引的查询和已加载的页;选择另一个插件市场时从初始查询开始。之后的页发送第一页的 `fetchedAt`,来自已替换文档的页会让列表重新加载一次;再次被替换时读取失败。列表保留其结果所回答的查询,因此新查询加载期间或失败后,空状态文案仍与显示的结果一致;**重试**重新执行失败的读取:某个查询的前几页,或下一页。profile 的组合包变化时,重新加载所有已加载的页以刷新安装状态;索引不再声明的过滤条件或排序键会从查询中移除。尚未发送的搜索文本、打开的条目和确认对话框是组件状态。`presentation.ts` 中的纯函数根据文本得出查询顺序、移除未声明的 id、使用当前界面语言的 `Intl.Collator` 对声明排序,以及列出来源信息。图标以 `referrerpolicy="no-referrer"` 延迟加载,失败时回退到默认插画。
+本插件把标签为**发现**的 `discover` 条目注册到 `plugins.tab`,即 [`ui-plugin-manager`](../ui-plugin-manager/README.zh.md) 声明的标签页 slot;owner props 提供 `install({ spec, registry?, title, version })` 和 `openPackage(name)`;发现页传入条目的本地化标题和所选版本。一个不依赖 React 的控制器在快照存储中拥有索引源读取、索引源编辑,以及所选索引的查询和已加载的页;选择另一个插件索引时从初始查询开始。之后的页发送第一页的 `fetchedAt`,来自已替换文档的页会让列表重新加载一次;再次被替换时读取失败。列表保留其结果所回答的查询,因此新查询加载期间或失败后,空状态文案仍与显示的结果一致;**重试**重新执行失败的读取:某个查询的前几页,或下一页。profile 的组合包变化时,重新加载所有已加载的页以刷新安装状态;索引不再声明的过滤条件或排序键会从查询中移除。尚未发送的搜索文本、打开的条目和确认对话框是组件状态。`presentation.ts` 中的纯函数根据文本得出查询顺序、移除未声明的 id、使用当前界面语言的 `Intl.Collator` 对声明排序,以及列出来源信息。图标以 `referrerpolicy="no-referrer"` 延迟加载,失败时回退到默认插画。
-----
diff --git a/packages/client/ui-plugin-market/locale/de.json b/packages/client/ui-plugin-market/locale/de.json
new file mode 100644
index 0000000000..7575ee4faa
--- /dev/null
+++ b/packages/client/ui-plugin-market/locale/de.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Plugin-Erkennung",
+ "description": "Konfigurierte Plugin-Indizes durchsuchen und deren Pakete installieren."
+ }
+}
diff --git a/packages/client/ui-plugin-market/locale/en.json b/packages/client/ui-plugin-market/locale/en.json
index 2c72ab4cec..222f7277ed 100644
--- a/packages/client/ui-plugin-market/locale/en.json
+++ b/packages/client/ui-plugin-market/locale/en.json
@@ -1,6 +1,6 @@
{
"meta": {
"title": "Plugin discovery",
- "description": "Browse configured plugin market indexes and install their packages."
+ "description": "Browse configured plugin indexes and install their packages."
}
}
diff --git a/packages/client/ui-plugin-market/locale/es.json b/packages/client/ui-plugin-market/locale/es.json
new file mode 100644
index 0000000000..a9dd1b071d
--- /dev/null
+++ b/packages/client/ui-plugin-market/locale/es.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Descubrimiento de complementos",
+ "description": "Explore los índices de complementos configurados e instale sus paquetes."
+ }
+}
diff --git a/packages/client/ui-plugin-market/locale/fr.json b/packages/client/ui-plugin-market/locale/fr.json
new file mode 100644
index 0000000000..e21adf5411
--- /dev/null
+++ b/packages/client/ui-plugin-market/locale/fr.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Découverte de plugins",
+ "description": "Parcourez les index de plugins configurés et installez leurs paquets."
+ }
+}
diff --git a/packages/client/ui-plugin-market/locale/ja.json b/packages/client/ui-plugin-market/locale/ja.json
index 7411851543..32e9d3c3d7 100644
--- a/packages/client/ui-plugin-market/locale/ja.json
+++ b/packages/client/ui-plugin-market/locale/ja.json
@@ -1,6 +1,6 @@
{
"meta": {
"title": "プラグイン検索 UI",
- "description": "設定済みのプラグインマーケットのインデックスを参照し、プラグインパッケージをインストールします。"
+ "description": "設定済みのプラグインインデックスを参照し、プラグインパッケージをインストールします。"
}
}
diff --git a/packages/client/ui-plugin-market/locale/pt.json b/packages/client/ui-plugin-market/locale/pt.json
new file mode 100644
index 0000000000..6192b57fe6
--- /dev/null
+++ b/packages/client/ui-plugin-market/locale/pt.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Descoberta de plug-ins",
+ "description": "Navegue pelos índices de plug-ins configurados e instale seus pacotes."
+ }
+}
diff --git a/packages/client/ui-plugin-market/locale/ru.json b/packages/client/ui-plugin-market/locale/ru.json
new file mode 100644
index 0000000000..c990647d59
--- /dev/null
+++ b/packages/client/ui-plugin-market/locale/ru.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Открытие плагина",
+ "description": "Просматривайте настроенные индексы плагинов и устанавливайте их пакеты."
+ }
+}
diff --git a/packages/client/ui-plugin-market/locale/zh.json b/packages/client/ui-plugin-market/locale/zh.json
index 4e9e89725f..f01c89d5df 100644
--- a/packages/client/ui-plugin-market/locale/zh.json
+++ b/packages/client/ui-plugin-market/locale/zh.json
@@ -1,6 +1,6 @@
{
"meta": {
"title": "插件发现界面",
- "description": "浏览已配置的插件市场索引,并安装其中的插件包。"
+ "description": "浏览已配置的插件索引,并安装其中的插件包。"
}
}
diff --git a/packages/client/ui-plugin-market/src/client/index.ts b/packages/client/ui-plugin-market/src/client/index.ts
index c1aab2c746..36aed35a01 100644
--- a/packages/client/ui-plugin-market/src/client/index.ts
+++ b/packages/client/ui-plugin-market/src/client/index.ts
@@ -12,7 +12,7 @@ import type {} from '@deepseek-ai/dsh-plugin-manager/types'
import type {} from '@deepseek-ai/dsh-plugin-market/types'
import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'
import { DiscoverSection } from './DiscoverSection.tsx'
-import { en, ja, zh, type PluginMarketLocaleKey } from './locales.ts'
+import { en, ja, es, fr, de, ru, pt, zh, type PluginMarketLocaleKey } from './locales.ts'
import { DiscoverController } from './market-store.ts'
export type { DiscoverFace, DiscoverState } from './market-store.ts'
@@ -36,7 +36,7 @@ export const inject = ['slots', 'locale', 'remote', 'remote.pluginMarket', 'remo
* @param ctx - the browser plugin context.
*/
export function apply(ctx: ClientContext): void {
- ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-plugin-market: dictionaries')
+ ctx.effect(() => ctx.locale.register(NS, { zh, en, ja, es, fr, de, ru, pt }), 'ui-plugin-market: dictionaries')
const controller = new DiscoverController(ctx, () => document.baseURI)
ctx.effect(() => () => { controller.dispose() }, 'ui-plugin-market: controller')
ctx.effect(() => {
diff --git a/packages/client/ui-plugin-market/src/client/locales.ts b/packages/client/ui-plugin-market/src/client/locales.ts
index 20aada6cae..52753bd63a 100644
--- a/packages/client/ui-plugin-market/src/client/locales.ts
+++ b/packages/client/ui-plugin-market/src/client/locales.ts
@@ -8,14 +8,14 @@ export const zh = {
retry: '重试',
close: '关闭',
cancel: '取消',
- emptyTitle: '还没有插件市场',
- emptyDescription: '添加一个插件市场索引,即可在这里浏览和安装其中的插件',
+ emptyTitle: '还没有插件索引',
+ emptyDescription: '添加一个插件索引,即可在这里浏览和安装其中的插件',
addFirstSource: '添加索引源',
sourcesFailed: '无法读取索引源',
manageSources: '管理索引源…',
- marketMenu: '切换插件市场,当前为 {market}',
+ marketMenu: '切换插件索引,当前为 {market}',
sourcesTitle: '索引源',
- sourcesDescription: '插件市场以一个索引文件的 URL 提供,「发现」从这里列出的索引浏览插件',
+ sourcesDescription: '插件索引以一个索引文件的 URL 提供,「发现」从这里列出的索引浏览插件',
sourcesLabel: '已添加的索引源',
sourceUrl: '索引 URL',
sourcePlaceholder: 'https://example.com/index.json',
@@ -70,7 +70,7 @@ export const zh = {
factSourceCode: '源代码',
factCategory: '分类',
factLabels: '标签',
- factMarket: '插件市场',
+ factMarket: '插件索引',
versionsTitle: '版本',
versionsLabel: '{name} 的版本',
versionDefault: '推荐',
@@ -102,14 +102,14 @@ export const en = {
retry: 'Retry',
close: 'Close',
cancel: 'Cancel',
- emptyTitle: 'No plugin markets yet',
- emptyDescription: 'Add a plugin market index to browse and install its plugins here.',
+ emptyTitle: 'No plugin indexes yet',
+ emptyDescription: 'Add a plugin index to browse and install its plugins here.',
addFirstSource: 'Add index source',
sourcesFailed: 'Could not read the index sources.',
manageSources: 'Manage index sources…',
- marketMenu: 'Switch plugin market, currently {market}',
+ marketMenu: 'Switch plugin index, currently {market}',
sourcesTitle: 'Index sources',
- sourcesDescription: 'A plugin market is published as the URL of one index file. Discover browses the indexes listed here.',
+ sourcesDescription: 'A plugin index is published as the URL of one index file. Discover browses the indexes listed here.',
sourcesLabel: 'Added index sources',
sourceUrl: 'Index URL',
sourcePlaceholder: 'https://example.com/index.json',
@@ -164,7 +164,7 @@ export const en = {
factSourceCode: 'Source code',
factCategory: 'Category',
factLabels: 'Labels',
- factMarket: 'Plugin market',
+ factMarket: 'Plugin index',
versionsTitle: 'Versions',
versionsLabel: 'Versions of {name}',
versionDefault: 'Recommended',
@@ -193,14 +193,14 @@ export const ja = {
'retry': '再試行',
'close': '閉じる',
'cancel': 'キャンセル',
- 'emptyTitle': 'プラグインマーケットはまだありません',
- 'emptyDescription': 'プラグインマーケットのインデックスを追加すると、ここでプラグインを閲覧してインストールできます。',
+ 'emptyTitle': 'プラグインインデックスはまだありません',
+ 'emptyDescription': 'プラグインインデックスを追加すると、ここでそのプラグインを閲覧してインストールできます。',
'addFirstSource': 'インデックスソースを追加',
'sourcesFailed': 'インデックスソースを読み取れませんでした。',
'manageSources': 'インデックスソースを管理…',
- 'marketMenu': 'プラグインマーケットを切り替え、現在は{market}',
+ 'marketMenu': 'プラグインインデックスを切り替え、現在は{market}',
'sourcesTitle': 'インデックスソース',
- 'sourcesDescription': 'プラグインマーケットは1つのインデックスファイルのURLとして公開されます。「見つける」ではここに一覧されたインデックスを閲覧します。',
+ 'sourcesDescription': 'プラグインインデックスは1つのインデックスファイルのURLとして公開されます。「見つける」ではここに一覧されたインデックスを閲覧します。',
'sourcesLabel': '追加済みのインデックスソース',
'sourceUrl': 'インデックスURL',
'sourcePlaceholder': 'https://example.com/index.json',
@@ -255,7 +255,7 @@ export const ja = {
'factSourceCode': 'ソースコード',
'factCategory': 'カテゴリ',
'factLabels': 'ラベル',
- 'factMarket': 'プラグインマーケット',
+ 'factMarket': 'プラグインインデックス',
'versionsTitle': 'バージョン',
'versionsLabel': '{name} のバージョン',
'versionDefault': '推奨',
@@ -275,3 +275,458 @@ export const ja = {
'detailFailed': '詳細を読み取れませんでした。',
'footnotes': '脚注',
} satisfies Record
+
+/** Spanish dictionary, checked against the English keys. */
+export const es = {
+ title: 'Descubrir',
+ refresh: 'Actualizar',
+ loading: 'leyendo el índice',
+ retry: 'Reintentar',
+ close: 'Cerrar',
+ cancel: 'Cancelar',
+ emptyTitle: 'Aún no hay índices de complementos',
+ emptyDescription: 'Agregue un índice de complementos para explorar e instalar sus complementos aquí.',
+ addFirstSource: 'Agregar fuente de índice',
+ sourcesFailed: 'No se pudieron leer las fuentes del índice.',
+ manageSources: 'Administrar fuentes de índice...',
+ marketMenu: 'Cambiar el índice de complementos; actual: {market}',
+ sourcesTitle: 'Fuentes del índice',
+ sourcesDescription: 'Un índice de complementos se publica mediante la URL de un archivo de índice. Descubrir explora los índices enumerados aquí.',
+ sourcesLabel: 'Fuentes de índice agregadas',
+ sourceUrl: 'URL del índice',
+ sourcePlaceholder: 'https://example.com/index.json',
+ addSource: 'Añadir',
+ removeSource: 'Quitar',
+ removeSourceNamed: 'Quitar {url}',
+ sourcesTrust: 'Los índices provienen de terceros. Agregue solo fuentes en las que confíe: sus complementos se ejecutan con sus permisos una vez instalados.',
+ sourceInvalid: 'Utilice una dirección http o https sin nombre de usuario, contraseña o fragmento #.',
+ sourceDuplicate: 'Esta fuente de índice ya está agregada.',
+ sourceUnavailable: 'Las fuentes del índice no se pueden guardar aquí.',
+ sourceFailed: 'No se pudieron guardar las fuentes del índice.',
+ readFailed: 'No se pudo leer este índice.',
+ readInvalid: 'Este índice no tiene un formato válido.',
+ readChanged: 'Este índice cambió durante la carga.',
+ search: 'Complementos de búsqueda',
+ filter: 'Etiquetas',
+ filterCount: 'Etiquetas · {count}',
+ sortBy: 'Tipo: {sort}',
+ sortRelevance: 'mejor partido',
+ sortDefault: 'Orden predeterminado',
+ sortPublished: 'Fecha de publicación',
+ switchToAscending: 'Orden ascendente',
+ switchToDescending: 'Orden descendente',
+ categoriesLabel: 'Categorías',
+ allCategories: 'Todos',
+ emptyIndex: 'Este índice no incluye complementos.',
+ noMatches: 'Ningún complemento coincide.',
+ showMore: 'Mostrar más',
+ entryMeta: '{version} · {date}',
+ entryMetaAuthor: '{version} · {author} · {date}',
+ openEntry: 'Ver {name}',
+ install: 'Instalar',
+ installTitle: 'Instalar {name}',
+ installVersionTitle: 'Instalar {name} {version}',
+ openInstalled: 'Ver {name} en Instalado',
+ statusInstalled: 'Instalado',
+ statusUpdate: 'Actualización disponible',
+ statusProvided: 'Proporcionado por DSH',
+ incompatible: 'Esta versión no es compatible con DSH {runtime} en ejecución; requiere DSH {range}.',
+ noCompatibleVersion: 'Ninguna versión es compatible con DSH {runtime}',
+ exempted: 'Esta versión no es compatible con DSH en ejecución, pero usted establece una exención de compatibilidad para ella.',
+ backToList: 'Descubrir',
+ factSource: 'Fuente',
+ factPackage: 'Paquete',
+ factRegistry: 'Registro',
+ factRepository: 'Repositorio',
+ factCommit: 'comprometerse',
+ factDirectory: 'Directorio',
+ factPublished: 'Publicado',
+ factAuthor: 'Autor',
+ factHomepage: 'Página de inicio',
+ factSourceCode: 'código fuente',
+ factCategory: 'Categoría',
+ factLabels: 'Etiquetas',
+ factMarket: 'Índice de complementos',
+ versionsTitle: 'Versiones',
+ versionsLabel: 'Versiones de {name}',
+ versionDefault: 'Recomendado',
+ versionInstalled: 'Instalado',
+ versionIncompatible: 'incompatible',
+ versionExempted: 'exento',
+ versionPrerelease: 'Presentación',
+ confirmTitle: '¿Instalar "{name}"?',
+ installTrust: 'Los complementos se ejecutan en esta máquina con sus permisos. Asegúrate de confiar en el origen de este.',
+ detailTitle: 'Detalles',
+ detailLoading: 'Detalles de lectura',
+ detailTooLarge: 'Los detalles o su repositorio exceden el límite de tamaño y no se cargaron.',
+ detailNetwork: 'No se pudo llegar a la fuente de los detalles.',
+ detailDenied: 'No se pudo acceder al repositorio de detalles. Verifique sus credenciales de Git.',
+ detailGitUnavailable: 'Los detalles están en un repositorio que debe leerse con Git y Git no se pudo ejecutar. Instale Git y vuelva a intentarlo.',
+ detailInvalid: 'El complemento declara un archivo de detalles no válido.',
+ detailFailed: 'No se pudieron leer los detalles.',
+ footnotes: 'Notas a pie de página',
+} satisfies Record
+
+/** French dictionary, checked against the English keys. */
+export const fr = {
+ title: 'Découvrez',
+ refresh: 'Actualiser',
+ loading: "Lire l'index",
+ retry: 'Réessayer',
+ close: 'Fermer',
+ cancel: 'Annuler',
+ emptyTitle: 'Aucun index de plugins pour le moment',
+ emptyDescription: 'Ajoutez un index de plugins pour parcourir et installer ses plugins ici.',
+ addFirstSource: "Ajouter une source d'index",
+ sourcesFailed: "Impossible de lire les sources de l'index.",
+ manageSources: "Gérer les sources d'index…",
+ marketMenu: 'Changer d’index de plugins, actuellement {market}',
+ sourcesTitle: "Sources d'index",
+ sourcesDescription: 'Un index de plugins est publié à l’URL d’un fichier d’index. Découvrir parcourt les index répertoriés ici.',
+ sourcesLabel: "Sources d'index ajoutées",
+ sourceUrl: "URL de l'index",
+ sourcePlaceholder: 'https://example.com/index.json',
+ addSource: 'Ajouter',
+ removeSource: 'Supprimer',
+ removeSourceNamed: 'Supprimer {url}',
+ sourcesTrust: "Les index proviennent de tiers. Ajoutez uniquement des sources auxquelles vous faites confiance : leurs plugins s'exécutent avec vos autorisations une fois installés.",
+ sourceInvalid: "Utilisez une adresse http ou https sans nom d'utilisateur, mot de passe ou fragment #.",
+ sourceDuplicate: "Cette source d'index est déjà ajoutée.",
+ sourceUnavailable: "Les sources d'index ne peuvent pas être enregistrées ici.",
+ sourceFailed: "Impossible d'enregistrer les sources de l'index.",
+ readFailed: 'Impossible de lire cet index.',
+ readInvalid: "Cet index n'est pas dans un format valide.",
+ readChanged: 'Cet index a changé lors du chargement.',
+ search: 'Plugins de recherche',
+ filter: 'Étiquettes',
+ filterCount: 'Étiquettes · {count}',
+ sortBy: 'Trier : {sort}',
+ sortRelevance: 'Meilleure correspondance',
+ sortDefault: 'Ordre par défaut',
+ sortPublished: 'Date de publication',
+ switchToAscending: 'Trier par ordre croissant',
+ switchToDescending: 'Trier par ordre décroissant',
+ categoriesLabel: 'Catégories',
+ allCategories: 'Tout',
+ emptyIndex: 'Cet index ne répertorie aucun plugin.',
+ noMatches: 'Aucun plugin ne correspond.',
+ showMore: 'Afficher plus',
+ entryMeta: '{version} · {date}',
+ entryMetaAuthor: '{version} · {author} · {date}',
+ openEntry: 'Voir {name}',
+ install: 'Installer',
+ installTitle: 'Installer {name}',
+ installVersionTitle: 'Installer {name} {version}',
+ openInstalled: 'Afficher {name} dans Installé',
+ statusInstalled: 'Installé',
+ statusUpdate: 'Mise à jour disponible',
+ statusProvided: 'Fourni par DSH',
+ incompatible: "Cette version n'est pas compatible avec le DSH {runtime} en cours d'exécution ; cela nécessite DSH {range}.",
+ noCompatibleVersion: "Aucune version n'est compatible avec DSH {runtime}",
+ exempted: "Cette version n'est pas compatible avec le DSH en cours d'exécution, mais vous avez défini une exemption de compatibilité pour celui-ci.",
+ backToList: 'Découvrez',
+ factSource: 'Source',
+ factPackage: 'Forfait',
+ factRegistry: 'Registre',
+ factRepository: 'Référentiel',
+ factCommit: "S'engager",
+ factDirectory: 'Annuaire',
+ factPublished: 'Publié',
+ factAuthor: 'Auteur',
+ factHomepage: "Page d'accueil",
+ factSourceCode: 'Code source',
+ factCategory: 'Catégorie',
+ factLabels: 'Étiquettes',
+ factMarket: 'Index de plugins',
+ versionsTitle: 'Versions',
+ versionsLabel: 'Versions de {name}',
+ versionDefault: 'Recommandé',
+ versionInstalled: 'Installé',
+ versionIncompatible: 'Incompatible',
+ versionExempted: 'Exonéré',
+ versionPrerelease: 'Avant-première',
+ confirmTitle: 'Installer « {name} » ?',
+ installTrust: "Les plugins s'exécutent sur cette machine avec vos autorisations. Assurez-vous de faire confiance à l'origine de celui-ci.",
+ detailTitle: 'Détails',
+ detailLoading: 'Détails de lecture',
+ detailTooLarge: "Les détails ou leur référentiel dépassent la limite de taille et n'ont pas été chargés.",
+ detailNetwork: "Impossible d'atteindre la source des détails.",
+ detailDenied: "Impossible d'accéder au référentiel des détails. Vérifiez vos informations d'identification Git.",
+ detailGitUnavailable: "Les détails se trouvent dans un référentiel qui doit être lu avec Git, et Git n'a pas pu s'exécuter. Installez Git, puis réessayez.",
+ detailInvalid: 'Le plugin déclare un fichier de détails invalide.',
+ detailFailed: 'Impossible de lire les détails.',
+ footnotes: 'Notes de bas de page',
+} satisfies Record
+
+/** German dictionary, checked against the English keys. */
+export const de = {
+ title: 'Entdecken',
+ refresh: 'Aktualisieren',
+ loading: 'Den Index lesen',
+ retry: 'Erneut versuchen',
+ close: 'Schließen',
+ cancel: 'Abbrechen',
+ emptyTitle: 'Noch keine Plugin-Indizes',
+ emptyDescription: 'Einen Plugin-Index hinzufügen, um dessen Plugins hier zu durchsuchen und zu installieren.',
+ addFirstSource: 'Indexquelle hinzufügen',
+ sourcesFailed: 'Die Indexquellen konnten nicht gelesen werden.',
+ manageSources: 'Indexquellen verwalten…',
+ marketMenu: 'Plugin-Index wechseln, aktuell {market}',
+ sourcesTitle: 'Indexquellen',
+ sourcesDescription: 'Ein Plugin-Index wird über die URL einer Indexdatei veröffentlicht. Entdecken durchsucht die hier aufgeführten Indizes.',
+ sourcesLabel: 'Indexquellen hinzugefügt',
+ sourceUrl: 'Index-URL',
+ sourcePlaceholder: 'https://example.com/index.json',
+ addSource: 'Hinzufügen',
+ removeSource: 'Entfernen',
+ removeSourceNamed: 'Entfernen Sie {url}',
+ sourcesTrust: 'Indizes stammen von Dritten. Fügen Sie nur Quellen hinzu, denen Sie vertrauen: Ihre Plugins werden nach der Installation mit Ihren Berechtigungen ausgeführt.',
+ sourceInvalid: 'Verwenden Sie eine http- oder https-Adresse ohne Benutzernamen, Passwort oder #-Fragment.',
+ sourceDuplicate: 'Diese Indexquelle wurde bereits hinzugefügt.',
+ sourceUnavailable: 'Indexquellen können hier nicht gespeichert werden.',
+ sourceFailed: 'Die Indexquellen konnten nicht gespeichert werden.',
+ readFailed: 'Dieser Index konnte nicht gelesen werden.',
+ readInvalid: 'Dieser Index hat kein gültiges Format.',
+ readChanged: 'Dieser Index hat sich beim Laden geändert.',
+ search: 'Such-Plugins',
+ filter: 'Etiketten',
+ filterCount: 'Etiketten · {count}',
+ sortBy: 'Sortierung: {sort}',
+ sortRelevance: 'Beste Übereinstimmung',
+ sortDefault: 'Standardreihenfolge',
+ sortPublished: 'Veröffentlichungsdatum',
+ switchToAscending: 'Aufsteigend sortieren',
+ switchToDescending: 'Absteigend sortieren',
+ categoriesLabel: 'Kategorien',
+ allCategories: 'Alle',
+ emptyIndex: 'Dieser Index listet keine Plugins auf.',
+ noMatches: 'Es stimmen keine Plugins überein.',
+ showMore: 'Mehr anzeigen',
+ entryMeta: '{version} · {date}',
+ entryMetaAuthor: '{version} · {author} · {date}',
+ openEntry: 'Siehe {name}',
+ install: 'Installieren',
+ installTitle: 'Installieren Sie {name}',
+ installVersionTitle: 'Installieren Sie {name} {version}',
+ openInstalled: '{name} unter „Installiert“ anzeigen',
+ statusInstalled: 'Installiert',
+ statusUpdate: 'Update verfügbar',
+ statusProvided: 'Bereitgestellt von DSH',
+ incompatible: 'Diese Version ist nicht mit dem laufenden DSH {runtime} kompatibel; es erfordert DSH {range}.',
+ noCompatibleVersion: 'Keine Version ist mit DSH {runtime} kompatibel',
+ exempted: 'Diese Version ist nicht mit der laufenden Version DSH kompatibel, Sie haben jedoch eine Kompatibilitätsausnahme dafür festgelegt.',
+ backToList: 'Entdecken',
+ factSource: 'Quelle',
+ factPackage: 'Paket',
+ factRegistry: 'Registrierung',
+ factRepository: 'Repository',
+ factCommit: 'Begehen',
+ factDirectory: 'Verzeichnis',
+ factPublished: 'Veröffentlicht',
+ factAuthor: 'Autor',
+ factHomepage: 'Homepage',
+ factSourceCode: 'Quellcode',
+ factCategory: 'Kategorie',
+ factLabels: 'Etiketten',
+ factMarket: 'Plugin-Index',
+ versionsTitle: 'Versionen',
+ versionsLabel: 'Versionen von {name}',
+ versionDefault: 'Empfohlen',
+ versionInstalled: 'Installiert',
+ versionIncompatible: 'Inkompatibel',
+ versionExempted: 'Ausgenommen',
+ versionPrerelease: 'Vorabversion',
+ confirmTitle: '„{name}“ installieren?',
+ installTrust: 'Plugins werden mit Ihren Berechtigungen auf diesem Computer ausgeführt. Stellen Sie sicher, dass Sie darauf vertrauen, woher dieser kommt.',
+ detailTitle: 'Details',
+ detailLoading: 'Details lesen',
+ detailTooLarge: 'Die Details oder ihr Repository überschreiten die Größenbeschränkung und wurden nicht geladen.',
+ detailNetwork: 'Die Quelle der Details konnte nicht erreicht werden.',
+ detailDenied: 'Auf das Repository mit den Details konnte nicht zugegriffen werden. Überprüfen Sie Ihre Git-Anmeldeinformationen.',
+ detailGitUnavailable: 'Die Details befinden sich in einem Repository, das mit Git gelesen werden muss, und Git konnte nicht ausgeführt werden. Installieren Sie Git und versuchen Sie es erneut.',
+ detailInvalid: 'Das Plugin deklariert eine ungültige Detaildatei.',
+ detailFailed: 'Die Details konnten nicht gelesen werden.',
+ footnotes: 'Fußnoten',
+} satisfies Record
+
+/** Russian dictionary, checked against the English keys. */
+export const ru = {
+ title: 'Откройте для себя',
+ refresh: 'Обновить',
+ loading: 'Чтение индекса',
+ retry: 'Повторить',
+ close: 'Закрыть',
+ cancel: 'Отмена',
+ emptyTitle: 'Индексы плагинов ещё не добавлены',
+ emptyDescription: 'Добавьте индекс плагинов, чтобы просматривать и устанавливать его плагины здесь.',
+ addFirstSource: 'Добавить источник индекса',
+ sourcesFailed: 'Не удалось прочитать исходники индекса.',
+ manageSources: 'Управление источниками индексов…',
+ marketMenu: 'Переключить индекс плагинов, текущий: {market}',
+ sourcesTitle: 'Источники индекса',
+ sourcesDescription: 'Индекс плагинов публикуется по URL файла индекса. В разделе «Обнаружение» можно просматривать перечисленные здесь индексы.',
+ sourcesLabel: 'Добавлены источники индекса',
+ sourceUrl: 'URL-адрес индекса',
+ sourcePlaceholder: 'https://example.com/index.json',
+ addSource: 'Добавить',
+ removeSource: 'Удалить',
+ removeSourceNamed: 'Удалить {url}',
+ sourcesTrust: 'Индексы поступают от третьих лиц. Добавляйте только те источники, которым вы доверяете: их плагины запускаются с вашими разрешениями после установки.',
+ sourceInvalid: 'Используйте адрес http или https без имени пользователя, пароля или фрагмента #.',
+ sourceDuplicate: 'Этот источник индекса уже добавлен.',
+ sourceUnavailable: 'Источники индекса здесь сохранить нельзя.',
+ sourceFailed: 'Не удалось сохранить источники индекса.',
+ readFailed: 'Не удалось прочитать этот индекс.',
+ readInvalid: 'Этот индекс имеет недопустимый формат.',
+ readChanged: 'Этот индекс менялся при загрузке.',
+ search: 'Поиск плагинов',
+ filter: 'Этикетки',
+ filterCount: 'Этикетки · {count}',
+ sortBy: 'Сортировать: {sort}',
+ sortRelevance: 'Лучший матч',
+ sortDefault: 'Порядок по умолчанию',
+ sortPublished: 'Дата публикации',
+ switchToAscending: 'Сортировать по возрастанию',
+ switchToDescending: 'Сортировать по убыванию',
+ categoriesLabel: 'Категории',
+ allCategories: 'Все',
+ emptyIndex: 'В этом индексе нет плагинов.',
+ noMatches: 'Никакие плагины не подходят.',
+ showMore: 'Показать больше',
+ entryMeta: '{version} · {date}',
+ entryMetaAuthor: '{version} · {author} · {date}',
+ openEntry: 'Посмотреть {name}',
+ install: 'Установить',
+ installTitle: 'Установить {name}',
+ installVersionTitle: 'Установить {name} {version}',
+ openInstalled: 'Посмотреть {name} в списке «Установлено»',
+ statusInstalled: 'Установлено',
+ statusUpdate: 'Доступно обновление',
+ statusProvided: 'Предоставлено DSH',
+ incompatible: 'Эта версия несовместима с работающим DSH {runtime}; для этого требуется DSH {range}.',
+ noCompatibleVersion: 'Ни одна версия не совместима с DSH {runtime}.',
+ exempted: 'Эта версия несовместима с работающим DSH, но вы установили для нее исключение совместимости.',
+ backToList: 'Откройте для себя',
+ factSource: 'Источник',
+ factPackage: 'Пакет',
+ factRegistry: 'Реестр',
+ factRepository: 'Репозиторий',
+ factCommit: 'Зафиксировать',
+ factDirectory: 'Каталог',
+ factPublished: 'Опубликовано',
+ factAuthor: 'Автор',
+ factHomepage: 'Домашняя страница',
+ factSourceCode: 'Исходный код',
+ factCategory: 'Категория',
+ factLabels: 'Этикетки',
+ factMarket: 'Индекс плагинов',
+ versionsTitle: 'Версии',
+ versionsLabel: 'Версии {name}',
+ versionDefault: 'Рекомендуется',
+ versionInstalled: 'Установлено',
+ versionIncompatible: 'Несовместимый',
+ versionExempted: 'освобожден',
+ versionPrerelease: 'Пререлиз',
+ confirmTitle: 'Установить "{name}"?',
+ installTrust: 'Плагины запускаются на этом компьютере с вашего разрешения. Убедитесь, что вы доверяете источнику этого сообщения.',
+ detailTitle: 'Подробности',
+ detailLoading: 'Читаем подробности',
+ detailTooLarge: 'Детали или их репозиторий превышают предельный размер и не были загружены.',
+ detailNetwork: 'До источника подробностей добраться не удалось.',
+ detailDenied: 'Не удалось получить доступ к хранилищу реквизитов. Проверьте свои учетные данные Git.',
+ detailGitUnavailable: 'Подробности находятся в репозитории, который необходимо прочитать с помощью Git, и Git не удалось запустить. Установите Git и повторите попытку.',
+ detailInvalid: 'Плагин объявляет неверный файл сведений.',
+ detailFailed: 'Подробности прочитать не удалось.',
+ footnotes: 'Сноски',
+} satisfies Record
+
+/** Portuguese dictionary, checked against the English keys. */
+export const pt = {
+ title: 'Descubra',
+ refresh: 'Atualizar',
+ loading: 'Lendo o índice',
+ retry: 'Tentar novamente',
+ close: 'Fechar',
+ cancel: 'Cancelar',
+ emptyTitle: 'Ainda não há índices de plug-ins',
+ emptyDescription: 'Adicione um índice de plug-ins para navegar e instalar seus plug-ins aqui.',
+ addFirstSource: 'Adicionar fonte de índice',
+ sourcesFailed: 'Não foi possível ler as fontes do índice.',
+ manageSources: 'Gerenciar fontes de índice…',
+ marketMenu: 'Mudar de índice de plug-ins, atual: {market}',
+ sourcesTitle: 'Fontes de índice',
+ sourcesDescription: 'Um índice de plug-ins é publicado pela URL de um arquivo de índice. Descobrir navega pelos índices listados aqui.',
+ sourcesLabel: 'Fontes de índice adicionadas',
+ sourceUrl: 'URL do índice',
+ sourcePlaceholder: 'https://example.com/index.json',
+ addSource: 'Adicionar',
+ removeSource: 'Remover',
+ removeSourceNamed: 'Remover {url}',
+ sourcesTrust: 'Os índices vêm de terceiros. Adicione apenas fontes em que você confia: seus plug-ins são executados com suas permissões, uma vez instalados.',
+ sourceInvalid: 'Use um endereço http ou https sem nome de usuário, senha ou fragmento #.',
+ sourceDuplicate: 'Esta fonte de índice já foi adicionada.',
+ sourceUnavailable: 'As fontes do índice não podem ser salvas aqui.',
+ sourceFailed: 'Não foi possível salvar as fontes do índice.',
+ readFailed: 'Não foi possível ler este índice.',
+ readInvalid: 'Este índice não está em um formato válido.',
+ readChanged: 'Este índice mudou durante o carregamento.',
+ search: 'Plug-ins de pesquisa',
+ filter: 'Etiquetas',
+ filterCount: 'Marcadores · {count}',
+ sortBy: 'Classificar: {sort}',
+ sortRelevance: 'Melhor correspondência',
+ sortDefault: 'Ordem padrão',
+ sortPublished: 'Data de publicação',
+ switchToAscending: 'Classificar em ordem crescente',
+ switchToDescending: 'Classificar em ordem decrescente',
+ categoriesLabel: 'Categorias',
+ allCategories: 'Todos',
+ emptyIndex: 'Este índice não lista plug-ins.',
+ noMatches: 'Nenhum plug-in corresponde.',
+ showMore: 'Mostrar mais',
+ entryMeta: '{version} · {date}',
+ entryMetaAuthor: '{version} · {author} · {date}',
+ openEntry: 'Ver {name}',
+ install: 'Instalar',
+ installTitle: 'Instalar {name}',
+ installVersionTitle: 'Instalar {name} {version}',
+ openInstalled: 'Ver {name} em Instalado',
+ statusInstalled: 'Instalado',
+ statusUpdate: 'Atualização disponível',
+ statusProvided: 'Fornecido por DSH',
+ incompatible: 'Esta versão não é compatível com o DSH {runtime} em execução; requer DSH {range}.',
+ noCompatibleVersion: 'Nenhuma versão é compatível com DSH {runtime}',
+ exempted: 'Esta versão não é compatível com o DSH em execução, mas você definiu uma isenção de compatibilidade para ela.',
+ backToList: 'Descubra',
+ factSource: 'Fonte',
+ factPackage: 'Pacote',
+ factRegistry: 'Registro',
+ factRepository: 'Repositório',
+ factCommit: 'Confirmar',
+ factDirectory: 'Diretório',
+ factPublished: 'Publicado',
+ factAuthor: 'Autor',
+ factHomepage: 'Página inicial',
+ factSourceCode: 'Código fonte',
+ factCategory: 'Categoria',
+ factLabels: 'Etiquetas',
+ factMarket: 'Índice de plug-ins',
+ versionsTitle: 'Versões',
+ versionsLabel: 'Versões de {name}',
+ versionDefault: 'Recomendado',
+ versionInstalled: 'Instalado',
+ versionIncompatible: 'Incompatível',
+ versionExempted: 'Isento',
+ versionPrerelease: 'Pré-lançamento',
+ confirmTitle: 'Instalar "{name}"?',
+ installTrust: 'Os plug-ins são executados nesta máquina com suas permissões. Certifique-se de confiar de onde este vem.',
+ detailTitle: 'Detalhes',
+ detailLoading: 'Lendo detalhes',
+ detailTooLarge: 'Os detalhes ou seu repositório excedem o limite de tamanho e não foram carregados.',
+ detailNetwork: 'Não foi possível acessar a fonte dos detalhes.',
+ detailDenied: 'Não foi possível acessar o repositório dos detalhes. Verifique suas credenciais do Git.',
+ detailGitUnavailable: 'Os detalhes estão em um repositório que deve ser lido com o Git, e o Git não pôde ser executado. Instale o Git e tente novamente.',
+ detailInvalid: 'O plugin declara um arquivo de detalhes inválido.',
+ detailFailed: 'Não foi possível ler os detalhes.',
+ footnotes: 'Notas de rodapé',
+} satisfies Record
diff --git a/packages/client/ui-plugin-market/tests/discover.client.spec.tsx b/packages/client/ui-plugin-market/tests/discover.client.spec.tsx
index 88ee3e6cd3..965275e9f8 100644
--- a/packages/client/ui-plugin-market/tests/discover.client.spec.tsx
+++ b/packages/client/ui-plugin-market/tests/discover.client.spec.tsx
@@ -50,7 +50,7 @@ const LISTING: MarketSearchResult = {
fetchedAt: 1,
total: 6,
nextOffset: null,
- title: { en: 'Alpha market', zh: '甲市场' },
+ title: { en: 'Alpha index', zh: '甲索引' },
metadata: {
categories: { tools: { title: 'Tools' }, themes: { title: 'Themes' } },
labels: { official: { title: 'Official' }, beta: { title: 'Beta' } },
@@ -231,7 +231,7 @@ describe('DiscoverSection', () => {
expect(screen.getByText(en.emptyIndex)).toBeTruthy()
})
- it('sorts by publish date, a market sort key, either direction, and back to the index order', () => {
+ it('sorts by publish date, an index sort key, either direction, and back to the index order', () => {
const { actions, answer } = renderTab()
openMenu(/^Sort: Default order/)
pick(en.sortPublished)
@@ -256,7 +256,7 @@ describe('DiscoverSection', () => {
answer(INITIAL_QUERY)
openMenu(/^Sort: Default order/)
expect(screen.queryByRole('menuitem', { name: en.switchToAscending })).toBeNull()
- for (const name of [/^Sort:/, en.filter, /^Switch plugin market/]) {
+ for (const name of [/^Sort:/, en.filter, /^Switch plugin index/]) {
fireEvent.keyDown(document.activeElement ?? document.body, { key: 'Escape' })
expect(screen.queryByRole('menu')).toBeNull()
openMenu(name)
@@ -264,17 +264,17 @@ describe('DiscoverSection', () => {
}
fireEvent.keyDown(document.activeElement ?? document.body, { key: 'Escape' })
expect(screen.queryByRole('menu')).toBeNull()
- openMenu(/^Switch plugin market/)
- openMenu(/^Switch plugin market/)
+ openMenu(/^Switch plugin index/)
+ openMenu(/^Switch plugin index/)
expect(screen.queryByRole('menu')).toBeNull()
})
- it('switches markets and opens source management from the market menu', () => {
+ it('switches indexes and opens source management from the index menu', () => {
const { actions } = renderTab()
- openMenu('Switch plugin market, currently Alpha market')
+ openMenu('Switch plugin index, currently Alpha index')
pick('b.example')
expect(actions.select).toHaveBeenCalledWith(B)
- openMenu(/^Switch plugin market/)
+ openMenu(/^Switch plugin index/)
pick(en.manageSources)
expect(actions.toggleSources).toHaveBeenCalledOnce()
})
@@ -287,7 +287,7 @@ describe('DiscoverSection', () => {
expect(detail.getByRole('status', { name: en.detailLoading })).toBeTruthy()
expect(detail.getByRole('heading', { name: 'Alpha' })).toBeTruthy()
expect(detail.getByText('dsh-a')).toBeTruthy()
- for (const value of ['Jane', 'dsh-a@1.0.0', 'https://r.example/', 'Tools', 'Official', 'Beta', 'Alpha market']) expect(detail.getByText(value)).toBeTruthy()
+ for (const value of ['Jane', 'dsh-a@1.0.0', 'https://r.example/', 'Tools', 'Official', 'Beta', 'Alpha index']) expect(detail.getByText(value)).toBeTruthy()
const homepage = detail.getByRole('link', { name: 'https://example.com/alpha#readme' })
expect([homepage.getAttribute('href'), homepage.getAttribute('target'), homepage.getAttribute('rel')])
.toEqual(['https://example.com/alpha#readme', '_blank', 'noopener noreferrer'])
@@ -420,14 +420,14 @@ describe('DiscoverSection', () => {
expect(screen.getByRole('status', { name: en.loading })).toBeTruthy()
expect(screen.getByRole('button', { name: en.refresh }).hasAttribute('disabled')).toBe(true)
set({ listing: { url: B, query: INITIAL_QUERY, status: 'ready', value: { ...LISTING, url: B, entries: [], total: 0 }, answered: INITIAL_QUERY, failure: null } })
- expect(screen.getByRole('button', { name: 'Switch plugin market, currently b.example' })).toBeTruthy()
+ expect(screen.getByRole('button', { name: 'Switch plugin index, currently b.example' })).toBeTruthy()
})
it('manages sources in a dialog with the trust notice and each refusal', () => {
const { actions, set } = renderTab({ editing: true, draft: 'https://c.example/index.json', indexes: [{ url: A, title: LISTING.title }, { url: B, error: 'HTTP 404' }] })
const dialog = within(screen.getByRole('dialog', { name: en.sourcesTitle }))
expect(dialog.getByText(en.sourcesTrust)).toBeTruthy()
- expect(dialog.getByText('Alpha market')).toBeTruthy()
+ expect(dialog.getByText('Alpha index')).toBeTruthy()
expect(dialog.getByText(B)).toBeTruthy()
expect(dialog.getByText('HTTP 404')).toBeTruthy()
fireEvent.click(dialog.getByRole('button', { name: `Remove ${B}` }))
@@ -453,7 +453,7 @@ describe('DiscoverSection', () => {
it('renders the Chinese copy and dates', () => {
renderTab({}, zh)
- expect(screen.getByRole('button', { name: '切换插件市场,当前为 甲市场' })).toBeTruthy()
+ expect(screen.getByRole('button', { name: '切换插件索引,当前为 甲索引' })).toBeTruthy()
expect(card('dsh-a').getByText('甲')).toBeTruthy()
expect(card('dsh-a').getByText(/^1\.0\.0 · Jane · 2026年9月/)).toBeTruthy()
expect(card('dsh-b').getByText(zh.statusInstalled)).toBeTruthy()
diff --git a/packages/client/ui-primitives/README.i18n.yaml b/packages/client/ui-primitives/README.i18n.yaml
index 7795512361..351d0e9c54 100644
--- a/packages/client/ui-primitives/README.i18n.yaml
+++ b/packages/client/ui-primitives/README.i18n.yaml
@@ -18,11 +18,11 @@
en: 10be7f75a1576575
zh: abf2905d89b0da48
/deepseek-ai-dsh-client-ui-primitives/use-this-package/component-catalog:
- en: 75e1681dcfc61a14
- zh: 3eda3143730f83e1
+ en: 3c79351ff2210ccb
+ zh: 4cbe07ded89bbaa1
/deepseek-ai-dsh-client-ui-primitives/use-this-package/controls-and-icons:
- en: dbd7f0e74c45a762
- zh: 7efbfbf5500cc835
+ en: b3744373b4b2c1fb
+ zh: 2dbde8ee0bfa5f28
/deepseek-ai-dsh-client-ui-primitives/use-this-package/rendering-agent-output:
en: 02f310acbae29955
zh: 1e41393d5d714b2d
diff --git a/packages/client/ui-primitives/README.md b/packages/client/ui-primitives/README.md
index 637ff7f55c..e1b9fd40db 100644
--- a/packages/client/ui-primitives/README.md
+++ b/packages/client/ui-primitives/README.md
@@ -49,7 +49,7 @@ Check this table before writing a control in a feature package. A plugin cannot
| `Input` | Single-line text entry for search boxes and inline forms. Its ref targets the native input for focus and is cleared on unmount. |
| `InlineEditor` | Autofocused multi-line inline editor for short drafts such as queued messages and goals. It grows with its text and its width up to six lines, then scrolls; Enter saves, Shift+Enter breaks the line, Enter during IME composition does nothing, and Escape cancels. The caller supplies the localized `label` and rebinds the scrollbar on an elevated surface. |
| `ZoomControls`, `DOCUMENT_ZOOM` | Floating document zoom controls with localized labels, fixed presets, and visibility/activity callbacks. An omitted `fit` hides the fitting row. The toolbar carries `data-document-zoom-controls`, plus `data-document-zoom-visible` while visible. An owner `className` overrides `bottom` and `max-width` regardless of stylesheet order; `inset-inline-start` and `transform` center the toolbar and move it on enter and exit, `opacity` fades it, and all three stay with the primitive. Renderers share the published scale limits. |
-| `Menu`, `MenuItemButton` | Dropdown of `items` data rows, separators, and group labels, with nested submenus; `children` adds component rows, each a `MenuItemButton` (`separatorBefore` starts a new group), in the same list. Every row shares the styling, the keyboard walk, and the focus return; closing stays the owner's state change for both kinds. While open, ↑/↓ (with Home and End) walk the list, Tab settles the focused row, and Escape or Shift+Tab close back to the anchor; selecting a row also returns the keyboard to the anchor unless the owner moved it itself. Only a keyboard on the anchor or inside the list is intercepted, and `autoFocus` decides solely whether opening focuses the first row. |
+| `Menu`, `MenuItemButton` | Dropdown of `items` data rows, separators, and group labels, with nested submenus; `children` adds component rows, each a `MenuItemButton` (`separatorBefore` starts a new group), in the same list. `MenuItemButton.current` exposes the current page or view through `aria-current` without changing selection or appearance. Every row shares the styling, the keyboard walk, and the focus return; closing stays the owner's state change for both kinds. While open, ↑/↓ (with Home and End) walk the list, Tab settles the focused row, and Escape or Shift+Tab close back to the anchor; selecting a row also returns the keyboard to the anchor unless the owner moved it itself. Only a keyboard on the anchor or inside the list is intercepted, and `autoFocus` decides solely whether opening focuses the first row. |
| `MenuGroup`, `observeStickyMenuGroups` | Localized, accessible groups for custom menus and listboxes, with shared sticky-heading styling and native intersection/size observation; the caller owns the observer's lifetime. |
| `Pill` | Selectable capsule button for view switchers and filters; takes `active` and `onClick`. |
| `SegmentedTabs` | Controlled equal-width tabs with a sliding indicator and Left/Right, Home, and End navigation. The caller supplies labels, tab/panel ids, and panel content. Default `size="md"` is 42px tall with a 4px inset; `size="sm"` is 34px tall with a 2px inset. |
@@ -96,9 +96,9 @@ Writing your own component in your own package is fine when the need is genuinel
### Controls and icons
-The catalog above lists what each export is for; this section covers the behavior that props alone do not show. Product icon names omit artboard sizes and end in `Regular` for the supplied one-pixel artwork or `Medium` for the same geometry at a 1.3px stroke; the `size` prop controls rendered dimensions. Named components keep the chosen weight visible at import sites; the library owns stroke geometry instead of consumer CSS overrides. Each product icon renders an `