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", | 		"dayjs": "^1.11.10", | ||||||
| 		"file-saver": "^2.0.5", | 		"file-saver": "^2.0.5", | ||||||
| 		"highlight.js": "^11.9.0", | 		"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", | 		"idb": "^7.1.1", | ||||||
| 		"js-sha256": "^0.10.1", | 		"js-sha256": "^0.10.1", | ||||||
| 		"katex": "^0.16.9", | 		"katex": "^0.16.9", | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { toast } from 'svelte-sonner'; | 	import { toast } from 'svelte-sonner'; | ||||||
| 	import { onMount, tick } from 'svelte'; | 	import { onMount, tick, getContext } from 'svelte'; | ||||||
| 	import { settings } from '$lib/stores'; | 	import { settings } from '$lib/stores'; | ||||||
| 	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils'; | 	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils'; | ||||||
| 
 | 
 | ||||||
|  | @ -14,6 +14,8 @@ | ||||||
| 	import { transcribeAudio } from '$lib/apis/audio'; | 	import { transcribeAudio } from '$lib/apis/audio'; | ||||||
| 	import Tooltip from '../common/Tooltip.svelte'; | 	import Tooltip from '../common/Tooltip.svelte'; | ||||||
| 
 | 
 | ||||||
|  | 	const i18n = getContext('i18n'); | ||||||
|  | 
 | ||||||
| 	export let submitPrompt: Function; | 	export let submitPrompt: Function; | ||||||
| 	export let stopResponse: Function; | 	export let stopResponse: Function; | ||||||
| 
 | 
 | ||||||
