From a4cb7f5c65919f2d366ee6054f0f82e566bc44f0 Mon Sep 17 00:00:00 2001 From: "Dedy F. Setyawan" Date: Mon, 15 Jun 2026 16:11:23 +0700 Subject: [PATCH 1/6] feat(react-ui): localize SearchableSelect component Signed-off-by: Dedy F. Setyawan --- core/http/react-ui/public/locales/en/common.json | 3 ++- core/http/react-ui/public/locales/id/common.json | 5 +++-- core/http/react-ui/src/components/SearchableSelect.jsx | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/http/react-ui/public/locales/en/common.json b/core/http/react-ui/public/locales/en/common.json index a18ecdb3acb8..7767ebbae5ed 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/id/common.json b/core/http/react-ui/public/locales/id/common.json index b20eeb00a667..80dc59c702ea 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/src/components/SearchableSelect.jsx b/core/http/react-ui/src/components/SearchableSelect.jsx index 50324d2620ea..52430adabc27 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 && (
- No matches + {t('forms.noMatch')}
)} From 0613a59500acfaee9254602a1bf4dacfce0539d0 Mon Sep 17 00:00:00 2001 From: "Dedy F. Setyawan" Date: Mon, 15 Jun 2026 16:20:50 +0700 Subject: [PATCH 2/6] feat(react-ui): localize ModelSelector component Signed-off-by: Dedy F. Setyawan --- core/http/react-ui/public/locales/en/models.json | 6 ++++++ core/http/react-ui/public/locales/id/models.json | 8 +++++++- core/http/react-ui/src/components/ModelSelector.jsx | 6 ++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core/http/react-ui/public/locales/en/models.json b/core/http/react-ui/public/locales/en/models.json index 603bd809cc8a..cceb0bff38c0 100644 --- a/core/http/react-ui/public/locales/en/models.json +++ b/core/http/react-ui/public/locales/en/models.json @@ -89,5 +89,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/models.json b/core/http/react-ui/public/locales/id/models.json index d88a04116291..4477124d9c4b 100644 --- a/core/http/react-ui/public/locales/id/models.json +++ b/core/http/react-ui/public/locales/id/models.json @@ -89,5 +89,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 1974a4927c4e..9009524ee6ed 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} From af6cb203f9b1c17d95aaa814caa495a6a321dcc2 Mon Sep 17 00:00:00 2001 From: "Dedy F. Setyawan" Date: Mon, 15 Jun 2026 16:32:02 +0700 Subject: [PATCH 3/6] fix(react-ui): dynamically localize back navigation caption to match page title Signed-off-by: Dedy F. Setyawan --- core/http/react-ui/e2e/model-editor-back-nav.spec.js | 6 +++--- core/http/react-ui/src/pages/Manage.jsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) 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 5ad085aeafb7..973d93967bce 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/src/pages/Manage.jsx b/core/http/react-ui/src/pages/Manage.jsx index 79c57413fbb9..10d684a6eb18 100644 --- a/core/http/react-ui/src/pages/Manage.jsx +++ b/core/http/react-ui/src/pages/Manage.jsx @@ -675,7 +675,7 @@ export default function Manage() { onClick: () => handleTogglePinned(model.id, model.pinned), disabled: pinningModels.has(model.id) || !!model.disabled }, { key: 'edit', icon: 'fa-pen-to-square', label: 'Edit configuration', - onClick: () => navigate(`/app/model-editor/${encodeURIComponent(model.id)}`, { state: fromState(location, 'Manage') }) }, + onClick: () => navigate(`/app/model-editor/${encodeURIComponent(model.id)}`, { state: fromState(location, t('manage.title')) }) }, { key: 'logs', icon: 'fa-terminal', label: 'Backend logs', onClick: () => navigate(`/app/backend-logs/${encodeURIComponent(model.id)}`) }, { divider: true }, From 0243a832710ec9a2b6a24f4e886fce26562c9e0f Mon Sep 17 00:00:00 2001 From: "Dedy F. Setyawan" Date: Mon, 15 Jun 2026 16:41:26 +0700 Subject: [PATCH 4/6] feat(react-ui): localize back navigation state on Models page Signed-off-by: Dedy F. Setyawan --- core/http/react-ui/public/locales/en/models.json | 1 + core/http/react-ui/public/locales/id/models.json | 1 + core/http/react-ui/src/pages/Models.jsx | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/http/react-ui/public/locales/en/models.json b/core/http/react-ui/public/locales/en/models.json index cceb0bff38c0..13eb3592ef1f 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" diff --git a/core/http/react-ui/public/locales/id/models.json b/core/http/react-ui/public/locales/id/models.json index 4477124d9c4b..a8c5404fa43d 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" diff --git a/core/http/react-ui/src/pages/Models.jsx b/core/http/react-ui/src/pages/Models.jsx index be8b04ae2852..7e2122801d9c 100644 --- a/core/http/react-ui/src/pages/Models.jsx +++ b/core/http/react-ui/src/pages/Models.jsx @@ -288,7 +288,7 @@ export default function Models() { - {!showTemplateSelector && tab === 'interactive' && ( )} {!showTemplateSelector && tab === 'yaml' && ( )} @@ -448,17 +450,17 @@ export default function ModelEditor() { display: 'flex', gap: 0, padding: '0 var(--spacing-lg)', borderBottom: '1px solid var(--color-border)', }}> - {['interactive', 'yaml'].map(t => { - const active = tab === t + {['interactive', 'yaml'].map(tb => { + const active = tab === tb const blocked = !active && isDirty return ( ) })} @@ -485,7 +487,7 @@ export default function ModelEditor() { background: 'var(--color-warning-light, rgba(245, 158, 11, 0.08))', }}> - Save or discard changes before switching tabs. + {t('actions.switchWarning')} )} @@ -512,7 +514,7 @@ export default function ModelEditor() {
{isCreateMode && (

- Edit the YAML directly. The model name must be set in the YAML for create to work. + {t('tabs.yamlDescription')}

)} handleFieldChange('name', e.target.value)} - placeholder="my-model-name" + placeholder={t('forms.modelName.placeholder')} style={{ maxWidth: 400 }} />

- Use letters, numbers, hyphens, underscores, and dots only. + {t('forms.modelName.hint')}

@@ -596,7 +598,7 @@ export default function ModelEditor() { ))} {activeSections.length === 0 && (
- Use the search bar above to add fields + {t('forms.empty.nav')}
)} @@ -610,9 +612,9 @@ export default function ModelEditor() { {activeSections.length === 0 && (
-

No fields configured

+

{t('forms.empty.title')}

- Use the search bar above to find and add configuration fields. + {t('forms.empty.text')}

)} From 3be9ac50301aa9cafe851ca09ece27f95e37406d Mon Sep 17 00:00:00 2001 From: "Dedy F. Setyawan" Date: Mon, 15 Jun 2026 16:50:48 +0700 Subject: [PATCH 6/6] fix(react-ui): fix Indonesian typo 'Import' to 'Impor' in importModel locale Signed-off-by: Dedy F. Setyawan --- core/http/react-ui/public/locales/id/importModel.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/http/react-ui/public/locales/id/importModel.json b/core/http/react-ui/public/locales/id/importModel.json index 0e3a8c49de4d..a23333873bb7 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 +}