forked from open-webui/open-webui
		
	main #2
					 17 changed files with 180 additions and 25 deletions
				
			
		|  | @ -45,6 +45,9 @@ | |||
| 		"dayjs": "^1.11.10", | ||||
| 		"file-saver": "^2.0.5", | ||||
| 		"highlight.js": "^11.9.0", | ||||
| 		"i18next": "^23.10.0", | ||||
| 		"i18next-browser-languagedetector": "^7.2.0", | ||||
| 		"i18next-resources-to-backend": "^1.2.0", | ||||
| 		"idb": "^7.1.1", | ||||
| 		"js-sha256": "^0.10.1", | ||||
| 		"katex": "^0.16.9", | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <script lang="ts"> | ||||
| 	import { toast } from 'svelte-sonner'; | ||||
| 	import { onMount, tick } from 'svelte'; | ||||
| 	import { onMount, tick, getContext } from 'svelte'; | ||||
| 	import { settings } from '$lib/stores'; | ||||
| 	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils'; | ||||
| 
 | ||||
|  | @ -14,6 +14,8 @@ | |||
| 	import { transcribeAudio } from '$lib/apis/audio'; | ||||
| 	import Tooltip from '../common/Tooltip.svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	export let submitPrompt: Function; | ||||
| 	export let stopResponse: Function; | ||||
| 
 | ||||