|  | @ -669,8 +671,8 @@ | ||||||
| 							placeholder={chatInputPlaceholder !== '' | 							placeholder={chatInputPlaceholder !== '' | ||||||
| 								? chatInputPlaceholder | 								? chatInputPlaceholder | ||||||
| 								: isRecording | 								: isRecording | ||||||
| 								? 'Listening...' | 								? $i18n.t('ChatInputPlaceholderListening') | ||||||
| 								: 'Send a message'} | 								: $i18n.t('ChatInputPlaceholder')} | ||||||
| 							bind:value={prompt} | 							bind:value={prompt} | ||||||
| 							on:keypress={(e) => { | 							on:keypress={(e) => { | ||||||
| 								if (e.keyCode == 13 && !e.shiftKey) { | 								if (e.keyCode == 13 && !e.shiftKey) { | ||||||
|  |  | ||||||
|  | @ -2,9 +2,11 @@ | ||||||
| 	import { generatePrompt } from '$lib/apis/ollama'; | 	import { generatePrompt } from '$lib/apis/ollama'; | ||||||
| 	import { models } from '$lib/stores'; | 	import { models } from '$lib/stores'; | ||||||
| 	import { splitStream } from '$lib/utils'; | 	import { splitStream } from '$lib/utils'; | ||||||
| 	import { tick } from 'svelte'; | 	import { tick, getContext } from 'svelte'; | ||||||
| 	import { toast } from 'svelte-sonner'; | 	import { toast } from 'svelte-sonner'; | ||||||
| 
 | 
 | ||||||
|  | 	const i18n = getContext('i18n'); | ||||||
|  | 
 | ||||||
| 	export let prompt = ''; | 	export let prompt = ''; | ||||||
| 	export let user = null; | 	export let user = null; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,9 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { WEBUI_BASE_URL } from '$lib/constants'; | 	import { WEBUI_BASE_URL } from '$lib/constants'; | ||||||
| 	import { user } from '$lib/stores'; | 	import { user } from '$lib/stores'; | ||||||
| 	import { onMount } from 'svelte'; | 	import { onMount, getContext } from 'svelte'; | ||||||
|  | 
 | ||||||
|  | 	const i18n = getContext('i18n'); | ||||||
| 
 | 
 | ||||||
| 	export let models = []; | 	export let models = []; | ||||||
| 	export let modelfiles = []; | 	export let modelfiles = []; | ||||||
|  | @ -64,9 +66,9 @@ | ||||||
| 					</div> | 					</div> | ||||||
| 				{/if} | 				{/if} | ||||||
| 			{:else} | 			{: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} | 			{/if} | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
|  |  | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { setDefaultModels } from '$lib/apis/configs'; | 	import { setDefaultModels } from '$lib/apis/configs'; | ||||||
| 	import { models, showSettings, settings, user } from '$lib/stores'; | 	import { models, showSettings, settings, user } from '$lib/stores'; | ||||||
| 	import { onMount, tick } from 'svelte'; | 	import { onMount, tick, getContext } from 'svelte'; | ||||||
| 	import { toast } from 'svelte-sonner'; | 	import { toast } from 'svelte-sonner'; | ||||||
| 
 | 
 | ||||||
|  | 	const i18n = getContext('i18n'); | ||||||
|  | 
 | ||||||
| 	export let selectedModels = ['']; | 	export let selectedModels = ['']; | ||||||
| 	export let disabled = false; | 	export let disabled = false; | ||||||
| 
 | 
 | ||||||
|  | @ -39,7 +41,9 @@ | ||||||
| 				bind:value={selectedModel} | 				bind:value={selectedModel} | ||||||
| 				{disabled} | 				{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} | 				{#each $models as model} | ||||||
| 					{#if model.name === 'hr'} | 					{#if model.name === 'hr'} | ||||||
|  | @ -133,5 +137,5 @@ | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <div class="text-left mt-1.5 text-xs text-gray-500"> | <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> | </div> | ||||||
|  |  | ||||||
|  | @ -1,10 +1,12 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { toast } from 'svelte-sonner'; | 	import { toast } from 'svelte-sonner'; | ||||||
| 	import { createEventDispatcher, onMount } from 'svelte'; | 	import { createEventDispatcher, onMount, getContext } from 'svelte'; | ||||||
| 	const dispatch = createEventDispatcher(); | 	const dispatch = createEventDispatcher(); | ||||||
| 
 | 
 | ||||||
| 	import { models, user } from '$lib/stores'; | 	import { models, user } from '$lib/stores'; | ||||||
| 
 | 
 | ||||||
|  | 	const i18n = getContext('i18n'); | ||||||
|  | 
 | ||||||
| 	import AdvancedParams from './Advanced/AdvancedParams.svelte'; | 	import AdvancedParams from './Advanced/AdvancedParams.svelte'; | ||||||
| 
 | 
 | ||||||
| 	export let saveSettings: Function; | 	export let saveSettings: Function; | ||||||
|  | @ -13,6 +15,9 @@ | ||||||
| 	// General | 	// General | ||||||
| 	let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light']; | 	let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light']; | ||||||
| 	let theme = 'dark'; | 	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 notificationEnabled = false; | ||||||
| 	let system = ''; | 	let system = ''; | ||||||
| 
 | 
 | ||||||
|  | @ -149,6 +154,25 @@ | ||||||
| 				</div> | 				</div> | ||||||
| 			</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> | ||||||
| 				<div class=" py-0.5 flex w-full justify-between"> | 				<div class=" py-0.5 flex w-full justify-between"> | ||||||
| 					<div class=" self-center text-xs font-medium">Notification</div> | 					<div class=" self-center text-xs font-medium">Notification</div> | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { toast } from 'svelte-sonner'; | 	import { toast } from 'svelte-sonner'; | ||||||
| 
 | 
 | ||||||
| 	import { createEventDispatcher, onMount } from 'svelte'; | 	import { createEventDispatcher, onMount, getContext } from 'svelte'; | ||||||
| 	import { config, user } from '$lib/stores'; | 	import { config, user } from '$lib/stores'; | ||||||
| 	import { | 	import { | ||||||
| 		getAUTOMATIC1111Url, | 		getAUTOMATIC1111Url, | ||||||
|  | @ -19,6 +19,8 @@ | ||||||
| 	import { getBackendConfig } from '$lib/apis'; | 	import { getBackendConfig } from '$lib/apis'; | ||||||
| 	const dispatch = createEventDispatcher(); | 	const dispatch = createEventDispatcher(); | ||||||
| 
 | 
 | ||||||
|  | 	const i18n = getContext('i18n'); | ||||||
|  | 
 | ||||||
| 	export let saveSettings: Function; | 	export let saveSettings: Function; | ||||||
| 
 | 
 | ||||||
| 	let loading = false; | 	let loading = false; | ||||||
|  | @ -193,10 +195,10 @@ | ||||||
| 						<select | 						<select | ||||||
| 							class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | 							class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | ||||||
| 							bind:value={selectedModel} | 							bind:value={selectedModel} | ||||||
| 							placeholder="Select a model" | 							placeholder={$i18n.t('ModelSelectorPlaceholder')} | ||||||
| 						> | 						> | ||||||
| 							{#if !selectedModel} | 							{#if !selectedModel} | ||||||
| 								<option value="" disabled selected>Select a model</option> | 								<option value="" disabled selected>{$i18n.t('ModelSelectorPlaceholder')}</option> | ||||||
| 							{/if} | 							{/if} | ||||||
| 							{#each models ?? [] as model} | 							{#each models ?? [] as model} | ||||||
| 								<option value={model.title} class="bg-gray-100 dark:bg-gray-700" | 								<option value={model.title} class="bg-gray-100 dark:bg-gray-700" | ||||||
|  |  | ||||||
|  | @ -2,10 +2,12 @@ | ||||||
| 	import { getBackendConfig } from '$lib/apis'; | 	import { getBackendConfig } from '$lib/apis'; | ||||||
| 	import { setDefaultPromptSuggestions } from '$lib/apis/configs'; | 	import { setDefaultPromptSuggestions } from '$lib/apis/configs'; | ||||||
| 	import { config, models, user } from '$lib/stores'; | 	import { config, models, user } from '$lib/stores'; | ||||||
| 	import { createEventDispatcher, onMount } from 'svelte'; | 	import { createEventDispatcher, onMount, getContext } from 'svelte'; | ||||||
| 	import { toast } from 'svelte-sonner'; | 	import { toast } from 'svelte-sonner'; | ||||||
| 	const dispatch = createEventDispatcher(); | 	const dispatch = createEventDispatcher(); | ||||||
| 
 | 
 | ||||||
|  | 	const i18n = getContext('i18n'); | ||||||
|  | 
 | ||||||
| 	export let saveSettings: Function; | 	export let saveSettings: Function; | ||||||
| 
 | 
 | ||||||
| 	// Addons | 	// Addons | ||||||
|  | @ -188,7 +190,7 @@ | ||||||
| 					<select | 					<select | ||||||
| 						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | 						class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" | ||||||
| 						bind:value={titleAutoGenerateModel} | 						bind:value={titleAutoGenerateModel} | ||||||
| 						placeholder="Select a model" | 						placeholder={$i18n.t('ModelSelectorPlaceholder')} | ||||||
| 					> | 					> | ||||||
| 						<option value="" selected>Current Model</option> | 						<option value="" selected>Current Model</option> | ||||||
| 						{#each $models as model} | 						{#each $models as model} | ||||||
|  |  | ||||||
|  | @ -6,9 +6,11 @@ | ||||||
| 	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; | 	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; | ||||||
| 	import { WEBUI_NAME, models, user } from '$lib/stores'; | 	import { WEBUI_NAME, models, user } from '$lib/stores'; | ||||||
| 	import { splitStream } from '$lib/utils'; | 	import { splitStream } from '$lib/utils'; | ||||||
| 	import { onMount } from 'svelte'; | 	import { onMount, getContext } from 'svelte'; | ||||||
| 	import { addLiteLLMModel, deleteLiteLLMModel, getLiteLLMModelInfo } from '$lib/apis/litellm'; | 	import { addLiteLLMModel, deleteLiteLLMModel, getLiteLLMModelInfo } from '$lib/apis/litellm'; | ||||||
| 
 | 
 | ||||||
|  | 	const i18n = getContext('i18n'); | ||||||
|  | 
 | ||||||
| 	export let getModels: Function; | 	export let getModels: Function; | ||||||
| 
 | 
 | ||||||
| 	let showLiteLLM = false; | 	let showLiteLLM = false; | ||||||
|  | @ -465,10 +467,10 @@ | ||||||
| 							<select | 							<select | ||||||
| 								class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" | 								class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" | ||||||
| 								bind:value={deleteModelTag} | 								bind:value={deleteModelTag} | ||||||
| 								placeholder="Select a model" | 								placeholder={$i18n.t('ModelSelectorPlaceholder')} | ||||||
| 							> | 							> | ||||||
| 								{#if !deleteModelTag} | 								{#if !deleteModelTag} | ||||||
| 									<option value="" disabled selected>Select a model</option> | 									<option value="" disabled selected>{$i18n.t('ModelSelectorPlaceholder')}</option> | ||||||
| 								{/if} | 								{/if} | ||||||
| 								{#each $models.filter((m) => m.size != null) as model} | 								{#each $models.filter((m) => m.size != null) as model} | ||||||
| 									<option value={model.name} class="bg-gray-100 dark:bg-gray-700" | 									<option value={model.name} class="bg-gray-100 dark:bg-gray-700" | ||||||
|  | @ -805,10 +807,11 @@ | ||||||
| 								<select | 								<select | ||||||
| 									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" | 									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" | ||||||
| 									bind:value={deleteLiteLLMModelId} | 									bind:value={deleteLiteLLMModelId} | ||||||
| 									placeholder="Select a model" | 									placeholder={$i18n.t('ModelSelectorPlaceholder')} | ||||||
| 								> | 								> | ||||||
| 									{#if !deleteLiteLLMModelId} | 									{#if !deleteLiteLLMModelId} | ||||||
| 										<option value="" disabled selected>Select a model</option> | 										<option value="" disabled selected>{$i18n.t('ModelSelectorPlaceholder')}</option | ||||||
|  | 										> | ||||||
| 									{/if} | 									{/if} | ||||||
| 									{#each liteLLMModelInfo as model} | 									{#each liteLLMModelInfo as model} | ||||||
| 										<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700" | 										<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700" | ||||||
|  |  | ||||||
|  | @ -8,6 +8,10 @@ | ||||||
| 	import { page } from '$app/stores'; | 	import { page } from '$app/stores'; | ||||||
| 	import { user, chats, settings, showSettings, chatId, tags } from '$lib/stores'; | 	import { user, chats, settings, showSettings, chatId, tags } from '$lib/stores'; | ||||||
| 	import { onMount } from 'svelte'; | 	import { onMount } from 'svelte'; | ||||||
|  | 	import { getContext } from 'svelte'; | ||||||
|  | 
 | ||||||
|  | 	const i18n = getContext('i18n'); | ||||||
|  | 
 | ||||||
| 	import { | 	import { | ||||||
| 		deleteChatById, | 		deleteChatById, | ||||||
| 		getChatList, | 		getChatList, | ||||||
|  | @ -124,7 +128,7 @@ | ||||||
| 						/> | 						/> | ||||||
| 					</div> | 					</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> | ||||||
| 
 | 
 | ||||||
| 				<div class="self-center"> | 				<div class="self-center"> | ||||||
|  | @ -169,7 +173,7 @@ | ||||||
| 					</div> | 					</div> | ||||||
| 
 | 
 | ||||||
| 					<div class="flex self-center"> | 					<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> | 					</div> | ||||||
| 				</a> | 				</a> | ||||||
| 			</div> | 			</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> | <script> | ||||||
| 	import { onMount, tick } from 'svelte'; | 	import { onMount, tick, setContext } from 'svelte'; | ||||||
| 	import { config, user, theme, WEBUI_NAME } from '$lib/stores'; | 	import { config, user, theme, WEBUI_NAME } from '$lib/stores'; | ||||||
| 	import { goto } from '$app/navigation'; | 	import { goto } from '$app/navigation'; | ||||||
| 	import { Toaster, toast } from 'svelte-sonner'; | 	import { Toaster, toast } from 'svelte-sonner'; | ||||||
|  | @ -11,6 +11,9 @@ | ||||||
| 	import '../tailwind.css'; | 	import '../tailwind.css'; | ||||||
| 	import 'tippy.js/dist/tippy.css'; | 	import 'tippy.js/dist/tippy.css'; | ||||||
| 	import { WEBUI_BASE_URL } from '$lib/constants'; | 	import { WEBUI_BASE_URL } from '$lib/constants'; | ||||||
|  | 	import i18n from '$lib/i18n'; | ||||||
|  | 
 | ||||||
|  | 	setContext('i18n', i18n); | ||||||
| 
 | 
 | ||||||
| 	let loaded = false; | 	let loaded = false; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue