diff --git a/core/http/react-ui/e2e/model-editor-back-nav.spec.js b/core/http/react-ui/e2e/model-editor-back-nav.spec.js index 5ad085aea..973d93967 100644 --- a/core/http/react-ui/e2e/model-editor-back-nav.spec.js +++ b/core/http/react-ui/e2e/model-editor-back-nav.spec.js @@ -44,7 +44,7 @@ test.describe('Model Editor — Back navigation', () => { await mockEditorEndpoints(page) }) - test('Back returns to Manage with a "Back to Manage" caption', async ({ page }) => { + test('Back returns to Manage with a "Back to System" caption', async ({ page }) => { await page.goto('/app/manage') await expect(page.locator('.table')).toBeVisible({ timeout: 10_000 }) @@ -55,7 +55,7 @@ test.describe('Model Editor — Back navigation', () => { await page.getByRole('menuitem', { name: 'Edit configuration' }).click() await expect(page).toHaveURL(/\/app\/model-editor\//) - const back = page.getByRole('button', { name: /Back to Manage/ }) + const back = page.getByRole('button', { name: /Back to System/ }) await expect(back).toBeVisible({ timeout: 10_000 }) await back.click() @@ -89,6 +89,6 @@ test.describe('Model Editor — Back navigation', () => { test('falls back to "Back to Manage" on a direct visit with no origin state', async ({ page }) => { await page.goto('/app/model-editor/mock-model') - await expect(page.getByRole('button', { name: /Back to Manage/ })).toBeVisible({ timeout: 10_000 }) + await expect(page.getByRole('button', { name: /Back to System/ })).toBeVisible({ timeout: 10_000 }) }) }) diff --git a/core/http/react-ui/public/locales/en/common.json b/core/http/react-ui/public/locales/en/common.json index a18ecdb3a..7767ebbae 100644 --- a/core/http/react-ui/public/locales/en/common.json +++ b/core/http/react-ui/public/locales/en/common.json @@ -86,7 +86,8 @@ "type": "Type", "value": "Value", "search": "Search...", - "selectPlaceholder": "Select an option..." + "selectPlaceholder": "Select an option...", + "noMatch": "No matches" }, "time": { "now": "now", diff --git a/core/http/react-ui/public/locales/en/modelEditor.json b/core/http/react-ui/public/locales/en/modelEditor.json new file mode 100644 index 000000000..ef4ddaa8e --- /dev/null +++ b/core/http/react-ui/public/locales/en/modelEditor.json @@ -0,0 +1,38 @@ +{ + "title": { + "add": "Add Model", + "edit": "Model Editor" + }, + "subtitle": { + "chooseModelType": "Choose a model type to get started", + "newModel": "New model" + }, + "actions": { + "backTo": "Back to {{page}}", + "system": "System", + "templates": "Templates", + "createModel": "Create Model", + "saveChanges": "Save Changes", + "saving": "Saving...", + "saved": "Saved", + "switchWarning": "Save or discard changes before switching tabs.", + "discardAndSwitch": "Discard & Switch" + }, + "tabs": { + "interactive": "Interactive", + "yaml": "YAML", + "yamlDescription": "Edit the YAML directly. The model name must be set in the YAML for create to work." + }, + "forms": { + "modelName": { + "label": "Model Name", + "placeholder": "my-model-name", + "hint": "Use letters, numbers, hyphens, underscores, and dots only." + }, + "empty": { + "nav": "Use the search bar above to add fields", + "title": "No fields configured", + "text": "Use the search bar above to find and add configuration fields." + } + } +} diff --git a/core/http/react-ui/public/locales/en/models.json b/core/http/react-ui/public/locales/en/models.json index 603bd809c..13eb3592e 100644 --- a/core/http/react-ui/public/locales/en/models.json +++ b/core/http/react-ui/public/locales/en/models.json @@ -1,6 +1,7 @@ { "title": "Install Models", "subtitle": "Browse and install AI models from the gallery", + "models": "Models", "stats": { "available": "Available", "installed": "Installed" @@ -89,5 +90,11 @@ "loadFailed": "Failed to load models: {{message}}", "installFailed": "Failed to install: {{message}}", "deleteFailed": "Failed to delete: {{message}}" + }, + "selector": { + "loading": "Loading models...", + "selectModel": "Select model...", + "searchPlaceholder": "Search models...", + "noModels": "No models available" } } diff --git a/core/http/react-ui/public/locales/id/common.json b/core/http/react-ui/public/locales/id/common.json index b20eeb00a..80dc59c70 100644 --- a/core/http/react-ui/public/locales/id/common.json +++ b/core/http/react-ui/public/locales/id/common.json @@ -86,7 +86,8 @@ "type": "Tipe", "value": "Nilai", "search": "Cari...", - "selectPlaceholder": "Pilih opsi..." + "selectPlaceholder": "Pilih opsi...", + "noMatch": "Tidak ada yang cocok" }, "time": { "now": "baru saja", @@ -106,4 +107,4 @@ "gigabytes": "GB", "terabytes": "TB" } -} \ No newline at end of file +} diff --git a/core/http/react-ui/public/locales/id/importModel.json b/core/http/react-ui/public/locales/id/importModel.json index 0e3a8c49d..a23333873 100644 --- a/core/http/react-ui/public/locales/id/importModel.json +++ b/core/http/react-ui/public/locales/id/importModel.json @@ -1,7 +1,7 @@ { "title": "Impor Model Baru", "subtitle": { - "simple": "Import model dari URI — deteksi otomatis memilih backend.", + "simple": "Impor model dari URI — deteksi otomatis memilih backend.", "powerYaml": "Tulis konfigurasi YAML lengkap untuk model.", "powerPrefs": "Preferensi impor tingkat lanjut." }, @@ -139,4 +139,4 @@ "local": "File konfigurasi YAML lokal" } } -} \ No newline at end of file +} diff --git a/core/http/react-ui/public/locales/id/modelEditor.json b/core/http/react-ui/public/locales/id/modelEditor.json new file mode 100644 index 000000000..16869d1bd --- /dev/null +++ b/core/http/react-ui/public/locales/id/modelEditor.json @@ -0,0 +1,38 @@ +{ + "title": { + "add": "Tambah Model", + "edit": "Editor Model" + }, + "subtitle": { + "chooseModelType": "Pilih tipe model untuk memulai", + "newModel": "Model baru" + }, + "actions": { + "backTo": "Kembali ke {{page}}", + "system": "Sistem", + "templates": "Templat", + "createModel": "Buat Model", + "saveChanges": "Simpan Perubahan", + "saving": "Menyimpan...", + "saved": "Tersimpan", + "switchWarning": "Simpan atau buang perubahan sebelum beralih tab.", + "discardAndSwitch": "Buang & Beralih" + }, + "tabs": { + "interactive": "Interaktif", + "yaml": "YAML", + "yamlDescription": "Edit YAML secara langsung. Nama model harus diatur di YAML agar pembuatan berhasil." + }, + "forms": { + "modelName": { + "label": "Nama Model", + "placeholder": "nama-model-saya", + "hint": "Gunakan huruf, angka, tanda hubung, garis bawah, dan titik saja." + }, + "empty": { + "nav": "Gunakan kolom pencarian di atas untuk menambahkan field", + "title": "Tidak ada field yang dikonfigurasi", + "text": "Gunakan kolom pencarian di atas untuk menemukan dan menambahkan field konfigurasi." + } + } +} diff --git a/core/http/react-ui/public/locales/id/models.json b/core/http/react-ui/public/locales/id/models.json index d88a04116..a8c5404fa 100644 --- a/core/http/react-ui/public/locales/id/models.json +++ b/core/http/react-ui/public/locales/id/models.json @@ -1,6 +1,7 @@ { "title": "Instal Model", "subtitle": "Telusuri dan instal model AI dari galeri", + "models": "Model", "stats": { "available": "Tersedia", "installed": "Terinstal" @@ -89,5 +90,11 @@ "loadFailed": "Gagal memuat model: {{message}}", "installFailed": "Gagal menginstal: {{message}}", "deleteFailed": "Gagal menghapus: {{message}}" + }, + "selector": { + "loading": "Memuat model...", + "selectModel": "Pilih model...", + "searchPlaceholder": "Cari model...", + "noModels": "Model tidak tersedia" } -} \ No newline at end of file +} diff --git a/core/http/react-ui/src/components/ModelSelector.jsx b/core/http/react-ui/src/components/ModelSelector.jsx index 1974a4927..9009524ee 100644 --- a/core/http/react-ui/src/components/ModelSelector.jsx +++ b/core/http/react-ui/src/components/ModelSelector.jsx @@ -1,12 +1,14 @@ import { useEffect, useMemo } from 'react' import { useModels } from '../hooks/useModels' import SearchableSelect from './SearchableSelect' +import { useTranslation } from 'react-i18next' export default function ModelSelector({ value, onChange, capability, className = '', options: externalOptions, loading: externalLoading, disabled: externalDisabled, searchPlaceholder, style, }) { + const { t } = useTranslation('models') // Skip capability fetch when external options are provided (capability will be undefined) const { models: hookModels, loading: hookLoading } = useModels(externalOptions ? undefined : capability) @@ -28,8 +30,8 @@ export default function ModelSelector({ value={value || ''} onChange={onChange} options={modelNames} - placeholder={isLoading ? 'Loading models...' : (modelNames.length === 0 ? 'No models available' : 'Select model...')} - searchPlaceholder={searchPlaceholder || 'Search models...'} + placeholder={isLoading ? t('selector.loading') : (modelNames.length === 0 ? t('selector.noModels') : t('selector.selectModel'))} + searchPlaceholder={searchPlaceholder || t('selector.searchPlaceholder')} disabled={isDisabled} className={className} style={style} diff --git a/core/http/react-ui/src/components/SearchableSelect.jsx b/core/http/react-ui/src/components/SearchableSelect.jsx index 50324d262..52430adab 100644 --- a/core/http/react-ui/src/components/SearchableSelect.jsx +++ b/core/http/react-ui/src/components/SearchableSelect.jsx @@ -1,10 +1,12 @@ import { useState, useEffect, useRef, useMemo } from 'react' +import { useTranslation } from 'react-i18next' export default function SearchableSelect({ value, onChange, options, placeholder = 'Select...', allOption, searchPlaceholder = 'Search...', disabled = false, style, className = '', }) { + const { t } = useTranslation('common') const [open, setOpen] = useState(false) const [query, setQuery] = useState('') const [focusIndex, setFocusIndex] = useState(-1) @@ -226,7 +228,7 @@ export default function SearchableSelect({ })} {filtered.length === 0 && !allOption && (
Failed to load config metadata: {metaError}
{isCreateMode - ? (showTemplateSelector ? 'Choose a model type to get started' : `New model${selectedTemplate ? ` — ${selectedTemplate.label}` : ''}`) + ? (showTemplateSelector ? t('subtitle.chooseModelType') : `${t('subtitle.newModel')}${selectedTemplate ? ` — ${selectedTemplate.label}` : ''}`) : decodeURIComponent(name)}
- Edit the YAML directly. The model name must be set in the YAML for create to work. + {t('tabs.yamlDescription')}
)}- Use letters, numbers, hyphens, underscores, and dots only. + {t('forms.modelName.hint')}
- Use the search bar above to find and add configuration fields. + {t('forms.empty.text')}