diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py
index 6eb5d3f4b1..b53d71a181 100644
--- a/backend/open_webui/utils/models.py
+++ b/backend/open_webui/utils/models.py
@@ -233,6 +233,7 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None)
if "filterIds" in meta:
filter_ids.extend(meta["filterIds"])
+ custom_model.name = translate_model_title(custom_model.name, request.headers.get("X-Language"))
models.append(
{
"id": f"{custom_model.id}",
diff --git a/src/lib/components/admin/Settings/Interface.svelte b/src/lib/components/admin/Settings/Interface.svelte
index 86507f7c08..4fe01b5e2e 100644
--- a/src/lib/components/admin/Settings/Interface.svelte
+++ b/src/lib/components/admin/Settings/Interface.svelte
@@ -408,7 +408,7 @@
id: uuidv4(),
type: '',
title: '',
- content: '',
+ content: JSON.stringify({ de: '', en: '', fr: '', it: '' }),
dismissible: true,
timestamp: Math.floor(Date.now() / 1000)
}
diff --git a/src/lib/components/admin/Settings/Interface/Banners.svelte b/src/lib/components/admin/Settings/Interface/Banners.svelte
index 6b96374d5e..1456c18c80 100644
--- a/src/lib/components/admin/Settings/Interface/Banners.svelte
+++ b/src/lib/components/admin/Settings/Interface/Banners.svelte
@@ -1,102 +1,228 @@
-
- {#each banners as banner, bannerIdx (banner.id)}
-
-
+
+
+ {#each banners as banner, bannerIdx (banner.id)}
+
+
-
-
+
+
-
+
+
+
+
+
+
+
-
-
- {/each}
+
+
+
+
+ {/each}
+
+
+{#if showBannerModal}
+
+
+
+
{$i18n.t('Edit Translations')}
+
+
+
+ {#each LANGS as lang}
+
+
+
+
+ {/each}
+
+
+
+
+
+
+{/if}
diff --git a/src/lib/components/common/Banner.svelte b/src/lib/components/common/Banner.svelte
index a79b8b42c3..e67af78ceb 100644
--- a/src/lib/components/common/Banner.svelte
+++ b/src/lib/components/common/Banner.svelte
@@ -5,6 +5,7 @@
import DOMPurify from 'dompurify';
import { marked } from 'marked';
import { WEBUI_BASE_URL } from '$lib/constants';
+ import { getLangCode, getTranslatedLabel } from '$lib/i18n';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
@@ -41,6 +42,7 @@
console.log('Banner mounted:', banner);
});
+$: langCode = getLangCode($i18n.language, 'en');
{#if !dismissed}
@@ -100,7 +102,7 @@
{/if}
- {@html marked.parse(DOMPurify.sanitize((banner?.content ?? '').replace(/\n/g, '
')))}
+ {@html marked.parse(DOMPurify.sanitize((getTranslatedLabel(banner?.content, langCode) ?? '').replace(/\n/g, '
')))}
diff --git a/src/lib/components/workspace/Models/ModelEditor.svelte b/src/lib/components/workspace/Models/ModelEditor.svelte
index c3344792f4..0999aa1642 100644
--- a/src/lib/components/workspace/Models/ModelEditor.svelte
+++ b/src/lib/components/workspace/Models/ModelEditor.svelte
@@ -1,6 +1,6 @@
@@ -677,27 +725,18 @@
{#if showTitleModal}
-
+
{$i18n.t('Edit Title Translations')}
-
- {#each Object.keys(titleTranslations) as lang}
+
+ {#each LANGS as lang}
+
+
-
+
{#if showPromptModal}
-
+
@@ -970,39 +1000,33 @@
on:click={() => {
showPromptModal = false;
editingIndex = null;
- newPrompt = { de: '', en: '', fr: '', it: '' };
+ newPrompt = createEmptyTranslations(); // Changed: use dynamic function
}}
>
-
-
+
+
-
- {#each Object.keys(newPrompt) as lang}
+
+ {#each LANGS as lang}
(newPrompt[lang] = e.target.value)}
placeholder={`Enter ${lang.toUpperCase()} translation`}
/>
{/each}
-
+
+ }}>{editingIndex === null ? 'Add' : 'Save'}
diff --git a/src/lib/i18n/index.ts b/src/lib/i18n/index.ts
index ccfa3a25b6..28e627f9d7 100644
--- a/src/lib/i18n/index.ts
+++ b/src/lib/i18n/index.ts
@@ -114,3 +114,69 @@ export function initI18nContext() {
// ---- exports ----
export const isLoading = createIsLoadingStore(i18next);
export default getI18nStore();
+
+// Utils for translations
+export interface Translations {
+ [key: string]: string;
+}
+
+/**
+ * Extracts the language code from a locale string (e.g., 'en-US' -> 'en')
+ * @param language - Full language code (e.g., 'en-US', 'es-ES')
+ * @param fallback - Default language code if parsing fails
+ * @returns Language code (e.g., 'en', 'es', 'de')
+ */
+export function getLangCode(language?: string, fallback: string = 'de'): string {
+ return language?.split('-')[0] || fallback;
+}
+
+/**
+ * Gets translated label from translation object or JSON string
+ * @param label - Translation object or JSON string containing translations
+ * @param langCode - Target language code (e.g., 'en', 'es', 'de')
+ * @returns Translated string or empty string if not found
+ */
+export function getTranslatedLabel(
+ label: string | Translations | null | undefined,
+ langCode: string
+): string {
+ if (!label) return '';
+
+ try {
+ // If it's already an object, use it directly
+ const translations: Translations = typeof label === 'object' ? label : JSON.parse(label);
+
+ return (
+ translations[langCode] ||
+ translations.en ||
+ translations.de ||
+ translations.fr ||
+ translations.it ||
+ ''
+ );
+ } catch (error) {
+ // Log parsing errors for debugging in development
+ if (process.env.NODE_ENV === 'development') {
+ console.warn('Failed to parse translation label:', label, error);
+ }
+
+ // If parsing fails, return the original value if it's a string
+ return typeof label === 'string' ? label : '';
+ }
+}
+
+/**
+ * Convenience function that combines getLangCode and getTranslatedLabel
+ * @param label - Translation object or JSON string
+ * @param language - Full language code (e.g., 'en-US')
+ * @param fallback - Fallback language code
+ * @returns Translated string
+ */
+export function translate(
+ label: string | Translations | null | undefined,
+ language?: string,
+ fallback: string = 'de'
+): string {
+ const langCode = getLangCode(language, fallback);
+ return getTranslatedLabel(label, langCode);
+}
\ No newline at end of file
diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts
index 441d3ca681..b22dec97c1 100644
--- a/src/lib/stores/index.ts
+++ b/src/lib/stores/index.ts
@@ -260,6 +260,7 @@ type Config = {
enable_autocomplete_generation: boolean;
enable_direct_connections: boolean;
enable_version_update_check: boolean;
+ translation_languages: string[];
};
oauth: {
providers: {