|  | @ -669,8 +671,8 @@ | |||
| 							placeholder={chatInputPlaceholder !== '' | ||||
| 								? chatInputPlaceholder | ||||
| 								: isRecording | ||||
| 								? 'Listening...' | ||||
| 								: 'Send a message'} | ||||
| 								? $i18n.t('ChatInputPlaceholderListening') | ||||
| 								: $i18n.t('ChatInputPlaceholder')} | ||||
| 							bind:value={prompt} | ||||
| 							on:keypress={(e) => { | ||||
| 								if (e.keyCode == 13 && !e.shiftKey) { | ||||
|  |  | |||
|  | @ -2,9 +2,11 @@ | |||
| 	import { generatePrompt } from '$lib/apis/ollama'; | ||||
| 	import { models } from '$lib/stores'; | ||||
| 	import { splitStream } from '$lib/utils'; | ||||
| 	import { tick } from 'svelte'; | ||||
| 	import { tick, getContext } from 'svelte'; | ||||
| 	import { toast } from 'svelte-sonner'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	export let prompt = ''; | ||||
| 	export let user = null; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,9 @@ | |||
| <script lang="ts"> | ||||
| 	import { WEBUI_BASE_URL } from '$lib/constants'; | ||||
| 	import { user } from '$lib/stores'; | ||||
| 	import { onMount } from 'svelte'; | ||||
| 	import { onMount, getContext } from 'svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	export let models = []; | ||||
| 	export let modelfiles = []; | ||||
|  | @ -64,9 +66,9 @@ | |||
| 					</div> | ||||
| 				{/if} | ||||
| 			{:else} | ||||
| 				<div class=" line-clamp-1">Hello, {$user.name}</div> | ||||
| 				<div class=" line-clamp-1">{$i18n.t('Hello', { name: $user.name })}</div> | ||||
| 
 | ||||
| 				<div>How can I help you today?</div> | ||||
| 				<div>{$i18n.t('GreetingPlaceholder')}</div> | ||||
| 			{/if} | ||||
| 		</div> | ||||
| 	</div> | ||||
|  |  | |||
|  | @ -1,9 +1,11 @@ | |||
| <script lang="ts"> | ||||
| 	import { setDefaultModels } from '$lib/apis/configs'; | ||||
| 	import { models, showSettings, settings, user } from '$lib/stores'; | ||||
| 	import { onMount, tick } from 'svelte'; | ||||
| 	import { onMount, tick, getContext } from 'svelte'; | ||||
| 	import { toast } from 'svelte-sonner'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	export let selectedModels = ['']; | ||||
| 	export let disabled = false; | ||||
| 
 | ||||
|  | @ -39,7 +41,9 @@ | |||
| 				bind:value={selectedModel} | ||||
| 				{disabled} | ||||
| 			> | ||||
| 				<option class=" text-gray-700" value="" selected disabled>Select a model</option> | ||||
| 				<option class=" text-gray-700" value="" selected disabled | ||||
| 					>{$i18n.t('ModelSelectorPlaceholder')}</option | ||||
| 				> | ||||
| 
 | ||||
| 				{#each $models as model} | ||||
| 					{#if model.name === 'hr'} | ||||
|  | @ -133,5 +137,5 @@ | |||
| </div> | ||||
| 
 | ||||
| <div class="text-left mt-1.5 text-xs text-gray-500"> | ||||
| 	<button on:click={saveDefaultModel}> Set as default</button> | ||||
| 	<button on:click={saveDefaultModel}> {$i18n.t('SetAsDefault')}</button> | ||||
| </div> | ||||
|  |  | |||
|  | @ -1,10 +1,12 @@ | |||
| <script lang="ts"> | ||||
| 	import { toast } from 'svelte-sonner'; | ||||
| 	import { createEventDispatcher, onMount } from 'svelte'; | ||||
| 	import { createEventDispatcher, onMount, getContext } from 'svelte'; | ||||
| 	const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
| 	import { models, user } from '$lib/stores'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	import AdvancedParams from './Advanced/AdvancedParams.svelte'; | ||||
| 
 | ||||
| 	export let saveSettings: Function; | ||||
|  | @ -13,6 +15,9 @@ | |||
| 	// General | ||||
| 	let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light']; | ||||
| 	let theme = 'dark'; | ||||
| 	// TODO: Get these dynamically from the i18n module | ||||
| 	let languages = ['en', 'fa', 'fr', 'de']; | ||||
| 	let lang = $i18n.language; | ||||
| 	let notificationEnabled = false; | ||||
| 	let system = ''; | ||||
| 
 | ||||
|  | @ -149,6 +154,25 @@ | |||
| 				</div> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div class=" py-0.5 flex w-full justify-between"> | ||||
| 				<div class=" self-center text-xs font-medium">Language</div> | ||||
| 				<div class="flex items-center relative"> | ||||
| 					<select | ||||
| 						class="w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right" | ||||
| 						bind:value={lang} | ||||
| 						placeholder="Select a language" | ||||
| 						on:change={(e) => { | ||||
| 							console.log($i18n); | ||||
| 							$i18n.changeLanguage(lang); | ||||
| 						}} | ||||
| 					> | ||||
| 						{#each languages as value} | ||||
| 							<option {value}>{value}</option> | ||||
| 						{/each} | ||||
| 					</select> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div> | ||||
| 				<div class=" py-0.5 flex w-full justify-between"> | ||||
| 					<div class=" self-center text-xs font-medium">Notification</div> | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| <script lang="ts"> | ||||
| 	import { toast } from 'svelte-sonner'; | ||||
| 
 | ||||
| 	import { createEventDispatcher, onMount } from 'svelte'; | ||||
| 	import { createEventDispatcher, onMount, getContext } from 'svelte'; | ||||
| 	import { config, user } from '$lib/stores'; | ||||
| 	import { | ||||
| 		getAUTOMATIC1111Url, | ||||
|  | @ -19,6 +19,8 @@ | |||
| 	import { getBackendConfig } from '$lib/apis'; | ||||
| 	const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	export let saveSettings: Function; | ||||
| 
 | ||||
| 	let loading = false; | ||||
|  | @ -193,10 +195,10 @@ | |||
| 						<select | ||||
| 							class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | ||||
| 							bind:value={selectedModel} | ||||
| 							placeholder="Select a model" | ||||
| 							placeholder={$i18n.t('ModelSelectorPlaceholder')} | ||||
| 						> | ||||
| 							{#if !selectedModel} | ||||
| 								<option value="" disabled selected>Select a model</option> | ||||
| 								<option value="" disabled selected>{$i18n.t('ModelSelectorPlaceholder')}</option> | ||||
| 							{/if} | ||||
| 							{#each models ?? [] as model} | ||||
| 								<option value={model.title} class="bg-gray-100 dark:bg-gray-700" | ||||
|  |  | |||
|  | @ -2,10 +2,12 @@ | |||
| 	import { getBackendConfig } from '$lib/apis'; | ||||
| 	import { setDefaultPromptSuggestions } from '$lib/apis/configs'; | ||||
| 	import { config, models, user } from '$lib/stores'; | ||||
| 	import { createEventDispatcher, onMount } from 'svelte'; | ||||
| 	import { createEventDispatcher, onMount, getContext } from 'svelte'; | ||||
| 	import { toast } from 'svelte-sonner'; | ||||
| 	const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	export let saveSettings: Function; | ||||
| 
 | ||||
| 	// Addons | ||||
|  | @ -188,7 +190,7 @@ | |||
| 					<select | ||||
| 						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | ||||
| 						bind:value={titleAutoGenerateModel} | ||||
| 						placeholder="Select a model" | ||||
| 						placeholder={$i18n.t('ModelSelectorPlaceholder')} | ||||
| 					> | ||||
| 						<option value="" selected>Current Model</option> | ||||
| 						{#each $models as model} | ||||
|  |  | |||
|  | @ -6,9 +6,11 @@ | |||
| 	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; | ||||
| 	import { WEBUI_NAME, models, user } from '$lib/stores'; | ||||
| 	import { splitStream } from '$lib/utils'; | ||||
| 	import { onMount } from 'svelte'; | ||||
| 	import { onMount, getContext } from 'svelte'; | ||||
| 	import { addLiteLLMModel, deleteLiteLLMModel, getLiteLLMModelInfo } from '$lib/apis/litellm'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	export let getModels: Function; | ||||
| 
 | ||||
| 	let showLiteLLM = false; | ||||
|  | @ -465,10 +467,10 @@ | |||
| 							<select | ||||
| 								class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" | ||||
| 								bind:value={deleteModelTag} | ||||
| 								placeholder="Select a model" | ||||
| 								placeholder={$i18n.t('ModelSelectorPlaceholder')} | ||||
| 							> | ||||
| 								{#if !deleteModelTag} | ||||
| 									<option value="" disabled selected>Select a model</option> | ||||
| 									<option value="" disabled selected>{$i18n.t('ModelSelectorPlaceholder')}</option> | ||||
| 								{/if} | ||||
| 								{#each $models.filter((m) => m.size != null) as model} | ||||
| 									<option value={model.name} class="bg-gray-100 dark:bg-gray-700" | ||||
|  | @ -805,10 +807,11 @@ | |||
| 								<select | ||||
| 									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" | ||||
| 									bind:value={deleteLiteLLMModelId} | ||||
| 									placeholder="Select a model" | ||||
| 									placeholder={$i18n.t('ModelSelectorPlaceholder')} | ||||
| 								> | ||||
| 									{#if !deleteLiteLLMModelId} | ||||
| 										<option value="" disabled selected>Select a model</option> | ||||
| 										<option value="" disabled selected>{$i18n.t('ModelSelectorPlaceholder')}</option | ||||
| 										> | ||||
| 									{/if} | ||||
| 									{#each liteLLMModelInfo as model} | ||||
| 										<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700" | ||||
|  |  | |||
|  | @ -8,6 +8,10 @@ | |||
| 	import { page } from '$app/stores'; | ||||
| 	import { user, chats, settings, showSettings, chatId, tags } from '$lib/stores'; | ||||
| 	import { onMount } from 'svelte'; | ||||
| 	import { getContext } from 'svelte'; | ||||
| 
 | ||||
| 	const i18n = getContext('i18n'); | ||||
| 
 | ||||
| 	import { | ||||
| 		deleteChatById, | ||||
| 		getChatList, | ||||
|  | @ -124,7 +128,7 @@ | |||
| 						/> | ||||
| 					</div> | ||||
| 
 | ||||
| 					<div class=" self-center font-medium text-sm">New Chat</div> | ||||
| 					<div class=" self-center font-medium text-sm">{$i18n.t('NewChat')}</div> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<div class="self-center"> | ||||
|  | @ -169,7 +173,7 @@ | |||
| 					</div> | ||||
| 
 | ||||
| 					<div class="flex self-center"> | ||||
| 						<div class=" self-center font-medium text-sm">Modelfiles</div> | ||||
| 						<div class=" self-center font-medium text-sm">{$i18n.t('Modelfiles')}</div> | ||||
| 					</div> | ||||
| 				</a> | ||||
| 			</div> | ||||
|  |  | |||
							
								
								
									
										30
									
								
								src/lib/i18n/index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/lib/i18n/index.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| import i18next from 'i18next'; | ||||
| import resourcesToBackend from 'i18next-resources-to-backend'; | ||||
| import LanguageDetector from 'i18next-browser-languagedetector'; | ||||
| import { createI18nStore, isLoading as isLoadingStore } from './store'; | ||||
| 
 | ||||
| i18next | ||||
| 	.use( | ||||
| 		resourcesToBackend((language, namespace) => import(`./locales/${language}/${namespace}.json`)) | ||||
| 	) | ||||
| 	.use(LanguageDetector) | ||||
| 	.init({ | ||||
| 		debug: true, | ||||
| 		detection: { | ||||
| 			order: ['querystring', 'localStorage', 'navigator'], | ||||
| 			caches: ['localStorage'], | ||||
| 			lookupQuerystring: 'lang', | ||||
| 			lookupLocalStorage: 'locale' | ||||
| 		}, | ||||
| 		fallbackLng: 'en', | ||||
| 		ns: 'common', | ||||
| 		// backend: {
 | ||||
| 		// 	loadPath: '/locales/{{lng}}/{{ns}}.json'
 | ||||
| 		// }
 | ||||
| 		interpolation: { | ||||
| 			escapeValue: false // not needed for svelte as it escapes by default
 | ||||
| 		} | ||||
| 	}); | ||||
| const i18n = createI18nStore(i18next); | ||||
| export default i18n; | ||||
| export const isLoading = isLoadingStore; | ||||
							
								
								
									
										10
									
								
								src/lib/i18n/locales/de/common.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/lib/i18n/locales/de/common.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| { | ||||
| 	"NewChat": "Neuer Chat", | ||||
| 	"Modelfiles": "Modelfiles", | ||||
| 	"GreetingPlaceholder": "Wie kann ich dir heute behilflich sein?", | ||||
| 	"Hello": "Hallo, {{name}}", | ||||
| 	"ChatInputPlaceholderListening": "nimmt auf...", | ||||
| 	"ChatInputPlaceholder": "Sende eine Nachricht", | ||||
| 	"ModelSelectorPlaceholder": "Wähle ein Modell", | ||||
| 	"SetAsDefault": "Als Standard festlegen" | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/lib/i18n/locales/en/common.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/lib/i18n/locales/en/common.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| { | ||||
| 	"NewChat": "New Chat", | ||||
| 	"Modelfiles": "Modelfiles", | ||||
| 	"GreetingPlaceholder": "How can I help you today?", | ||||
| 	"Hello": "Hello, {{name}}", | ||||
| 	"ChatInputPlaceholderListening": "Listening...", | ||||
| 	"ChatInputPlaceholder": "Send a Message", | ||||
| 	"ModelSelectorPlaceholder": "Select a model", | ||||
| 	"SetAsDefault": "Set as default" | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/lib/i18n/locales/fa/common.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/lib/i18n/locales/fa/common.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| { | ||||
| 	"NewChat": "چت جدید", | ||||
| 	"Modelfiles": "فایلهای مدل", | ||||
| 	"GreetingPlaceholder": "امروز چطور می توانم کمک تان کنم؟", | ||||
| 	"Hello": "سلام، {{name}}", | ||||
| 	"ChatInputPlaceholderListening": "در حال گوش دادن...", | ||||
| 	"ChatInputPlaceholder": "یک پیام ارسال کنید", | ||||
| 	"ModelSelectorPlaceholder": "یک مدل انتخاب کنید", | ||||
| 	"SetAsDefault": "تنظیم به عنوان پیشفرض" | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/lib/i18n/locales/fr/common.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/lib/i18n/locales/fr/common.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| { | ||||
| 	"NewChat": "New Chat", | ||||
| 	"Modelfiles": "Modelfiles", | ||||
| 	"GreetingPlaceholder": "How can I help you today?", | ||||
| 	"Hello": "Hello, {{name}}", | ||||
| 	"ChatInputPlaceholderListening": "Listening...", | ||||
| 	"ChatInputPlaceholder": "Send a Message", | ||||
| 	"ModelSelectorPlaceholder": "Select a model", | ||||
| 	"SetAsDefault": "Set as default" | ||||
| } | ||||
							
								
								
									
										34
									
								
								src/lib/i18n/store.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/lib/i18n/store.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | |||
| import type { i18n } from 'i18next'; | ||||
| import { writable } from 'svelte/store'; | ||||
| 
 | ||||
| export const createI18nStore = (i18n: i18n) => { | ||||
| 	const i18nWritable = writable(i18n); | ||||
| 
 | ||||
| 	i18n.on('initialized', () => { | ||||
| 		i18nWritable.set(i18n); | ||||
| 	}); | ||||
| 	i18n.on('loaded', () => { | ||||
| 		i18nWritable.set(i18n); | ||||
| 	}); | ||||
| 	i18n.on('added', () => i18nWritable.set(i18n)); | ||||
| 	i18n.on('languageChanged', () => { | ||||
| 		i18nWritable.set(i18n); | ||||
| 	}); | ||||
| 	return i18nWritable; | ||||
| }; | ||||
| 
 | ||||
| export const isLoading = (i18n: i18n) => { | ||||
| 	const isLoading = writable(false); | ||||
| 
 | ||||
| 	// if loaded resources are empty || {}, set loading to true
 | ||||
| 	i18n.on('loaded', (resources) => { | ||||
| 		Object.keys(resources).length !== 0 && isLoading.set(false); | ||||
| 	}); | ||||
| 
 | ||||
| 	// if resources failed loading, set loading to true
 | ||||
| 	i18n.on('failedLoading', () => { | ||||
| 		isLoading.set(true); | ||||
| 	}); | ||||
| 
 | ||||
| 	return isLoading; | ||||
| }; | ||||
|  | @ -1,5 +1,5 @@ | |||
| <script> | ||||
| 	import { onMount, tick } from 'svelte'; | ||||
| 	import { onMount, tick, setContext } from 'svelte'; | ||||
| 	import { config, user, theme, WEBUI_NAME } from '$lib/stores'; | ||||
| 	import { goto } from '$app/navigation'; | ||||
| 	import { Toaster, toast } from 'svelte-sonner'; | ||||
|  | @ -11,6 +11,9 @@ | |||
| 	import '../tailwind.css'; | ||||
| 	import 'tippy.js/dist/tippy.css'; | ||||
| 	import { WEBUI_BASE_URL } from '$lib/constants'; | ||||
| 	import i18n from '$lib/i18n'; | ||||
| 
 | ||||
| 	setContext('i18n', i18n); | ||||
| 
 | ||||
| 	let loaded = false; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue