diff --git a/frontend/e2e/localization.spec.js b/frontend/e2e/localization.spec.js new file mode 100644 index 0000000..42d28c8 --- /dev/null +++ b/frontend/e2e/localization.spec.js @@ -0,0 +1,48 @@ +import { test, expect } from '@playwright/test'; +import { waitForAppReady, setupConsoleCollector, resetMockState } from './helpers.js'; + +test.describe('Desktop localization', () => { + let consoleCollector; + + test.beforeEach(async ({ page }) => { + consoleCollector = setupConsoleCollector(page); + await resetMockState(page); + await page.goto('/'); + await waitForAppReady(page); + }); + + test.afterEach(async () => { + consoleCollector.assertNoErrors(); + }); + + test('switches shell language live and persists the choice', async ({ page }) => { + await page.locator('[data-settings-menu-button]').click(); + await expect(page.locator('[data-settings-language="system"]')).toHaveAttribute('aria-checked', 'true'); + await page.locator('[data-settings-language="ru"]').click(); + + await expect(page.locator('[data-settings-menu-button]')).toHaveAttribute('title', 'Настройки'); + await expect(page.locator('.vault-status')).toContainText('Хранилище: открыто'); + await expect(page.locator('.sidebar .plugin-item').filter({ hasText: 'Тест платформы' })).toBeVisible(); + await expect(page.locator('[data-status-item-id="verstak.platform-test.status"]')).toContainText('Все тесты пройдены'); + + await page.locator('[data-settings-menu-button]').click(); + await expect(page.locator('[data-settings-action="plugin-manager"]')).toContainText('Менеджер плагинов'); + await expect(page.locator('[data-settings-language="ru"]')).toHaveAttribute('aria-checked', 'true'); + + await page.reload(); + await waitForAppReady(page); + await expect(page.locator('[data-settings-menu-button]')).toHaveAttribute('title', 'Настройки'); + await page.locator('[data-settings-menu-button]').click(); + await expect(page.locator('[data-settings-language="ru"]')).toHaveAttribute('aria-checked', 'true'); + }); + + test('switches back to English without reloading', async ({ page }) => { + await page.locator('[data-settings-menu-button]').click(); + await page.locator('[data-settings-language="ru"]').click(); + await page.locator('[data-settings-menu-button]').click(); + await page.locator('[data-settings-language="en"]').click(); + + await expect(page.locator('[data-settings-menu-button]')).toHaveAttribute('title', 'Settings'); + await expect(page.locator('.vault-status')).toContainText('Vault: open'); + }); +}); diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 1e7a007..7ededf2 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -9,13 +9,19 @@ import WorkspaceHost from './lib/shell/WorkspaceHost.svelte'; import * as App from '../wailsjs/go/api/App'; import { debug } from './lib/log/debug.js'; - import { onMount } from 'svelte'; - import { tick } from 'svelte'; + import { onDestroy, onMount, tick } from 'svelte'; + import { i18n } from './lib/i18n/index.js'; let currentView = 'workspace'; let vaultStatus = { status: 'unknown', path: '', vaultId: '' }; let needsVaultSelection = false; let loading = true; + let locale = i18n.getLocale(); + const unsubscribeLocale = i18n.subscribe((nextLocale) => { locale = nextLocale; }); + $: tr = ((activeLocale) => (key, params, fallback) => { + void activeLocale; + return i18n.t(key, params, fallback); + })(locale); let activeView = null; let activeViewPluginId = ''; @@ -375,11 +381,13 @@ await checkVault(); pushNavigation(); }); + + onDestroy(unsubscribeLocale); {#if loading}
Loading Verstak...
+{tr('app.loading')}
Select a plugin view from the sidebar
+{tr('pluginView.select')}
Plugin View Error
+{tr('pluginView.error')}
Plugin: {currentPluginId || 'unknown'}
-Component: {currentComponent || 'unknown'}
- +{tr('common.plugin')}: {currentPluginId || tr('common.unknown')}
+{tr('common.component')}: {currentComponent || tr('common.unknown')}
+ {#if pluginInfo} - + {/if} {#if getComponentList().length > 0} - + {/if}Loading plugin bundle...
+{tr('bundle.loading')}
Plugin is usable, but some optional capabilities are unavailable.
+{tr('pluginCard.degraded')}
{/if} {#if m.description} @@ -94,34 +103,34 @@