Explorar el Código

feat: Migrate hardcoded strings to i18n calls

Ased Mammad hace 1 año
padre
commit
3c471ee2ca
Se han modificado 46 ficheros con 492 adiciones y 381 borrados
  1. 5 3
      src/lib/components/ChangelogModal.svelte
  2. 6 5
      src/lib/components/admin/EditUserModal.svelte
  3. 6 4
      src/lib/components/admin/Settings/Database.svelte
  4. 12 10
      src/lib/components/admin/Settings/General.svelte
  5. 7 5
      src/lib/components/admin/Settings/Users.svelte
  6. 7 4
      src/lib/components/admin/SettingsModal.svelte
  7. 17 11
      src/lib/components/chat/MessageInput.svelte
  8. 8 4
      src/lib/components/chat/MessageInput/Documents.svelte
  9. 2 2
      src/lib/components/chat/MessageInput/Models.svelte
  10. 7 4
      src/lib/components/chat/MessageInput/PromptCommands.svelte
  11. 4 2
      src/lib/components/chat/Messages.svelte
  12. 5 3
      src/lib/components/chat/Messages/ResponseMessage.svelte
  13. 7 5
      src/lib/components/chat/Messages/UserMessage.svelte
  14. 2 2
      src/lib/components/chat/ModelSelector.svelte
  15. 4 2
      src/lib/components/chat/Settings/About.svelte
  16. 6 4
      src/lib/components/chat/Settings/Account.svelte
  17. 8 5
      src/lib/components/chat/Settings/Account/UpdatePassword.svelte
  18. 11 9
      src/lib/components/chat/Settings/Advanced.svelte
  19. 39 35
      src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
  20. 24 20
      src/lib/components/chat/Settings/Audio.svelte
  21. 14 10
      src/lib/components/chat/Settings/Chats.svelte
  22. 8 6
      src/lib/components/chat/Settings/Connections.svelte
  23. 18 18
      src/lib/components/chat/Settings/General.svelte
  24. 11 9
      src/lib/components/chat/Settings/Images.svelte
  25. 15 15
      src/lib/components/chat/Settings/Interface.svelte
  26. 25 26
      src/lib/components/chat/Settings/Models.svelte
  27. 13 10
      src/lib/components/chat/SettingsModal.svelte
  28. 6 3
      src/lib/components/chat/ShareChatModal.svelte
  29. 12 9
      src/lib/components/chat/ShortcutsModal.svelte
  30. 5 3
      src/lib/components/documents/AddDocModal.svelte
  31. 7 5
      src/lib/components/documents/EditDocModal.svelte
  32. 13 9
      src/lib/components/documents/Settings/General.svelte
  33. 5 2
      src/lib/components/documents/SettingsModal.svelte
  34. 4 1
      src/lib/components/layout/Navbar.svelte
  35. 7 8
      src/lib/components/layout/Sidebar.svelte
  36. 11 7
      src/routes/(app)/+layout.svelte
  37. 9 7
      src/routes/(app)/admin/+page.svelte
  38. 14 9
      src/routes/(app)/documents/+page.svelte
  39. 14 12
      src/routes/(app)/modelfiles/+page.svelte
  40. 32 26
      src/routes/(app)/modelfiles/create/+page.svelte
  41. 14 12
      src/routes/(app)/modelfiles/edit/+page.svelte
  42. 10 8
      src/routes/(app)/prompts/+page.svelte
  43. 9 7
      src/routes/(app)/prompts/create/+page.svelte
  44. 9 7
      src/routes/(app)/prompts/edit/+page.svelte
  45. 7 5
      src/routes/auth/+page.svelte
  46. 13 8
      src/routes/error/+page.svelte

+ 5 - 3
src/lib/components/ChangelogModal.svelte

@@ -1,5 +1,5 @@
 <script lang="ts">
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 	import { Confetti } from 'svelte-confetti';
 
 	import { WEBUI_NAME, config } from '$lib/stores';
@@ -9,6 +9,8 @@
 
 	import Modal from './common/Modal.svelte';
 
+	const i18n = getContext('i18n');
+
 	export let show = false;
 
 	let changelog = null;
@@ -45,7 +47,7 @@
 			</button>
 		</div>
 		<div class="flex items-center mt-1">
-			<div class="text-sm dark:text-gray-200">Release Notes</div>
+			<div class="text-sm dark:text-gray-200">{$i18n.t('Release Notes')}</div>
 			<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" />
 			<div class="text-sm dark:text-gray-200">
 				v{WEBUI_VERSION}
@@ -108,7 +110,7 @@
 				}}
 				class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
 			>
-				<span class="relative">Okay, Let's Go!</span>
+				<span class="relative">{$i18n.t("Okay, Let's Go!")}</span>
 			</button>
 		</div>
 	</div>

+ 6 - 5
src/lib/components/admin/EditUserModal.svelte

@@ -2,11 +2,12 @@
 	import { toast } from 'svelte-sonner';
 	import dayjs from 'dayjs';
 	import { createEventDispatcher } from 'svelte';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 
 	import { updateUserById } from '$lib/apis/users';
 	import Modal from '../common/Modal.svelte';
 
+	const i18n = getContext('i18n');
 	const dispatch = createEventDispatcher();
 
 	export let show = false;
@@ -42,7 +43,7 @@
 <Modal size="sm" bind:show>
 	<div>
 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
-			<div class=" text-lg font-medium self-center">Edit User</div>
+			<div class=" text-lg font-medium self-center">{$i18n.t('Edit User')}</div>
 			<button
 				class="self-center"
 				on:click={() => {
@@ -93,7 +94,7 @@
 
 					<div class=" flex flex-col space-y-1.5">
 						<div class="flex flex-col w-full">
-							<div class=" mb-1 text-xs text-gray-500">Email</div>
+							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Email')}</div>
 
 							<div class="flex-1">
 								<input
@@ -108,7 +109,7 @@
 						</div>
 
 						<div class="flex flex-col w-full">
-							<div class=" mb-1 text-xs text-gray-500">Name</div>
+							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
 
 							<div class="flex-1">
 								<input
@@ -122,7 +123,7 @@
 						</div>
 
 						<div class="flex flex-col w-full">
-							<div class=" mb-1 text-xs text-gray-500">New Password</div>
+							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('New Password')}</div>
 
 							<div class="flex-1">
 								<input

+ 6 - 4
src/lib/components/admin/Settings/Database.svelte

@@ -1,6 +1,8 @@
 <script lang="ts">
 	import { downloadDatabase } from '$lib/apis/utils';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
 
 	export let saveHandler: Function;
 
@@ -17,10 +19,10 @@
 >
 	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 		<div>
-			<div class=" mb-2 text-sm font-medium">Database</div>
+			<div class=" mb-2 text-sm font-medium">{$i18n.t('Database')}</div>
 
 			<div class="  flex w-full justify-between">
-				<!-- <div class=" self-center text-xs font-medium">Allow Chat Deletion</div> -->
+				<!-- <div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div> -->
 
 				<button
 					class=" flex rounded-md py-1.5 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
@@ -46,7 +48,7 @@
 							/>
 						</svg>
 					</div>
-					<div class=" self-center text-sm font-medium">Download Database</div>
+					<div class=" self-center text-sm font-medium">{$i18n.t('Download Database')}</div>
 				</button>
 			</div>
 		</div>

+ 12 - 10
src/lib/components/admin/Settings/General.svelte

@@ -7,7 +7,9 @@
 		updateDefaultUserRole,
 		updateJWTExpiresDuration
 	} from '$lib/apis/auths';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
 
 	export let saveHandler: Function;
 	let signUpEnabled = true;
@@ -43,10 +45,10 @@
 >
 	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 		<div>
-			<div class=" mb-2 text-sm font-medium">General Settings</div>
+			<div class=" mb-2 text-sm font-medium">{$i18n.t('General Settings')}</div>
 
 			<div class="  flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Enable New Sign Ups</div>
+				<div class=" self-center text-xs font-medium">{$i18n.t('Enable New Sign Ups')}</div>
 
 				<button
 					class="p-1 px-3 text-xs flex rounded transition"
@@ -66,7 +68,7 @@
 								d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
 							/>
 						</svg>
-						<span class="ml-2 self-center">Enabled</span>
+						<span class="ml-2 self-center">{$i18n.t('Enabled')}</span>
 					{:else}
 						<svg
 							xmlns="http://www.w3.org/2000/svg"
@@ -81,13 +83,13 @@
 							/>
 						</svg>
 
-						<span class="ml-2 self-center">Disabled</span>
+						<span class="ml-2 self-center">{$i18n.t('Disabled')}</span>
 					{/if}
 				</button>
 			</div>
 
 			<div class=" flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Default User Role</div>
+				<div class=" self-center text-xs font-medium">{$i18n.t('Default User Role')}</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"
@@ -97,9 +99,9 @@
 							updateDefaultUserRoleHandler(e.target.value);
 						}}
 					>
-						<option value="pending">Pending</option>
-						<option value="user">User</option>
-						<option value="admin">Admin</option>
+						<option value="pending">{$i18n.t('Pending')}</option>
+						<option value="user">{$i18n.t('User')}</option>
+						<option value="admin">{$i18n.t('Admin')}</option>
 					</select>
 				</div>
 			</div>
@@ -108,7 +110,7 @@
 
 			<div class=" w-full justify-between">
 				<div class="flex w-full justify-between">
-					<div class=" self-center text-xs font-medium">JWT Expiration</div>
+					<div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</div>
 				</div>
 
 				<div class="flex mt-2 space-x-2">

+ 7 - 5
src/lib/components/admin/Settings/Users.svelte

@@ -1,7 +1,9 @@
 <script lang="ts">
 	import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
 	import { getUserPermissions, updateUserPermissions } from '$lib/apis/users';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
 
 	export let saveHandler: Function;
 
@@ -26,10 +28,10 @@
 >
 	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 		<div>
-			<div class=" mb-2 text-sm font-medium">User Permissions</div>
+			<div class=" mb-2 text-sm font-medium">{$i18n.t('User Permissions')}</div>
 
 			<div class="  flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Allow Chat Deletion</div>
+				<div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div>
 
 				<button
 					class="p-1 px-3 text-xs flex rounded transition"
@@ -49,7 +51,7 @@
 								d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
 							/>
 						</svg>
-						<span class="ml-2 self-center">Allow</span>
+						<span class="ml-2 self-center">{$i18n.t('Allow')}</span>
 					{:else}
 						<svg
 							xmlns="http://www.w3.org/2000/svg"
@@ -64,7 +66,7 @@
 							/>
 						</svg>
 
-						<span class="ml-2 self-center">Don't Allow</span>
+						<span class="ml-2 self-center">{$i18n.t("Don't Allow")}</span>
 					{/if}
 				</button>
 			</div>

+ 7 - 4
src/lib/components/admin/SettingsModal.svelte

@@ -1,10 +1,13 @@
 <script>
+	import { getContext } from 'svelte';
 	import Modal from '../common/Modal.svelte';
 	import Database from './Settings/Database.svelte';
 
 	import General from './Settings/General.svelte';
 	import Users from './Settings/Users.svelte';
 
+	const i18n = getContext('i18n');
+
 	export let show = false;
 
 	let selectedTab = 'general';
@@ -13,7 +16,7 @@
 <Modal bind:show>
 	<div>
 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
-			<div class=" text-lg font-medium self-center">Admin Settings</div>
+			<div class=" text-lg font-medium self-center">{$i18n.t('Admin Settings')}</div>
 			<button
 				class="self-center"
 				on:click={() => {
@@ -61,7 +64,7 @@
 							/>
 						</svg>
 					</div>
-					<div class=" self-center">General</div>
+					<div class=" self-center">{$i18n.t('General')}</div>
 				</button>
 
 				<button
@@ -85,7 +88,7 @@
 							/>
 						</svg>
 					</div>
-					<div class=" self-center">Users</div>
+					<div class=" self-center">{$i18n.t('Users')}</div>
 				</button>
 
 				<button
@@ -113,7 +116,7 @@
 							/>
 						</svg>
 					</div>
-					<div class=" self-center">Database</div>
+					<div class=" self-center">{$i18n.t('Database')}</div>
 				</button>
 			</div>
 			<div class="flex-1 md:min-h-[380px]">

+ 17 - 11
src/lib/components/chat/MessageInput.svelte

@@ -220,7 +220,7 @@
 						isRecording = false;
 					};
 				} else {
-					toast.error('SpeechRecognition API is not supported in this browser.');
+					toast.error($i18n.t('SpeechRecognition API is not supported in this browser.'));
 				}
 			}
 		}
@@ -341,12 +341,15 @@
 						uploadDoc(file);
 					} else {
 						toast.error(
-							`Unknown File Type '${file['type']}', but accepting and treating as plain text`
+							$i18n.t(
+								`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
+								{ file_type: file['type'] }
+							)
 						);
 						uploadDoc(file);
 					}
 				} else {
-					toast.error(`File not found.`);
+					toast.error($i18n.t(`File not found.`));
 				}
 			}
 
@@ -485,13 +488,16 @@
 								filesInputElement.value = '';
 							} else {
 								toast.error(
-									`Unknown File Type '${file['type']}', but accepting and treating as plain text`
+									$i18n.t(
+										`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
+										{ file_type: file['type'] }
+									)
 								);
 								uploadDoc(file);
 								filesInputElement.value = '';
 							}
 						} else {
-							toast.error(`File not found.`);
+							toast.error($i18n.t(`File not found.`));
 						}
 					}}
 				/>
@@ -578,7 +584,7 @@
 													{file.name}
 												</div>
 
-												<div class=" text-gray-500 text-sm">Document</div>
+												<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
 											</div>
 										</div>
 									{:else if file.type === 'collection'}
@@ -606,7 +612,7 @@
 													{file?.title ?? `#${file.name}`}
 												</div>
 
-												<div class=" text-gray-500 text-sm">Collection</div>
+												<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
 											</div>
 										</div>
 									{/if}
@@ -640,7 +646,7 @@
 					<div class=" flex">
 						{#if fileUploadEnabled}
 							<div class=" self-end mb-2 ml-1">
-								<Tooltip content="Upload files">
+								<Tooltip content={$i18n.t('Upload files')}>
 									<button
 										class="bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
 										type="button"
@@ -811,7 +817,7 @@
 
 						<div class="self-end mb-2 flex space-x-1 mr-1">
 							{#if messages.length == 0 || messages.at(-1).done == true}
-								<Tooltip content="Record voice">
+								<Tooltip content={$i18n.t('Record voice')}>
 									{#if speechRecognitionEnabled}
 										<button
 											id="voice-input-button"
@@ -880,7 +886,7 @@
 									{/if}
 								</Tooltip>
 
-								<Tooltip content="Send message">
+								<Tooltip content={$i18n.t('Send message')}>
 									<button
 										class="{prompt !== ''
 											? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
@@ -926,7 +932,7 @@
 				</form>
 
 				<div class="mt-1.5 text-xs text-gray-500 text-center">
-					LLMs can make mistakes. Verify important information.
+					{$i18n.t('LLMs can make mistakes. Verify important information.')}
 				</div>
 			</div>
 		</div>

+ 8 - 4
src/lib/components/chat/MessageInput/Documents.svelte

@@ -3,9 +3,11 @@
 
 	import { documents } from '$lib/stores';
 	import { removeFirstHashWord, isValidHttpUrl } from '$lib/utils';
-	import { tick } from 'svelte';
+	import { tick, getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 
+	const i18n = getContext('i18n');
+
 	export let prompt = '';
 
 	const dispatch = createEventDispatcher();
@@ -117,7 +119,7 @@
 									{doc?.title ?? `#${doc.name}`}
 								</div>
 
-								<div class=" text-xs text-gray-600 line-clamp-1">Collection</div>
+								<div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Collection')}</div>
 							{:else}
 								<div class=" font-medium text-black line-clamp-1">
 									#{doc.name} ({doc.filename})
@@ -140,7 +142,9 @@
 									confirmSelectWeb(url);
 								} else {
 									toast.error(
-										'Oops! Looks like the URL is invalid. Please double-check and try again.'
+										$i18n.t(
+											'Oops! Looks like the URL is invalid. Please double-check and try again.'
+										)
 									);
 								}
 							}}
@@ -149,7 +153,7 @@
 								{prompt.split(' ')?.at(0)?.substring(1)}
 							</div>
 
-							<div class=" text-xs text-gray-600 line-clamp-1">Web</div>
+							<div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Web')}</div>
 						</button>
 					{/if}
 				</div>

+ 2 - 2
src/lib/components/chat/MessageInput/Models.svelte

@@ -43,7 +43,7 @@
 		user = JSON.parse(JSON.stringify(model.name));
 		await tick();
 
-		chatInputPlaceholder = `'${model.name}' is thinking...`;
+		chatInputPlaceholder = $i18n.t('{{modelName}} is thinking...', { modelName: model.name });
 
 		const chatInputElement = document.getElementById('chat-textarea');
 
@@ -111,7 +111,7 @@
 					toast.error(error.error);
 				}
 			} else {
-				toast.error(`Uh-oh! There was an issue connecting to Ollama.`);
+				toast.error($i18n.t('Uh-oh! There was an issue connecting to llama.'));
 			}
 		}
 

+ 7 - 4
src/lib/components/chat/MessageInput/PromptCommands.svelte

@@ -1,9 +1,11 @@
 <script lang="ts">
 	import { prompts } from '$lib/stores';
 	import { findWordIndices } from '$lib/utils';
-	import { tick } from 'svelte';
+	import { tick, getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 
+	const i18n = getContext('i18n');
+
 	export let prompt = '';
 	let selectedCommandIdx = 0;
 	let filteredPromptCommands = [];
@@ -29,7 +31,7 @@
 
 		if (command.content.includes('{{CLIPBOARD}}')) {
 			const clipboardText = await navigator.clipboard.readText().catch((err) => {
-				toast.error('Failed to read clipboard contents');
+				toast.error($i18n.t('Failed to read clipboard contents'));
 				return '{{CLIPBOARD}}';
 			});
 
@@ -113,8 +115,9 @@
 					</div>
 
 					<div class="line-clamp-1">
-						Tip: Update multiple variable slots consecutively by pressing the tab key in the chat
-						input after each replacement.
+						{$i18n.t(
+							'Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.'
+						)}
 					</div>
 				</div>
 			</div>

+ 4 - 2
src/lib/components/chat/Messages.svelte

@@ -2,7 +2,7 @@
 	import { v4 as uuidv4 } from 'uuid';
 
 	import { chats, config, modelfiles, settings, user } from '$lib/stores';
-	import { tick } from 'svelte';
+	import { tick, getContext } from 'svelte';
 
 	import { toast } from 'svelte-sonner';
 	import { getChatList, updateChatById } from '$lib/apis/chats';
@@ -13,6 +13,8 @@
 	import Spinner from '../common/Spinner.svelte';
 	import { imageGenerations } from '$lib/apis/images';
 
+	const i18n = getContext('i18n');
+
 	export let chatId = '';
 	export let sendPrompt: Function;
 	export let continueGeneration: Function;
@@ -67,7 +69,7 @@
 		navigator.clipboard.writeText(text).then(
 			function () {
 				console.log('Async: Copying to clipboard was successful!');
-				toast.success('Copying to clipboard was successful!');
+				toast.success($i18n.t('Copying to clipboard was successful!'));
 			},
 			function (err) {
 				console.error('Async: Could not copy text: ', err);

+ 5 - 3
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -7,7 +7,9 @@
 	import 'katex/dist/katex.min.css';
 
 	import { createEventDispatcher } from 'svelte';
-	import { onMount, tick } from 'svelte';
+	import { onMount, tick, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
 
 	const dispatch = createEventDispatcher();
 
@@ -355,7 +357,7 @@
 											editMessageConfirmHandler();
 										}}
 									>
-										Save
+										{$i18n.t('Save')}
 									</button>
 
 									<button
@@ -364,7 +366,7 @@
 											cancelEditMessage();
 										}}
 									>
-										Cancel
+										{$i18n.t('Cancel')}
 									</button>
 								</div>
 							</div>

+ 7 - 5
src/lib/components/chat/Messages/UserMessage.svelte

@@ -1,11 +1,13 @@
 <script lang="ts">
 	import dayjs from 'dayjs';
 
-	import { tick, createEventDispatcher } from 'svelte';
+	import { tick, createEventDispatcher, getContext } from 'svelte';
 	import Name from './Name.svelte';
 	import ProfileImage from './ProfileImage.svelte';
 	import { modelfiles, settings } from '$lib/stores';
 
+	const i18n = getContext('i18n');
+
 	const dispatch = createEventDispatcher();
 
 	export let user;
@@ -123,7 +125,7 @@
 											{file.name}
 										</div>
 
-										<div class=" text-gray-500 text-sm">Document</div>
+										<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
 									</div>
 								</button>
 							{:else if file.type === 'collection'}
@@ -152,7 +154,7 @@
 											{file?.title ?? `#${file.name}`}
 										</div>
 
-										<div class=" text-gray-500 text-sm">Collection</div>
+										<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
 									</div>
 								</button>
 							{/if}
@@ -179,7 +181,7 @@
 								editMessageConfirmHandler();
 							}}
 						>
-							Save & Submit
+							{$i18n.t('Save & Submit')}
 						</button>
 
 						<button
@@ -188,7 +190,7 @@
 								cancelEditMessage();
 							}}
 						>
-							Cancel
+							{$i18n.t('Cancel')}
 						</button>
 					</div>
 				</div>

+ 2 - 2
src/lib/components/chat/ModelSelector.svelte

@@ -12,7 +12,7 @@
 	const saveDefaultModel = async () => {
 		const hasEmptyModel = selectedModels.filter((it) => it === '');
 		if (hasEmptyModel.length) {
-			toast.error('Choose a model before saving...');
+			toast.error($i18n.t('Choose a model before saving...'));
 			return;
 		}
 		settings.set({ ...$settings, models: selectedModels });
@@ -22,7 +22,7 @@
 			console.log('setting default models globally');
 			await setDefaultModels(localStorage.token, selectedModels.join(','));
 		}
-		toast.success('Default model updated');
+		toast.success($i18n.t('Default model updated'));
 	};
 
 	$: if (selectedModels.length > 0 && $models.length > 0) {

+ 4 - 2
src/lib/components/chat/Settings/About.svelte

@@ -4,7 +4,9 @@
 	import { WEBUI_VERSION } from '$lib/constants';
 	import { WEBUI_NAME, config, showChangelog } from '$lib/stores';
 	import { compareVersion } from '$lib/utils';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
 
 	let ollamaVersion = '';
 
@@ -88,7 +90,7 @@
 			<hr class=" dark:border-gray-700" />
 
 			<div>
-				<div class=" mb-2.5 text-sm font-medium">Ollama Version</div>
+				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Version')}</div>
 				<div class="flex w-full">
 					<div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
 						{ollamaVersion ?? 'N/A'}

+ 6 - 4
src/lib/components/chat/Settings/Account.svelte

@@ -1,6 +1,6 @@
 <script lang="ts">
 	import { toast } from 'svelte-sonner';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 
 	import { user } from '$lib/stores';
 	import { updateUserProfile } from '$lib/apis/auths';
@@ -9,6 +9,8 @@
 	import { getGravatarUrl } from '$lib/apis/utils';
 	import { copyToClipboard } from '$lib/utils';
 
+	const i18n = getContext('i18n');
+
 	export let saveHandler: Function;
 
 	let profileImageUrl = '';
@@ -99,7 +101,7 @@
 			}}
 		/>
 
-		<div class=" mb-2.5 text-sm font-medium">Profile</div>
+		<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Profile')}</div>
 
 		<div class="flex space-x-5">
 			<div class="flex flex-col">
@@ -147,7 +149,7 @@
 
 			<div class="flex-1">
 				<div class="flex flex-col w-full">
-					<div class=" mb-1 text-xs text-gray-500">Name</div>
+					<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
 
 					<div class="flex-1">
 						<input
@@ -168,7 +170,7 @@
 
 		<div class=" w-full justify-between">
 			<div class="flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">JWT Token</div>
+				<div class=" self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
 			</div>
 
 			<div class="flex mt-2">

+ 8 - 5
src/lib/components/chat/Settings/Account/UpdatePassword.svelte

@@ -1,7 +1,10 @@
 <script lang="ts">
+	import { getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 	import { updateUserPassword } from '$lib/apis/auths';
 
+	const i18n = getContext('i18n');
+
 	let show = false;
 	let currentPassword = '';
 	let newPassword = '';
@@ -17,7 +20,7 @@
 			);
 
 			if (res) {
-				toast.success('Successfully updated.');
+				toast.success($i18n.t('Successfully updated.'));
 			}
 
 			currentPassword = '';
@@ -40,7 +43,7 @@
 	}}
 >
 	<div class="flex justify-between items-center text-sm">
-		<div class="  font-medium">Change Password</div>
+		<div class="  font-medium">{$i18n.t('Change Password')}</div>
 		<button
 			class=" text-xs font-medium text-gray-500"
 			type="button"
@@ -53,7 +56,7 @@
 	{#if show}
 		<div class=" py-2.5 space-y-1.5">
 			<div class="flex flex-col w-full">
-				<div class=" mb-1 text-xs text-gray-500">Current Password</div>
+				<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Current Password')}</div>
 
 				<div class="flex-1">
 					<input
@@ -67,7 +70,7 @@
 			</div>
 
 			<div class="flex flex-col w-full">
-				<div class=" mb-1 text-xs text-gray-500">New Password</div>
+				<div class=" mb-1 text-xs text-gray-500">{$i18n.t('New Password')}</div>
 
 				<div class="flex-1">
 					<input
@@ -81,7 +84,7 @@
 			</div>
 
 			<div class="flex flex-col w-full">
-				<div class=" mb-1 text-xs text-gray-500">Confirm Password</div>
+				<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Confirm Password')}</div>
 
 				<div class="flex-1">
 					<input

+ 11 - 9
src/lib/components/chat/Settings/Advanced.svelte

@@ -1,8 +1,10 @@
 <script lang="ts">
-	import { createEventDispatcher, onMount } from 'svelte';
+	import { createEventDispatcher, onMount, getContext } from 'svelte';
+	import AdvancedParams from './Advanced/AdvancedParams.svelte';
+
+	const i18n = getContext('i18n');
 	const dispatch = createEventDispatcher();
 
-	import AdvancedParams from './Advanced/AdvancedParams.svelte';
 	export let saveSettings: Function;
 
 	// Advanced
@@ -55,14 +57,14 @@
 
 <div class="flex flex-col h-full justify-between text-sm">
 	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
-		<div class=" text-sm font-medium">Parameters</div>
+		<div class=" text-sm font-medium">{$i18n.t('Parameters')}</div>
 
 		<AdvancedParams bind:options />
 		<hr class=" dark:border-gray-700" />
 
 		<div class=" py-1 w-full justify-between">
 			<div class="flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Keep Alive</div>
+				<div class=" self-center text-xs font-medium">{$i18n.t('Keep Alive')}</div>
 
 				<button
 					class="p-1 px-3 text-xs flex rounded transition"
@@ -72,9 +74,9 @@
 					}}
 				>
 					{#if keepAlive === null}
-						<span class="ml-2 self-center"> Default </span>
+						<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 					{:else}
-						<span class="ml-2 self-center"> Custom </span>
+						<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 					{/if}
 				</button>
 			</div>
@@ -93,7 +95,7 @@
 
 		<div>
 			<div class=" py-1 flex w-full justify-between">
-				<div class=" self-center text-sm font-medium">Request Mode</div>
+				<div class=" self-center text-sm font-medium">{$i18n.t('Request Mode')}</div>
 
 				<button
 					class="p-1 px-3 text-xs flex rounded transition"
@@ -102,7 +104,7 @@
 					}}
 				>
 					{#if requestFormat === ''}
-						<span class="ml-2 self-center"> Default </span>
+						<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 					{:else if requestFormat === 'json'}
 						<!-- <svg
                             xmlns="http://www.w3.org/2000/svg"
@@ -114,7 +116,7 @@
                                 d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
                             />
                         </svg> -->
-						<span class="ml-2 self-center"> JSON </span>
+						<span class="ml-2 self-center">{$i18n.t(' JSON ')}</span>
 					{/if}
 				</button>
 			</div>

+ 39 - 35
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte

@@ -1,4 +1,8 @@
 <script lang="ts">
+	import { getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
+
 	export let options = {
 		// Advanced
 		seed: 0,
@@ -20,7 +24,7 @@
 <div class=" space-y-3 text-xs">
 	<div>
 		<div class=" py-0.5 flex w-full justify-between">
-			<div class=" w-20 text-xs font-medium self-center">Seed</div>
+			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Seed')}</div>
 			<div class=" flex-1 self-center">
 				<input
 					class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
@@ -36,7 +40,7 @@
 
 	<div>
 		<div class=" py-0.5 flex w-full justify-between">
-			<div class=" w-20 text-xs font-medium self-center">Stop Sequence</div>
+			<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Stop Sequence')}</div>
 			<div class=" flex-1 self-center">
 				<input
 					class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
@@ -51,7 +55,7 @@
 
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">Temperature</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Temperature')}</div>
 
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
@@ -61,9 +65,9 @@
 				}}
 			>
 				{#if options.temperature === ''}
-					<span class="ml-2 self-center"> Default </span>
+					<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
 				{:else}
-					<span class="ml-2 self-center"> Custom </span>
+					<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
 				{/if}
 			</button>
 		</div>
@@ -97,7 +101,7 @@
 
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">Mirostat</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Mirostat')}</div>
 
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
@@ -107,9 +111,9 @@
 				}}
 			>
 				{#if options.mirostat === ''}
-					<span class="ml-2 self-center"> Default </span>
+					<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 				{:else}
-					<span class="ml-2 self-center"> Custom </span>
+					<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 				{/if}
 			</button>
 		</div>
@@ -143,7 +147,7 @@
 
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">Mirostat Eta</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Mirostat Eta')}</div>
 
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
@@ -153,9 +157,9 @@
 				}}
 			>
 				{#if options.mirostat_eta === ''}
-					<span class="ml-2 self-center"> Default </span>
+					<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 				{:else}
-					<span class="ml-2 self-center"> Custom </span>
+					<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 				{/if}
 			</button>
 		</div>
@@ -189,7 +193,7 @@
 
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">Mirostat Tau</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Mirostat Tau')}</div>
 
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
@@ -199,9 +203,9 @@
 				}}
 			>
 				{#if options.mirostat_tau === ''}
-					<span class="ml-2 self-center"> Default </span>
+					<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 				{:else}
-					<span class="ml-2 self-center"> Custom </span>
+					<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 				{/if}
 			</button>
 		</div>
@@ -235,7 +239,7 @@
 
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">Top K</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Top K')}</div>
 
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
@@ -245,9 +249,9 @@
 				}}
 			>
 				{#if options.top_k === ''}
-					<span class="ml-2 self-center"> Default </span>
+					<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 				{:else}
-					<span class="ml-2 self-center"> Custom </span>
+					<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 				{/if}
 			</button>
 		</div>
@@ -281,7 +285,7 @@
 
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">Top P</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Top P')}</div>
 
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
@@ -291,9 +295,9 @@
 				}}
 			>
 				{#if options.top_p === ''}
-					<span class="ml-2 self-center"> Default </span>
+					<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 				{:else}
-					<span class="ml-2 self-center"> Custom </span>
+					<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 				{/if}
 			</button>
 		</div>
@@ -327,7 +331,7 @@
 
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">Repeat Penalty</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Repeat Penalty')}</div>
 
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
@@ -337,9 +341,9 @@
 				}}
 			>
 				{#if options.repeat_penalty === ''}
-					<span class="ml-2 self-center"> Default </span>
+					<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 				{:else}
-					<span class="ml-2 self-center"> Custom </span>
+					<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 				{/if}
 			</button>
 		</div>
@@ -373,7 +377,7 @@
 
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">Repeat Last N</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Repeat Last N')}</div>
 
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
@@ -383,9 +387,9 @@
 				}}
 			>
 				{#if options.repeat_last_n === ''}
-					<span class="ml-2 self-center"> Default </span>
+					<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 				{:else}
-					<span class="ml-2 self-center"> Custom </span>
+					<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 				{/if}
 			</button>
 		</div>
@@ -419,7 +423,7 @@
 
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">Tfs Z</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Tfs Z')}</div>
 
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
@@ -429,9 +433,9 @@
 				}}
 			>
 				{#if options.tfs_z === ''}
-					<span class="ml-2 self-center"> Default </span>
+					<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 				{:else}
-					<span class="ml-2 self-center"> Custom </span>
+					<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 				{/if}
 			</button>
 		</div>
@@ -465,7 +469,7 @@
 
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">Context Length</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Context Length')}</div>
 
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
@@ -475,9 +479,9 @@
 				}}
 			>
 				{#if options.num_ctx === ''}
-					<span class="ml-2 self-center"> Default </span>
+					<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 				{:else}
-					<span class="ml-2 self-center"> Custom </span>
+					<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 				{/if}
 			</button>
 		</div>
@@ -510,7 +514,7 @@
 	</div>
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
-			<div class=" self-center text-xs font-medium">Max Tokens</div>
+			<div class=" self-center text-xs font-medium">{$i18n.t('Max Tokens')}</div>
 
 			<button
 				class="p-1 px-3 text-xs flex rounded transition"
@@ -520,9 +524,9 @@
 				}}
 			>
 				{#if options.num_predict === ''}
-					<span class="ml-2 self-center"> Default </span>
+					<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 				{:else}
-					<span class="ml-2 self-center"> Custom </span>
+					<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 				{/if}
 			</button>
 		</div>

+ 24 - 20
src/lib/components/chat/Settings/Audio.svelte

@@ -1,8 +1,10 @@
 <script lang="ts">
-	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;
 
 	// Audio
@@ -101,10 +103,10 @@
 >
 	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 		<div>
-			<div class=" mb-1 text-sm font-medium">STT Settings</div>
+			<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
 
 			<div class=" py-0.5 flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Speech-to-Text Engine</div>
+				<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
 				<div class="flex items-center relative">
 					<select
 						class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
@@ -119,14 +121,14 @@
 							}
 						}}
 					>
-						<option value="">Default (Web API)</option>
-						<option value="whisper-local">Whisper (Local)</option>
+						<option value="">{$i18n.t('Default (Web API)')}</option>
+						<option value="whisper-local">{$i18n.t('Whisper (Local)')}</option>
 					</select>
 				</div>
 			</div>
 
 			<div class=" py-0.5 flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Conversation Mode</div>
+				<div class=" self-center text-xs font-medium">{$i18n.t('Conversation Mode')}</div>
 
 				<button
 					class="p-1 px-3 text-xs flex rounded transition"
@@ -136,15 +138,17 @@
 					type="button"
 				>
 					{#if conversationMode === true}
-						<span class="ml-2 self-center">On</span>
+						<span class="ml-2 self-center">{$i18n.t('On')}</span>
 					{:else}
-						<span class="ml-2 self-center">Off</span>
+						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 					{/if}
 				</button>
 			</div>
 
 			<div class=" py-0.5 flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Auto-send input after 3 sec.</div>
+				<div class=" self-center text-xs font-medium">
+					{$i18n.t('Auto-send input after 3 sec.')}
+				</div>
 
 				<button
 					class="p-1 px-3 text-xs flex rounded transition"
@@ -154,19 +158,19 @@
 					type="button"
 				>
 					{#if speechAutoSend === true}
-						<span class="ml-2 self-center">On</span>
+						<span class="ml-2 self-center">{$i18n.t('On')}</span>
 					{:else}
-						<span class="ml-2 self-center">Off</span>
+						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 					{/if}
 				</button>
 			</div>
 		</div>
 
 		<div>
-			<div class=" mb-1 text-sm font-medium">TTS Settings</div>
+			<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
 
 			<div class=" py-0.5 flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Text-to-Speech Engine</div>
+				<div class=" self-center text-xs font-medium">{$i18n.t('Text-to-Speech Engine')}</div>
 				<div class="flex items-center relative">
 					<select
 						class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
@@ -182,14 +186,14 @@
 							}
 						}}
 					>
-						<option value="">Default (Web API)</option>
-						<option value="openai">Open AI</option>
+						<option value="">{$i18n.t('Default (Web API)')}</option>
+						<option value="openai">{$i18n.t('Open AI')}</option>
 					</select>
 				</div>
 			</div>
 
 			<div class=" py-0.5 flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Auto-playback response</div>
+				<div class=" self-center text-xs font-medium">{$i18n.t('Auto-playback response')}</div>
 
 				<button
 					class="p-1 px-3 text-xs flex rounded transition"
@@ -199,9 +203,9 @@
 					type="button"
 				>
 					{#if responseAutoPlayback === true}
-						<span class="ml-2 self-center">On</span>
+						<span class="ml-2 self-center">{$i18n.t('On')}</span>
 					{:else}
-						<span class="ml-2 self-center">Off</span>
+						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 					{/if}
 				</button>
 			</div>
@@ -211,7 +215,7 @@
 
 		{#if TTSEngine === ''}
 			<div>
-				<div class=" mb-2.5 text-sm font-medium">Set Voice</div>
+				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
 				<div class="flex w-full">
 					<div class="flex-1">
 						<select
@@ -230,7 +234,7 @@
 			</div>
 		{:else if TTSEngine === 'openai'}
 			<div>
-				<div class=" mb-2.5 text-sm font-medium">Set Voice</div>
+				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
 				<div class="flex w-full">
 					<div class="flex-1">
 						<select

+ 14 - 10
src/lib/components/chat/Settings/Chats.svelte

@@ -13,10 +13,12 @@
 		getChatList
 	} from '$lib/apis/chats';
 	import { getImportOrigin, convertOpenAIChats } from '$lib/utils';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 	import { goto } from '$app/navigation';
 	import { toast } from 'svelte-sonner';
 
+	const i18n = getContext('i18n');
+
 	export let saveSettings: Function;
 	// Chats
 	let saveChatHistory = true;
@@ -104,7 +106,7 @@
 			class="flex flex-col justify-between rounded-md items-center py-2 px-3.5 w-full transition"
 		>
 			<div class="flex w-full justify-between">
-				<div class=" self-center text-sm font-medium">Chat History</div>
+				<div class=" self-center text-sm font-medium">{$i18n.t('Chat History')}</div>
 
 				<button
 					class="p-1 px-3 text-xs flex rounded transition"
@@ -128,7 +130,7 @@
 							/>
 						</svg>
 
-						<span class="ml-2 self-center"> On </span>
+						<span class="ml-2 self-center"> {$i18n.t('On')} </span>
 					{:else}
 						<svg
 							xmlns="http://www.w3.org/2000/svg"
@@ -146,7 +148,7 @@
 							/>
 						</svg>
 
-						<span class="ml-2 self-center">Off</span>
+						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 					{/if}
 				</button>
 			</div>
@@ -180,7 +182,7 @@
 						/>
 					</svg>
 				</div>
-				<div class=" self-center text-sm font-medium">Import Chats</div>
+				<div class=" self-center text-sm font-medium">{$i18n.t('Import Chats')}</div>
 			</button>
 			<button
 				class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
@@ -202,7 +204,7 @@
 						/>
 					</svg>
 				</div>
-				<div class=" self-center text-sm font-medium">Export Chats</div>
+				<div class=" self-center text-sm font-medium">{$i18n.t('Export Chats')}</div>
 			</button>
 		</div>
 
@@ -288,7 +290,7 @@
 						/>
 					</svg>
 				</div>
-				<div class=" self-center text-sm font-medium">Delete Chats</div>
+				<div class=" self-center text-sm font-medium">{$i18n.t('Delete Chats')}</div>
 			</button>
 		{/if}
 
@@ -316,7 +318,9 @@
 						/>
 					</svg>
 				</div>
-				<div class=" self-center text-sm font-medium">Export All Chats (All Users)</div>
+				<div class=" self-center text-sm font-medium">
+					{$i18n.t('Export All Chats (All Users)')}
+				</div>
 			</button>
 
 			<hr class=" dark:border-gray-700" />
@@ -330,7 +334,7 @@
 					});
 
 					if (res) {
-						toast.success('Success');
+						toast.success($i18n.t('Success'));
 					}
 				}}
 			>
@@ -348,7 +352,7 @@
 						/>
 					</svg>
 				</div>
-				<div class=" self-center text-sm font-medium">Reset Vector Storage</div>
+				<div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
 			</button>
 		{/if}
 	</div>

+ 8 - 6
src/lib/components/chat/Settings/Connections.svelte

@@ -1,12 +1,14 @@
 <script lang="ts">
 	import { models, user } from '$lib/stores';
-	import { createEventDispatcher, onMount } from 'svelte';
+	import { createEventDispatcher, onMount, getContext } from 'svelte';
 	const dispatch = createEventDispatcher();
 
 	import { getOllamaAPIUrl, getOllamaVersion, updateOllamaAPIUrl } from '$lib/apis/ollama';
 	import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai';
 	import { toast } from 'svelte-sonner';
 
+	const i18n = getContext('i18n');
+
 	export let getModels: Function;
 
 	// External
@@ -34,7 +36,7 @@
 		});
 
 		if (ollamaVersion) {
-			toast.success('Server connection verified');
+			toast.success($i18n.t('Server connection verified'));
 			await models.set(await getModels());
 		}
 	};
@@ -64,7 +66,7 @@
 		<div class=" space-y-3">
 			<div class="mt-2 space-y-2 pr-1.5">
 				<div class="flex justify-between items-center text-sm">
-					<div class="  font-medium">OpenAI API</div>
+					<div class="  font-medium">{$i18n.t('OpenAI API')}</div>
 					<button
 						class=" text-xs font-medium text-gray-500"
 						type="button"
@@ -76,7 +78,7 @@
 
 				{#if showOpenAI}
 					<div>
-						<div class=" mb-2.5 text-sm font-medium">API Key</div>
+						<div class=" mb-2.5 text-sm font-medium">{$i18n.t('API Key')}</div>
 						<div class="flex w-full">
 							<div class="flex-1">
 								<input
@@ -90,7 +92,7 @@
 					</div>
 
 					<div>
-						<div class=" mb-2.5 text-sm font-medium">API Base URL</div>
+						<div class=" mb-2.5 text-sm font-medium">{$i18n.t('API Base URL')}</div>
 						<div class="flex w-full">
 							<div class="flex-1">
 								<input
@@ -114,7 +116,7 @@
 		<hr class=" dark:border-gray-700" />
 
 		<div>
-			<div class=" mb-2.5 text-sm font-medium">Ollama API URL</div>
+			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama API URL')}</div>
 			<div class="flex w-full">
 				<div class="flex-1 mr-2">
 					<input

+ 18 - 18
src/lib/components/chat/Settings/General.svelte

@@ -91,10 +91,10 @@
 <div class="flex flex-col h-full justify-between text-sm">
 	<div class="  pr-1.5 overflow-y-scroll max-h-[20.5rem]">
 		<div class="">
-			<div class=" mb-1 text-sm font-medium">WebUI Settings</div>
+			<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Settings')}</div>
 
 			<div class=" py-0.5 flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Theme</div>
+				<div class=" self-center text-xs font-medium">{$i18n.t('Theme')}</div>
 				<div class="flex items-center relative">
 					<div class=" absolute right-16">
 						{#if theme === 'dark'}
@@ -146,16 +146,16 @@
 							console.log(theme);
 						}}
 					>
-						<option value="dark">Dark</option>
-						<option value="light">Light</option>
-						<option value="rose-pine dark">Rosé Pine</option>
-						<option value="rose-pine-dawn light">Rosé Pine Dawn</option>
+						<option value="dark">{$i18n.t('Dark')}</option>
+						<option value="light">{$i18n.t('Light')}</option>
+						<option value="rose-pine dark">{$i18n.t('Rosé Pine')}</option>
+						<option value="rose-pine-dawn light">{$i18n.t('Rosé Pine Dawn')}</option>
 					</select>
 				</div>
 			</div>
 
 			<div class=" py-0.5 flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Language</div>
+				<div class=" self-center text-xs font-medium">{$i18n.t('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"
@@ -175,7 +175,7 @@
 
 			<div>
 				<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">{$i18n.t('Notification')}</div>
 
 					<button
 						class="p-1 px-3 text-xs flex rounded transition"
@@ -185,9 +185,9 @@
 						type="button"
 					>
 						{#if notificationEnabled === true}
-							<span class="ml-2 self-center">On</span>
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 						{:else}
-							<span class="ml-2 self-center">Off</span>
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 						{/if}
 					</button>
 				</div>
@@ -197,7 +197,7 @@
 		<hr class=" dark:border-gray-700 my-3" />
 
 		<div>
-			<div class=" my-2.5 text-sm font-medium">System Prompt</div>
+			<div class=" my-2.5 text-sm font-medium">{$i18n.t('System Prompt')}</div>
 			<textarea
 				bind:value={system}
 				class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
@@ -207,7 +207,7 @@
 
 		<div class="mt-2 space-y-3 pr-1.5">
 			<div class="flex justify-between items-center text-sm">
-				<div class="  font-medium">Advanced Parameters</div>
+				<div class="  font-medium">{$i18n.t('Advanced Parameters')}</div>
 				<button
 					class=" text-xs font-medium text-gray-500"
 					type="button"
@@ -223,7 +223,7 @@
 
 				<div class=" py-1 w-full justify-between">
 					<div class="flex w-full justify-between">
-						<div class=" self-center text-xs font-medium">Keep Alive</div>
+						<div class=" self-center text-xs font-medium">{$i18n.t('Keep Alive')}</div>
 
 						<button
 							class="p-1 px-3 text-xs flex rounded transition"
@@ -233,9 +233,9 @@
 							}}
 						>
 							{#if keepAlive === null}
-								<span class="ml-2 self-center"> Default </span>
+								<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
 							{:else}
-								<span class="ml-2 self-center"> Custom </span>
+								<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
 							{/if}
 						</button>
 					</div>
@@ -254,7 +254,7 @@
 
 				<div>
 					<div class=" py-1 flex w-full justify-between">
-						<div class=" self-center text-sm font-medium">Request Mode</div>
+						<div class=" self-center text-sm font-medium">{$i18n.t('Request Mode')}</div>
 
 						<button
 							class="p-1 px-3 text-xs flex rounded transition"
@@ -263,7 +263,7 @@
 							}}
 						>
 							{#if requestFormat === ''}
-								<span class="ml-2 self-center"> Default </span>
+								<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
 							{:else if requestFormat === 'json'}
 								<!-- <svg
                             xmlns="http://www.w3.org/2000/svg"
@@ -275,7 +275,7 @@
                                 d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
                             />
                         </svg> -->
-								<span class="ml-2 self-center"> JSON </span>
+								<span class="ml-2 self-center"> {$i18n.t('JSON')} </span>
 							{/if}
 						</button>
 					</div>

+ 11 - 9
src/lib/components/chat/Settings/Images.svelte

@@ -58,7 +58,7 @@
 			await getModels();
 
 			if (models) {
-				toast.success('Server connection verified');
+				toast.success($i18n.t('Server connection verified'));
 			}
 		} else {
 			AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
@@ -117,11 +117,13 @@
 >
 	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[20.5rem]">
 		<div>
-			<div class=" mb-1 text-sm font-medium">Image Settings</div>
+			<div class=" mb-1 text-sm font-medium">{$i18n.t('Image Settings')}</div>
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs font-medium">Image Generation (Experimental)</div>
+					<div class=" self-center text-xs font-medium">
+						{$i18n.t('Image Generation (Experimental)')}
+					</div>
 
 					<button
 						class="p-1 px-3 text-xs flex rounded transition"
@@ -131,9 +133,9 @@
 						type="button"
 					>
 						{#if enableImageGeneration === true}
-							<span class="ml-2 self-center">On</span>
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 						{:else}
-							<span class="ml-2 self-center">Off</span>
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 						{/if}
 					</button>
 				</div>
@@ -141,7 +143,7 @@
 		</div>
 		<hr class=" dark:border-gray-700" />
 
-		<div class=" mb-2.5 text-sm font-medium">AUTOMATIC1111 Base URL</div>
+		<div class=" mb-2.5 text-sm font-medium">{$i18n.t('AUTOMATIC1111 Base URL')}</div>
 		<div class="flex w-full">
 			<div class="flex-1 mr-2">
 				<input
@@ -189,7 +191,7 @@
 			<hr class=" dark:border-gray-700" />
 
 			<div>
-				<div class=" mb-2.5 text-sm font-medium">Set Default Model</div>
+				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Default Model')}</div>
 				<div class="flex w-full">
 					<div class="flex-1 mr-2">
 						<select
@@ -211,7 +213,7 @@
 			</div>
 
 			<div>
-				<div class=" mb-2.5 text-sm font-medium">Set Image Size</div>
+				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Image Size')}</div>
 				<div class="flex w-full">
 					<div class="flex-1 mr-2">
 						<input
@@ -224,7 +226,7 @@
 			</div>
 
 			<div>
-				<div class=" mb-2.5 text-sm font-medium">Set Steps</div>
+				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Steps')}</div>
 				<div class="flex w-full">
 					<div class="flex-1 mr-2">
 						<input

+ 15 - 15
src/lib/components/chat/Settings/Interface.svelte

@@ -96,11 +96,11 @@
 >
 	<div class=" space-y-3 pr-1.5 overflow-y-scroll h-80">
 		<div>
-			<div class=" mb-1 text-sm font-medium">WebUI Add-ons</div>
+			<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Add-ons')}</div>
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs font-medium">Title Auto-Generation</div>
+					<div class=" self-center text-xs font-medium">{$i18n.t('Title Auto-Generation')}</div>
 
 					<button
 						class="p-1 px-3 text-xs flex rounded transition"
@@ -110,9 +110,9 @@
 						type="button"
 					>
 						{#if titleAutoGenerate === true}
-							<span class="ml-2 self-center">On</span>
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 						{:else}
-							<span class="ml-2 self-center">Off</span>
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 						{/if}
 					</button>
 				</div>
@@ -120,7 +120,7 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs font-medium">Response AutoCopy to Clipboard</div>
+					<div class=" self-center text-xs font-medium">{$i18n.t('Response AutoCopy to Clipboard')}</div>
 
 					<button
 						class="p-1 px-3 text-xs flex rounded transition"
@@ -130,9 +130,9 @@
 						type="button"
 					>
 						{#if responseAutoCopy === true}
-							<span class="ml-2 self-center">On</span>
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 						{:else}
-							<span class="ml-2 self-center">Off</span>
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 						{/if}
 					</button>
 				</div>
@@ -140,7 +140,7 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs font-medium">Full Screen Mode</div>
+					<div class=" self-center text-xs font-medium">{$i18n.t('Full Screen Mode')}</div>
 
 					<button
 						class="p-1 px-3 text-xs flex rounded transition"
@@ -150,9 +150,9 @@
 						type="button"
 					>
 						{#if fullScreenMode === true}
-							<span class="ml-2 self-center">On</span>
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 						{:else}
-							<span class="ml-2 self-center">Off</span>
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 						{/if}
 					</button>
 				</div>
@@ -172,9 +172,9 @@
 						type="button"
 					>
 						{#if showUsername === true}
-							<span class="ml-2 self-center">On</span>
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
 						{:else}
-							<span class="ml-2 self-center">Off</span>
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
 						{/if}
 					</button>
 				</div>
@@ -184,7 +184,7 @@
 		<hr class=" dark:border-gray-700" />
 
 		<div>
-			<div class=" mb-2.5 text-sm font-medium">Set Title Auto-Generation Model</div>
+			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Title Auto-Generation Model')}</div>
 			<div class="flex w-full">
 				<div class="flex-1 mr-2">
 					<select
@@ -227,7 +227,7 @@
 				</button>
 			</div>
 			<div class="mt-3">
-				<div class=" mb-2.5 text-sm font-medium">Title Generation Prompt</div>
+				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Title Generation Prompt')}</div>
 				<textarea
 					bind:value={titleGenerationPrompt}
 					class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
@@ -241,7 +241,7 @@
 
 			<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 				<div class="flex w-full justify-between mb-2">
-					<div class=" self-center text-sm font-semibold">Default Prompt Suggestions</div>
+					<div class=" self-center text-sm font-semibold">{$i18n.t('Default Prompt Suggestions')}</div>
 
 					<button
 						class="p-1 px-3 text-xs flex rounded transition"

+ 25 - 26
src/lib/components/chat/Settings/Models.svelte

@@ -78,7 +78,7 @@
 				if (!data.success) {
 					toast.error(data.error);
 				} else {
-					toast.success(`Model '${modelName}' has been successfully downloaded.`);
+					toast.success($i18n.t(`Model '${modelName}' has been successfully downloaded.`));
 
 					const notification = new Notification($WEBUI_NAME, {
 						body: `Model '${modelName}' has been successfully downloaded.`,
@@ -243,7 +243,7 @@
 		});
 
 		if (res) {
-			toast.success(`Deleted ${deleteModelTag}`);
+			toast.success($i18n.t(`Deleted ${deleteModelTag}`));
 		}
 
 		deleteModelTag = '';
@@ -372,7 +372,7 @@
 				<div>
 					<div class=" mb-2 text-sm font-medium">Manage Ollama Models</div>
 
-					<div class=" mb-2 text-sm font-medium">Pull a model from Ollama.com</div>
+					<div class=" mb-2 text-sm font-medium">{$i18n.t('Pull a model from Ollama.com')}</div>
 					<div class="flex w-full">
 						<div class="flex-1 mr-2">
 							<input
@@ -436,7 +436,7 @@
 						To access the available model names for downloading, <a
 							class=" text-gray-500 dark:text-gray-300 font-medium underline"
 							href="https://ollama.com/library"
-							target="_blank">click here.</a
+							target="_blank">{$i18n.t('click here.')}</a
 						>
 					</div>
 
@@ -461,7 +461,7 @@
 				</div>
 
 				<div>
-					<div class=" mb-2 text-sm font-medium">Delete a model</div>
+					<div class=" mb-2 text-sm font-medium">{$i18n.t('Delete a model')}</div>
 					<div class="flex w-full">
 						<div class="flex-1 mr-2">
 							<select
@@ -503,7 +503,7 @@
 
 				<div>
 					<div class="flex justify-between items-center text-xs">
-						<div class=" text-sm font-medium">Experimental</div>
+						<div class=" text-sm font-medium">{$i18n.t('Experimental')}</div>
 						<button
 							class=" text-xs font-medium text-gray-500"
 							type="button"
@@ -521,7 +521,7 @@
 						}}
 					>
 						<div class=" mb-2 flex w-full justify-between">
-							<div class="  text-sm font-medium">Upload a GGUF model</div>
+							<div class="  text-sm font-medium">{$i18n.t('Upload a GGUF model')}</div>
 
 							<button
 								class="p-1 px-3 text-xs flex rounded transition"
@@ -535,9 +535,9 @@
 								type="button"
 							>
 								{#if modelUploadMode === 'file'}
-									<span class="ml-2 self-center">File Mode</span>
+									<span class="ml-2 self-center">{$i18n.t('File Mode')}</span>
 								{:else}
-									<span class="ml-2 self-center">URL Mode</span>
+									<span class="ml-2 self-center">{$i18n.t('URL Mode')}</span>
 								{/if}
 							</button>
 						</div>
@@ -642,7 +642,7 @@
 						{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
 							<div>
 								<div>
-									<div class=" my-2.5 text-sm font-medium">Modelfile Content</div>
+									<div class=" my-2.5 text-sm font-medium">{$i18n.t('Modelfile Content')}</div>
 									<textarea
 										bind:value={modelFileContent}
 										class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
@@ -655,13 +655,13 @@
 							To access the GGUF models available for downloading, <a
 								class=" text-gray-500 dark:text-gray-300 font-medium underline"
 								href="https://huggingface.co/models?search=gguf"
-								target="_blank">click here.</a
+								target="_blank">{$i18n.t('click here.')}</a
 							>
 						</div>
 
 						{#if uploadProgress !== null}
 							<div class="mt-2">
-								<div class=" mb-2 text-xs">Upload Progress</div>
+								<div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div>
 
 								<div class="w-full rounded-full dark:bg-gray-800">
 									<div
@@ -685,11 +685,11 @@
 		<div class=" space-y-3">
 			<div class="mt-2 space-y-3 pr-1.5">
 				<div>
-					<div class=" mb-2 text-sm font-medium">Manage LiteLLM Models</div>
+					<div class=" mb-2 text-sm font-medium">{$i18n.t('Manage LiteLLM Models')}</div>
 
 					<div>
 						<div class="flex justify-between items-center text-xs">
-							<div class=" text-sm font-medium">Add a model</div>
+							<div class=" text-sm font-medium">{$i18n.t('Add a model')}</div>
 							<button
 								class=" text-xs font-medium text-gray-500"
 								type="button"
@@ -732,7 +732,7 @@
 
 						{#if showLiteLLMParams}
 							<div>
-								<div class=" mb-1.5 text-sm font-medium">Model Name</div>
+								<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Model Name')}</div>
 								<div class="flex w-full">
 									<div class="flex-1">
 										<input
@@ -746,7 +746,7 @@
 							</div>
 
 							<div>
-								<div class=" mb-1.5 text-sm font-medium">API Base URL</div>
+								<div class=" mb-1.5 text-sm font-medium">{$i18n.t('API Base URL')}</div>
 								<div class="flex w-full">
 									<div class="flex-1">
 										<input
@@ -760,7 +760,7 @@
 							</div>
 
 							<div>
-								<div class=" mb-1.5 text-sm font-medium">API Key</div>
+								<div class=" mb-1.5 text-sm font-medium">{$i18n.t('API Key')}</div>
 								<div class="flex w-full">
 									<div class="flex-1">
 										<input
@@ -774,7 +774,7 @@
 							</div>
 
 							<div>
-								<div class="mb-1.5 text-sm font-medium">API RPM</div>
+								<div class="mb-1.5 text-sm font-medium">{$i18n.t('API RPM')}</div>
 								<div class="flex w-full">
 									<div class="flex-1">
 										<input
@@ -801,7 +801,7 @@
 					</div>
 
 					<div>
-						<div class=" mb-2.5 text-sm font-medium">Delete a model</div>
+						<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Delete a model')}</div>
 						<div class="flex w-full">
 							<div class="flex-1 mr-2">
 								<select
@@ -810,8 +810,7 @@
 									placeholder={$i18n.t('Select a model')}
 								>
 									{#if !deleteLiteLLMModelId}
-										<option value="" disabled selected>{$i18n.t('Select a model')}</option
-										>
+										<option value="" disabled selected>{$i18n.t('Select a model')}</option>
 									{/if}
 									{#each liteLLMModelInfo as model}
 										<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700"
@@ -846,7 +845,7 @@
 
 			<!-- <div class="mt-2 space-y-3 pr-1.5">
 				<div>
-					<div class=" mb-2.5 text-sm font-medium">Add LiteLLM Model</div>
+					<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Add LiteLLM Model')}</div>
 					<div class="flex w-full mb-2">
 						<div class="flex-1">
 							<input
@@ -859,7 +858,7 @@
 					</div>
 
 					<div class="flex justify-between items-center text-sm">
-						<div class="  font-medium">Advanced Model Params</div>
+						<div class="  font-medium">{$i18n.t('Advanced Model Params')}</div>
 						<button
 							class=" text-xs font-medium text-gray-500"
 							type="button"
@@ -871,7 +870,7 @@
 
 					{#if showLiteLLMParams}
 						<div>
-							<div class=" mb-2.5 text-sm font-medium">LiteLLM API Key</div>
+							<div class=" mb-2.5 text-sm font-medium">{$i18n.t('LiteLLM API Key')}</div>
 							<div class="flex w-full">
 								<div class="flex-1">
 									<input
@@ -885,7 +884,7 @@
 						</div>
 
 						<div>
-							<div class=" mb-2.5 text-sm font-medium">LiteLLM API Base URL</div>
+							<div class=" mb-2.5 text-sm font-medium">{$i18n.t('LiteLLM API Base URL')}</div>
 							<div class="flex w-full">
 								<div class="flex-1">
 									<input
@@ -899,7 +898,7 @@
 						</div>
 
 						<div>
-							<div class=" mb-2.5 text-sm font-medium">LiteLLM API RPM</div>
+							<div class=" mb-2.5 text-sm font-medium">{$i18n.t('LiteLLM API RPM')}</div>
 							<div class="flex w-full">
 								<div class="flex-1">
 									<input

+ 13 - 10
src/lib/components/chat/SettingsModal.svelte

@@ -1,4 +1,5 @@
 <script lang="ts">
+	import { getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 	import { models, settings, user } from '$lib/stores';
 
@@ -17,6 +18,8 @@
 	import Connections from './Settings/Connections.svelte';
 	import Images from './Settings/Images.svelte';
 
+	const i18n = getContext('i18n');
+
 	export let show = false;
 
 	const saveSettings = async (updated) => {
@@ -58,7 +61,7 @@
 <Modal bind:show>
 	<div>
 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
-			<div class=" text-lg font-medium self-center">Settings</div>
+			<div class=" text-lg font-medium self-center">{$i18n.t('Settings')}</div>
 			<button
 				class="self-center"
 				on:click={() => {
@@ -106,7 +109,7 @@
 							/>
 						</svg>
 					</div>
-					<div class=" self-center">General</div>
+					<div class=" self-center">{$i18n.t('General')}</div>
 				</button>
 
 				{#if $user?.role === 'admin'}
@@ -131,7 +134,7 @@
 								/>
 							</svg>
 						</div>
-						<div class=" self-center">Connections</div>
+						<div class=" self-center">{$i18n.t('Connections')}</div>
 					</button>
 
 					<button
@@ -157,7 +160,7 @@
 								/>
 							</svg>
 						</div>
-						<div class=" self-center">Models</div>
+						<div class=" self-center">{$i18n.t('Models')}</div>
 					</button>
 				{/if}
 
@@ -184,7 +187,7 @@
 							/>
 						</svg>
 					</div>
-					<div class=" self-center">Interface</div>
+					<div class=" self-center">{$i18n.t('Interface')}</div>
 				</button>
 
 				<button
@@ -211,7 +214,7 @@
 							/>
 						</svg>
 					</div>
-					<div class=" self-center">Audio</div>
+					<div class=" self-center">{$i18n.t('Audio')}</div>
 				</button>
 
 				{#if $user.role === 'admin'}
@@ -238,7 +241,7 @@
 								/>
 							</svg>
 						</div>
-						<div class=" self-center">Images</div>
+						<div class=" self-center">{$i18n.t('Images')}</div>
 					</button>
 				{/if}
 
@@ -265,7 +268,7 @@
 							/>
 						</svg>
 					</div>
-					<div class=" self-center">Chats</div>
+					<div class=" self-center">{$i18n.t('Chats')}</div>
 				</button>
 
 				<button
@@ -291,7 +294,7 @@
 							/>
 						</svg>
 					</div>
-					<div class=" self-center">Account</div>
+					<div class=" self-center">{$i18n.t('Account')}</div>
 				</button>
 
 				<button
@@ -317,7 +320,7 @@
 							/>
 						</svg>
 					</div>
-					<div class=" self-center">About</div>
+					<div class=" self-center">{$i18n.t('About')}</div>
 				</button>
 			</div>
 			<div class="flex-1 md:min-h-[380px]">

+ 6 - 3
src/lib/components/chat/ShareChatModal.svelte

@@ -1,6 +1,9 @@
 <script lang="ts">
+	import { getContext } from 'svelte';
 	import Modal from '../common/Modal.svelte';
 
+	const i18n = getContext('i18n');
+
 	export let downloadChat: Function;
 	export let shareChat: Function;
 
@@ -17,11 +20,11 @@
 				show = false;
 			}}
 		>
-			Share to OpenWebUI Community
+			{$i18n.t('Share to OpenWebUI Community')}
 		</button>
 
 		<div class="flex justify-center space-x-1 mt-1.5">
-			<div class=" self-center text-gray-400 text-xs font-medium">or</div>
+			<div class=" self-center text-gray-400 text-xs font-medium">{$i18n.t('or')}</div>
 
 			<button
 				class=" self-center rounded-full text-xs font-medium text-gray-700 dark:text-gray-500 underline"
@@ -31,7 +34,7 @@
 					show = false;
 				}}
 			>
-				Download as a File
+				{$i18n.t('Download as a File')}
 			</button>
 		</div>
 	</div>

+ 12 - 9
src/lib/components/chat/ShortcutsModal.svelte

@@ -1,13 +1,16 @@
 <script lang="ts">
+	import { getContext } from 'svelte';
 	import Modal from '../common/Modal.svelte';
 
+	const i18n = getContext('i18n');
+
 	export let show = false;
 </script>
 
 <Modal bind:show>
 	<div>
 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
-			<div class=" text-lg font-medium self-center">Keyboard shortcuts</div>
+			<div class=" text-lg font-medium self-center">{$i18n.t('Keyboard shortcuts')}</div>
 			<button
 				class="self-center"
 				on:click={() => {
@@ -32,7 +35,7 @@
 			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
 				<div class="flex flex-col space-y-3 w-full self-start">
 					<div class="w-full flex justify-between items-center">
-						<div class=" text-sm">Open new chat</div>
+						<div class=" text-sm">{$i18n.t('Open new chat')}</div>
 
 						<div class="flex space-x-1 text-xs">
 							<div
@@ -56,7 +59,7 @@
 					</div>
 
 					<div class="w-full flex justify-between items-center">
-						<div class=" text-sm">Focus chat input</div>
+						<div class=" text-sm">{$i18n.t('Focus chat input')}</div>
 
 						<div class="flex space-x-1 text-xs">
 							<div
@@ -74,7 +77,7 @@
 					</div>
 
 					<div class="w-full flex justify-between items-center">
-						<div class=" text-sm">Copy last code block</div>
+						<div class=" text-sm">{$i18n.t('Copy last code block')}</div>
 
 						<div class="flex space-x-1 text-xs">
 							<div
@@ -98,7 +101,7 @@
 					</div>
 
 					<div class="w-full flex justify-between items-center">
-						<div class=" text-sm">Copy last response</div>
+						<div class=" text-sm">{$i18n.t('Copy last response')}</div>
 
 						<div class="flex space-x-1 text-xs">
 							<div
@@ -124,7 +127,7 @@
 
 				<div class="flex flex-col space-y-3 w-full self-start">
 					<div class="w-full flex justify-between items-center">
-						<div class=" text-sm">Toggle settings</div>
+						<div class=" text-sm">{$i18n.t('Toggle settings')}</div>
 
 						<div class="flex space-x-1 text-xs">
 							<div
@@ -141,7 +144,7 @@
 					</div>
 
 					<div class="w-full flex justify-between items-center">
-						<div class=" text-sm">Toggle sidebar</div>
+						<div class=" text-sm">{$i18n.t('Toggle sidebar')}</div>
 
 						<div class="flex space-x-1 text-xs">
 							<div
@@ -165,7 +168,7 @@
 					</div>
 
 					<div class="w-full flex justify-between items-center">
-						<div class=" text-sm">Delete chat</div>
+						<div class=" text-sm">{$i18n.t('Delete chat')}</div>
 
 						<div class="flex space-x-1 text-xs">
 							<div
@@ -188,7 +191,7 @@
 					</div>
 
 					<div class="w-full flex justify-between items-center">
-						<div class=" text-sm">Show shortcuts</div>
+						<div class=" text-sm">{$i18n.t('Show shortcuts')}</div>
 
 						<div class="flex space-x-1 text-xs">
 							<div

+ 5 - 3
src/lib/components/documents/AddDocModal.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 	import { toast } from 'svelte-sonner';
 	import dayjs from 'dayjs';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 
 	import { createNewDoc, getDocs, tagDocByName, updateDocByName } from '$lib/apis/documents';
 	import Modal from '../common/Modal.svelte';
@@ -13,6 +13,8 @@
 	import { transformFileName } from '$lib/utils';
 	import { SUPPORTED_FILE_EXTENSIONS, SUPPORTED_FILE_TYPE } from '$lib/constants';
 
+	const i18n = getContext('i18n');
+
 	export let show = false;
 	export let selectedDoc;
 
@@ -96,7 +98,7 @@
 <Modal size="sm" bind:show>
 	<div>
 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
-			<div class=" text-lg font-medium self-center">Add Docs</div>
+			<div class=" text-lg font-medium self-center">{$i18n.t('Add Docs')}</div>
 			<button
 				class="self-center"
 				on:click={() => {
@@ -145,7 +147,7 @@
 
 					<div class=" flex flex-col space-y-1.5">
 						<div class="flex flex-col w-full">
-							<div class=" mb-1.5 text-xs text-gray-500">Tags</div>
+							<div class=" mb-1.5 text-xs text-gray-500">{$i18n.t('Tags')}</div>
 
 							<Tags {tags} addTag={addTagHandler} deleteTag={deleteTagHandler} />
 						</div>

+ 7 - 5
src/lib/components/documents/EditDocModal.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 	import { toast } from 'svelte-sonner';
 	import dayjs from 'dayjs';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 
 	import { getDocs, tagDocByName, updateDocByName } from '$lib/apis/documents';
 	import Modal from '../common/Modal.svelte';
@@ -10,6 +10,8 @@
 	import Tags from '../common/Tags.svelte';
 	import { addTagById } from '$lib/apis/chats';
 
+	const i18n = getContext('i18n');
+
 	export let show = false;
 	export let selectedDoc;
 
@@ -74,7 +76,7 @@
 <Modal size="sm" bind:show>
 	<div>
 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
-			<div class=" text-lg font-medium self-center">Edit Doc</div>
+			<div class=" text-lg font-medium self-center">{$i18n.t('Edit Doc')}</div>
 			<button
 				class="self-center"
 				on:click={() => {
@@ -105,7 +107,7 @@
 				>
 					<div class=" flex flex-col space-y-1.5">
 						<div class="flex flex-col w-full">
-							<div class=" mb-1 text-xs text-gray-500">Name Tag</div>
+							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name Tag')}</div>
 
 							<div class="flex flex-1">
 								<div
@@ -134,7 +136,7 @@
 						</div>
 
 						<div class="flex flex-col w-full">
-							<div class=" mb-1 text-xs text-gray-500">Title</div>
+							<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Title')}</div>
 
 							<div class="flex-1">
 								<input
@@ -148,7 +150,7 @@
 						</div>
 
 						<div class="flex flex-col w-full">
-							<div class=" mb-1.5 text-xs text-gray-500">Tags</div>
+							<div class=" mb-1.5 text-xs text-gray-500">{$i18n.t('Tags')}</div>
 
 							<Tags {tags} addTag={addTagHandler} deleteTag={deleteTagHandler} />
 						</div>

+ 13 - 9
src/lib/components/documents/Settings/General.svelte

@@ -8,9 +8,11 @@
 		updateRAGTemplate
 	} from '$lib/apis/rag';
 	import { documents } from '$lib/stores';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 
+	const i18n = getContext('i18n');
+
 	export let saveHandler: Function;
 
 	let loading = false;
@@ -27,7 +29,7 @@
 
 		if (res) {
 			await documents.set(await getDocs(localStorage.token));
-			toast.success('Scan complete!');
+			toast.success($i18n.t('Scan complete!'));
 		}
 	};
 
@@ -57,10 +59,12 @@
 >
 	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
 		<div>
-			<div class=" mb-2 text-sm font-medium">General Settings</div>
+			<div class=" mb-2 text-sm font-medium">{$i18n.t('General Settings')}</div>
 
 			<div class="  flex w-full justify-between">
-				<div class=" self-center text-xs font-medium">Scan for documents from '/data/docs'</div>
+				<div class=" self-center text-xs font-medium">
+					{$i18n.t('Scan for documents from {{path}}', { path: '/data/docs' })}
+				</div>
 
 				<button
 					class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded flex flex-row space-x-1 items-center {loading
@@ -73,7 +77,7 @@
 					type="button"
 					disabled={loading}
 				>
-					<div class="self-center font-medium">Scan</div>
+					<div class="self-center font-medium">{$i18n.t('Scan')}</div>
 
 					<!-- <svg
 						xmlns="http://www.w3.org/2000/svg"
@@ -122,11 +126,11 @@
 		<hr class=" dark:border-gray-700" />
 
 		<div class=" ">
-			<div class=" text-sm font-medium">Chunk Params</div>
+			<div class=" text-sm font-medium">{$i18n.t('Chunk Params')}</div>
 
 			<div class=" flex">
 				<div class="  flex w-full justify-between">
-					<div class="self-center text-xs font-medium min-w-fit">Chunk Size</div>
+					<div class="self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Size')}</div>
 
 					<div class="self-center p-3">
 						<input
@@ -141,7 +145,7 @@
 				</div>
 
 				<div class="flex w-full">
-					<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
+					<div class=" self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Overlap')}</div>
 
 					<div class="self-center p-3">
 						<input
@@ -157,7 +161,7 @@
 			</div>
 
 			<div>
-				<div class=" mb-2.5 text-sm font-medium">RAG Template</div>
+				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('RAG Template')}</div>
 				<textarea
 					bind:value={template}
 					class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"

+ 5 - 2
src/lib/components/documents/SettingsModal.svelte

@@ -1,7 +1,10 @@
 <script>
+	import { getContext } from 'svelte';
 	import Modal from '../common/Modal.svelte';
 	import General from './Settings/General.svelte';
 
+	const i18n = getContext('i18n');
+
 	export let show = false;
 
 	let selectedTab = 'general';
@@ -10,7 +13,7 @@
 <Modal bind:show>
 	<div>
 		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
-			<div class=" text-lg font-medium self-center">Document Settings</div>
+			<div class=" text-lg font-medium self-center">{$i18n.t('Document Settings')}</div>
 			<button
 				class="self-center"
 				on:click={() => {
@@ -58,7 +61,7 @@
 							/>
 						</svg>
 					</div>
-					<div class=" self-center">General</div>
+					<div class=" self-center">{$i18n.t('General')}</div>
 				</button>
 			</div>
 			<div class="flex-1 md:min-h-[380px]">

+ 4 - 1
src/lib/components/layout/Navbar.svelte

@@ -1,4 +1,5 @@
 <script lang="ts">
+	import { getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 	import fileSaver from 'file-saver';
 	const { saveAs } = fileSaver;
@@ -9,6 +10,8 @@
 	import TagInput from '../common/Tags/TagInput.svelte';
 	import Tags from '../common/Tags.svelte';
 
+	const i18n = getContext('i18n');
+
 	export let initNewChat: Function;
 	export let title: string = $WEBUI_NAME;
 	export let shareEnabled: boolean = false;
@@ -26,7 +29,7 @@
 		const chat = (await getChatById(localStorage.token, $chatId)).chat;
 		console.log('share', chat);
 
-		toast.success('Redirecting you to OpenWebUI Community');
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
 		const url = 'https://openwebui.com';
 		// const url = 'http://localhost:5173';
 

+ 7 - 8
src/lib/components/layout/Sidebar.svelte

@@ -7,8 +7,7 @@
 	import { goto, invalidateAll } from '$app/navigation';
 	import { page } from '$app/stores';
 	import { user, chats, settings, showSettings, chatId, tags } from '$lib/stores';
-	import { onMount } from 'svelte';
-	import { getContext } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 
 	const i18n = getContext('i18n');
 
@@ -201,7 +200,7 @@
 					</div>
 
 					<div class="flex self-center">
-						<div class=" self-center font-medium text-sm">Prompts</div>
+						<div class=" self-center font-medium text-sm">{$i18n.t('Prompts')}</div>
 					</div>
 				</a>
 			</div>
@@ -229,7 +228,7 @@
 					</div>
 
 					<div class="flex self-center">
-						<div class=" self-center font-medium text-sm">Documents</div>
+						<div class=" self-center font-medium text-sm">{$i18n.t('Documents')}</div>
 					</div>
 				</a>
 			</div>
@@ -239,7 +238,7 @@
 			{#if !($settings.saveChatHistory ?? true)}
 				<div class="absolute z-40 w-full h-full bg-black/90 flex justify-center">
 					<div class=" text-left px-5 py-2">
-						<div class=" font-medium">Chat History is off for this browser.</div>
+						<div class=" font-medium">{$i18n.t('Chat History is off for this browser.')}</div>
 						<div class="text-xs mt-2">
 							When history is turned off, new chats on this browser won't appear in your history on
 							any of your devices. <span class=" font-semibold"
@@ -596,7 +595,7 @@
 												/>
 											</svg>
 										</div>
-										<div class=" self-center font-medium">Admin Panel</div>
+										<div class=" self-center font-medium">{$i18n.t('Admin Panel')}</div>
 									</button>
 								{/if}
 
@@ -628,7 +627,7 @@
 											/>
 										</svg>
 									</div>
-									<div class=" self-center font-medium">Settings</div>
+									<div class=" self-center font-medium">{$i18n.t('Settings')}</div>
 								</button>
 							</div>
 
@@ -662,7 +661,7 @@
 											/>
 										</svg>
 									</div>
-									<div class=" self-center font-medium">Sign Out</div>
+									<div class=" self-center font-medium">{$i18n.t('Sign Out')}</div>
 								</button>
 							</div>
 						</div>

+ 11 - 7
src/routes/(app)/+layout.svelte

@@ -1,10 +1,10 @@
 <script lang="ts">
 	import { toast } from 'svelte-sonner';
+	import { onMount, tick, getContext } from 'svelte';
 	import { openDB, deleteDB } from 'idb';
 	import fileSaver from 'file-saver';
 	const { saveAs } = fileSaver;
 
-	import { onMount, tick } from 'svelte';
 	import { goto } from '$app/navigation';
 
 	import { getOllamaModels, getOllamaVersion } from '$lib/apis/ollama';
@@ -35,6 +35,8 @@
 	import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
 	import ChangelogModal from '$lib/components/ChangelogModal.svelte';
 
+	const i18n = getContext('i18n');
+
 	let ollamaVersion = '';
 	let loaded = false;
 
@@ -266,12 +268,14 @@
 							</div>
 
 							<div class=" mt-4 text-center text-sm dark:text-gray-200 w-full">
-								Saving chat logs directly to your browser's storage is no longer supported. Please
-								take a moment to download and delete your chat logs by clicking the button below.
-								Don't worry, you can easily re-import your chat logs to the backend through <span
-									class="font-semibold dark:text-white">Settings > Chats > Import Chats</span
-								>. This ensures that your valuable conversations are securely saved to your backend
-								database. Thank you!
+								{$i18n.t(
+									"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through"
+								)}
+								<span class="font-semibold dark:text-white"
+									>{$i18n.t('Settings')} > {$i18n.t('Chats')} > {$i18n.t('Import Chats')}</span
+								>. {$i18n.t(
+									'This ensures that your valuable conversations are securely saved to your backend database. Thank you!'
+								)}
 							</div>
 
 							<div class=" mt-6 mx-auto relative group w-fit">

+ 9 - 7
src/routes/(app)/admin/+page.svelte

@@ -2,7 +2,7 @@
 	import { WEBUI_API_BASE_URL } from '$lib/constants';
 	import { WEBUI_NAME, config, user } from '$lib/stores';
 	import { goto } from '$app/navigation';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 
 	import { toast } from 'svelte-sonner';
 
@@ -11,6 +11,8 @@
 	import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
 	import SettingsModal from '$lib/components/admin/SettingsModal.svelte';
 
+	const i18n = getContext('i18n');
+
 	let loaded = false;
 	let users = [];
 
@@ -37,7 +39,7 @@
 		});
 		if (res) {
 			users = await getUsers(localStorage.token);
-			toast.success('Successfully updated');
+			toast.success($i18n.t('Successfully updated'));
 		}
 	};
 
@@ -115,7 +117,7 @@
 										/>
 									</svg>
 
-									<div class=" text-xs">Admin Settings</div>
+									<div class=" text-xs">{$i18n.t('Admin Settings')}</div>
 								</button>
 							</div>
 						</div>
@@ -131,10 +133,10 @@
 									class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"
 								>
 									<tr>
-										<th scope="col" class="px-3 py-2"> Role </th>
-										<th scope="col" class="px-3 py-2"> Name </th>
-										<th scope="col" class="px-3 py-2"> Email </th>
-										<th scope="col" class="px-3 py-2"> Action </th>
+										<th scope="col" class="px-3 py-2"> {$i18n.t('Role')} </th>
+										<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
+										<th scope="col" class="px-3 py-2"> {$i18n.t('Email')} </th>
+										<th scope="col" class="px-3 py-2"> {$i18n.t('Action')} </th>
 									</tr>
 								</thead>
 								<tbody>

+ 14 - 9
src/routes/(app)/documents/+page.svelte

@@ -3,7 +3,7 @@
 	import fileSaver from 'file-saver';
 	const { saveAs } = fileSaver;
 
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 	import { WEBUI_NAME, documents } from '$lib/stores';
 	import { createNewDoc, deleteDocByName, getDocs } from '$lib/apis/documents';
 
@@ -17,6 +17,9 @@
 	import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
 	import SettingsModal from '$lib/components/documents/SettingsModal.svelte';
 	import AddDocModal from '$lib/components/documents/AddDocModal.svelte';
+
+	const i18n = getContext('i18n');
+
 	let importFiles = '';
 
 	let inputFiles = '';
@@ -188,7 +191,7 @@
 		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
 			<div class="mb-6">
 				<div class="flex justify-between items-center">
-					<div class=" text-2xl font-semibold self-center">My Documents</div>
+					<div class=" text-2xl font-semibold self-center">{$i18n.t('My Documents')}</div>
 
 					<div>
 						<button
@@ -211,7 +214,7 @@
 								/>
 							</svg>
 
-							<div class=" text-xs">Document Settings</div>
+							<div class=" text-xs">{$i18n.t('Document Settings')}</div>
 						</button>
 					</div>
 				</div>
@@ -274,7 +277,7 @@
 					on:dragleave={onDragLeave}
 				>
 					<div class="  pointer-events-none">
-						<div class="text-center dark:text-white text-2xl font-semibold z-50">Add Files</div>
+						<div class="text-center dark:text-white text-2xl font-semibold z-50">{$i18n.t('Add Files')}</div>
 
 						<div class=" mt-2 text-center text-sm dark:text-gray-200 w-full">
 							Drop any files here to add to my documents
@@ -314,7 +317,7 @@
 								// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
 							}}
 						>
-							<div class=" text-xs font-medium self-center line-clamp-1">all</div>
+							<div class=" text-xs font-medium self-center line-clamp-1">{$i18n.t('all')}</div>
 						</button>
 
 						{#each tags as tag}
@@ -344,7 +347,7 @@
 										// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
 									}}
 								>
-									<div class=" text-xs font-medium self-center line-clamp-1">add tags</div>
+									<div class=" text-xs font-medium self-center line-clamp-1">{$i18n.t('add tags')}</div>
 								</button> -->
 
 								<button
@@ -354,7 +357,9 @@
 										// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
 									}}
 								>
-									<div class=" text-xs font-medium self-center line-clamp-1">delete</div>
+									<div class=" text-xs font-medium self-center line-clamp-1">
+										{$i18n.t('delete')}
+									</div>
 								</button>
 							</div>
 						</div>
@@ -562,7 +567,7 @@
 							document.getElementById('documents-import-input')?.click();
 						}}
 					>
-						<div class=" self-center mr-2 font-medium">Import Documents Mapping</div>
+						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Documents Mapping')}</div>
 
 						<div class=" self-center">
 							<svg
@@ -589,7 +594,7 @@
 							saveAs(blob, `documents-mapping-export-${Date.now()}.json`);
 						}}
 					>
-						<div class=" self-center mr-2 font-medium">Export Documents Mapping</div>
+						<div class=" self-center mr-2 font-medium">{$i18n.t('Export Documents Mapping')}</div>
 
 						<div class=" self-center">
 							<svg

+ 14 - 12
src/routes/(app)/modelfiles/+page.svelte

@@ -3,7 +3,7 @@
 	import fileSaver from 'file-saver';
 	const { saveAs } = fileSaver;
 
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 
 	import { WEBUI_NAME, modelfiles, settings, user } from '$lib/stores';
 	import { createModel, deleteModel } from '$lib/apis/ollama';
@@ -14,6 +14,8 @@
 	} from '$lib/apis/modelfiles';
 	import { goto } from '$app/navigation';
 
+	const i18n = getContext('i18n');
+
 	let localModelfiles = [];
 	let importFiles;
 
@@ -23,7 +25,7 @@
 		success = await deleteModel(localStorage.token, tagName);
 
 		if (success) {
-			toast.success(`Deleted ${tagName}`);
+			toast.success($i18n.t(`Deleted {tagName}`, { tagName }));
 		}
 
 		return success;
@@ -36,7 +38,7 @@
 	};
 
 	const shareModelfile = async (modelfile) => {
-		toast.success('Redirecting you to OpenWebUI Community');
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
 
 		const url = 'https://openwebui.com';
 
@@ -78,7 +80,7 @@
 <div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
 	<div class="flex flex-col justify-between w-full overflow-y-auto">
 		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
-			<div class=" text-2xl font-semibold mb-3">My Modelfiles</div>
+			<div class=" text-2xl font-semibold mb-3">{$i18n.t('My Modelfiles')}</div>
 
 			<a class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2" href="/modelfiles/create">
 				<div class=" self-center w-10">
@@ -101,8 +103,8 @@
 				</div>
 
 				<div class=" self-center">
-					<div class=" font-bold">Create a modelfile</div>
-					<div class=" text-sm">Customize Ollama models for a specific purpose</div>
+					<div class=" font-bold">{$i18n.t('Create a modelfile')}</div>
+					<div class=" text-sm">{$i18n.t('Customize Ollama models for a specific purpose')}</div>
 				</div>
 			</a>
 
@@ -266,7 +268,7 @@
 							document.getElementById('modelfiles-import-input')?.click();
 						}}
 					>
-						<div class=" self-center mr-2 font-medium">Import Modelfiles</div>
+						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Modelfiles')}</div>
 
 						<div class=" self-center">
 							<svg
@@ -290,7 +292,7 @@
 							saveModelfiles($modelfiles);
 						}}
 					>
-						<div class=" self-center mr-2 font-medium">Export Modelfiles</div>
+						<div class=" self-center mr-2 font-medium">{$i18n.t('Export Modelfiles')}</div>
 
 						<div class=" self-center">
 							<svg
@@ -331,7 +333,7 @@
 									await modelfiles.set(await getModelfiles(localStorage.token));
 								}}
 							>
-								<div class=" self-center mr-2 font-medium">Sync All</div>
+								<div class=" self-center mr-2 font-medium">{$i18n.t('Sync All')}</div>
 
 								<div class=" self-center">
 									<svg
@@ -382,7 +384,7 @@
 			</div>
 
 			<div class=" my-16">
-				<div class=" text-2xl font-semibold mb-3">Made by OpenWebUI Community</div>
+				<div class=" text-2xl font-semibold mb-3">{$i18n.t('Made by OpenWebUI Community')}</div>
 
 				<a
 					class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2"
@@ -409,8 +411,8 @@
 					</div>
 
 					<div class=" self-center">
-						<div class=" font-bold">Discover a modelfile</div>
-						<div class=" text-sm">Discover, download, and explore model presets</div>
+						<div class=" font-bold">{$i18n.t('Discover a modelfile')}</div>
+						<div class=" text-sm">{$i18n.t('Discover, download, and explore model presets')}</div>
 					</div>
 				</a>
 			</div>

+ 32 - 26
src/routes/(app)/modelfiles/create/+page.svelte

@@ -6,10 +6,12 @@
 
 	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
 	import { splitStream } from '$lib/utils';
-	import { onMount, tick } from 'svelte';
+	import { onMount, tick, getContext } from 'svelte';
 	import { createModel } from '$lib/apis/ollama';
 	import { createNewModelfile, getModelfileByTagName, getModelfiles } from '$lib/apis/modelfiles';
 
+	const i18n = getContext('i18n');
+
 	let loading = false;
 
 	let filesInputElement;
@@ -349,7 +351,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 				}}
 			/>
 
-			<div class=" text-2xl font-semibold mb-6">My Modelfiles</div>
+			<div class=" text-2xl font-semibold mb-6">{$i18n.t('My Modelfiles')}</div>
 
 			<button
 				class="flex space-x-1"
@@ -371,7 +373,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 						/>
 					</svg>
 				</div>
-				<div class=" self-center font-medium text-sm">Back</div>
+				<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
 			</button>
 			<hr class="my-3 dark:border-gray-700" />
 
@@ -418,7 +420,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 
 				<div class="my-2 flex space-x-2">
 					<div class="flex-1">
-						<div class=" text-sm font-semibold mb-2">Name*</div>
+						<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
 
 						<div>
 							<input
@@ -431,7 +433,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 					</div>
 
 					<div class="flex-1">
-						<div class=" text-sm font-semibold mb-2">Model Tag Name*</div>
+						<div class=" text-sm font-semibold mb-2">{$i18n.t('Model Tag Name')}*</div>
 
 						<div>
 							<input
@@ -445,7 +447,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 				</div>
 
 				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">Description*</div>
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}*</div>
 
 					<div>
 						<input
@@ -459,7 +461,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 
 				<div class="my-2">
 					<div class="flex w-full justify-between">
-						<div class=" self-center text-sm font-semibold">Modelfile</div>
+						<div class=" self-center text-sm font-semibold">{$i18n.t('Modelfile')}</div>
 
 						<button
 							class="p-1 px-3 text-xs flex rounded transition"
@@ -469,9 +471,9 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 							}}
 						>
 							{#if raw}
-								<span class="ml-2 self-center"> Raw Format </span>
+								<span class="ml-2 self-center"> {$i18n.t('Raw Format')} </span>
 							{:else}
-								<span class="ml-2 self-center"> Builder Mode </span>
+								<span class="ml-2 self-center"> {$i18n.t('Builder Mode')} </span>
 							{/if}
 						</button>
 					</div>
@@ -480,7 +482,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 
 					{#if raw}
 						<div class="mt-2">
-							<div class=" text-xs font-semibold mb-2">Content*</div>
+							<div class=" text-xs font-semibold mb-2">{$i18n.t('Content')}*</div>
 
 							<div>
 								<textarea
@@ -493,12 +495,13 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 							</div>
 
 							<div class="text-xs text-gray-400 dark:text-gray-500">
-								Not sure what to write? Switch to <button
+								{$i18n.t('Not sure what to write? Switch to')}
+								<button
 									class="text-gray-500 dark:text-gray-300 font-medium cursor-pointer"
 									type="button"
 									on:click={() => {
 										raw = !raw;
-									}}>Builder Mode</button
+									}}>{$i18n.t('Builder Mode')}</button
 								>
 								or
 								<a
@@ -506,13 +509,13 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 									href="https://openwebui.com"
 									target="_blank"
 								>
-									Click here to check other modelfiles.
+									{$i18n.t('Click here to check other modelfiles.')}
 								</a>
 							</div>
 						</div>
 					{:else}
 						<div class="my-2">
-							<div class=" text-xs font-semibold mb-2">From (Base Model)*</div>
+							<div class=" text-xs font-semibold mb-2">{$i18n.t('From (Base Model)')}*</div>
 
 							<div>
 								<input
@@ -524,16 +527,17 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 							</div>
 
 							<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
-								To access the available model names for downloading, <a
+								{$i18n.t('To access the available model names for downloading,')}
+								<a
 									class=" text-gray-500 dark:text-gray-300 font-medium"
 									href="https://ollama.com/library"
-									target="_blank">click here.</a
+									target="_blank">{$i18n.t('click here.')}</a
 								>
 							</div>
 						</div>
 
 						<div class="my-1">
-							<div class=" text-xs font-semibold mb-2">System Prompt</div>
+							<div class=" text-xs font-semibold mb-2">{$i18n.t('System Prompt')}</div>
 
 							<div>
 								<textarea
@@ -546,7 +550,9 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 						</div>
 
 						<div class="flex w-full justify-between">
-							<div class=" self-center text-sm font-semibold">Modelfile Advanced Settings</div>
+							<div class=" self-center text-sm font-semibold">
+								{$i18n.t('Modelfile Advanced Settings')}
+							</div>
 
 							<button
 								class="p-1 px-3 text-xs flex rounded transition"
@@ -556,16 +562,16 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 								}}
 							>
 								{#if advanced}
-									<span class="ml-2 self-center"> Custom </span>
+									<span class="ml-2 self-center">{$i18n.t(' Custom ')}</span>
 								{:else}
-									<span class="ml-2 self-center"> Default </span>
+									<span class="ml-2 self-center">{$i18n.t(' Default ')}</span>
 								{/if}
 							</button>
 						</div>
 
 						{#if advanced}
 							<div class="my-2">
-								<div class=" text-xs font-semibold mb-2">Template</div>
+								<div class=" text-xs font-semibold mb-2">{$i18n.t('Template')}</div>
 
 								<div>
 									<textarea
@@ -578,7 +584,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 							</div>
 
 							<div class="my-2">
-								<div class=" text-xs font-semibold mb-2">Parameters</div>
+								<div class=" text-xs font-semibold mb-2">{$i18n.t('Parameters')}</div>
 
 								<div>
 									<AdvancedParams bind:options />
@@ -590,7 +596,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 
 				<div class="my-2">
 					<div class="flex w-full justify-between mb-2">
-						<div class=" self-center text-sm font-semibold">Prompt suggestions</div>
+						<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
 
 						<button
 							class="p-1 px-3 text-xs flex rounded transition"
@@ -647,7 +653,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 				</div>
 
 				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">Categories</div>
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Categories')}</div>
 
 					<div class="grid grid-cols-4">
 						{#each Object.keys(categories) as category}
@@ -661,7 +667,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 
 				{#if pullProgress !== null}
 					<div class="my-2">
-						<div class=" text-sm font-semibold mb-2">Pull Progress</div>
+						<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>
 						<div class="w-full rounded-full dark:bg-gray-800">
 							<div
 								class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
@@ -684,7 +690,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
 						type="submit"
 						disabled={loading}
 					>
-						<div class=" self-center font-medium">Save & Create</div>
+						<div class=" self-center font-medium">{$i18n.t('Save & Create')}</div>
 
 						{#if loading}
 							<div class="ml-1.5 self-center">

+ 14 - 12
src/routes/(app)/modelfiles/edit/+page.svelte

@@ -3,7 +3,7 @@
 	import { toast } from 'svelte-sonner';
 	import { goto } from '$app/navigation';
 
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 	import { page } from '$app/stores';
 
 	import { settings, user, config, modelfiles } from '$lib/stores';
@@ -14,6 +14,8 @@
 
 	import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
 
+	const i18n = getContext('i18n');
+
 	let loading = false;
 
 	let filesInputElement;
@@ -248,7 +250,7 @@
 				}}
 			/>
 
-			<div class=" text-2xl font-semibold mb-6">My Modelfiles</div>
+			<div class=" text-2xl font-semibold mb-6">{$i18n.t('My Modelfiles')}</div>
 
 			<button
 				class="flex space-x-1"
@@ -270,7 +272,7 @@
 						/>
 					</svg>
 				</div>
-				<div class=" self-center font-medium text-sm">Back</div>
+				<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
 			</button>
 			<hr class="my-3 dark:border-gray-700" />
 
@@ -317,7 +319,7 @@
 
 				<div class="my-2 flex space-x-2">
 					<div class="flex-1">
-						<div class=" text-sm font-semibold mb-2">Name*</div>
+						<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
 
 						<div>
 							<input
@@ -330,7 +332,7 @@
 					</div>
 
 					<div class="flex-1">
-						<div class=" text-sm font-semibold mb-2">Model Tag Name*</div>
+						<div class=" text-sm font-semibold mb-2">{$i18n.t('Model Tag Name')}*</div>
 
 						<div>
 							<input
@@ -345,7 +347,7 @@
 				</div>
 
 				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">Description*</div>
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}*</div>
 
 					<div>
 						<input
@@ -359,13 +361,13 @@
 
 				<div class="my-2">
 					<div class="flex w-full justify-between">
-						<div class=" self-center text-sm font-semibold">Modelfile</div>
+						<div class=" self-center text-sm font-semibold">{$i18n.t('Modelfile')}</div>
 					</div>
 
 					<!-- <div class=" text-sm font-semibold mb-2"></div> -->
 
 					<div class="mt-2">
-						<div class=" text-xs font-semibold mb-2">Content*</div>
+						<div class=" text-xs font-semibold mb-2">{$i18n.t('Content')}*</div>
 
 						<div>
 							<textarea
@@ -381,7 +383,7 @@
 
 				<div class="my-2">
 					<div class="flex w-full justify-between mb-2">
-						<div class=" self-center text-sm font-semibold">Prompt suggestions</div>
+						<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
 
 						<button
 							class="p-1 px-3 text-xs flex rounded transition"
@@ -438,7 +440,7 @@
 				</div>
 
 				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">Categories</div>
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Categories')}</div>
 
 					<div class="grid grid-cols-4">
 						{#each Object.keys(categories) as category}
@@ -453,7 +455,7 @@
 
 				{#if pullProgress !== null}
 					<div class="my-2">
-						<div class=" text-sm font-semibold mb-2">Pull Progress</div>
+						<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>
 						<div class="w-full rounded-full dark:bg-gray-800">
 							<div
 								class="dark:bg-gray-600 text-xs font-medium text-blue-100 text-center p-0.5 leading-none rounded-full"
@@ -476,7 +478,7 @@
 						type="submit"
 						disabled={loading}
 					>
-						<div class=" self-center font-medium">Save & Update</div>
+						<div class=" self-center font-medium">{$i18n.t('Save & Update')}</div>
 
 						{#if loading}
 							<div class="ml-1.5 self-center">

+ 10 - 8
src/routes/(app)/prompts/+page.svelte

@@ -3,17 +3,19 @@
 	import fileSaver from 'file-saver';
 	const { saveAs } = fileSaver;
 
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 	import { WEBUI_NAME, prompts } from '$lib/stores';
 	import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
 	import { error } from '@sveltejs/kit';
 	import { goto } from '$app/navigation';
 
+	const i18n = getContext('i18n');
+
 	let importFiles = '';
 	let query = '';
 
 	const sharePrompt = async (prompt) => {
-		toast.success('Redirecting you to OpenWebUI Community');
+		toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
 
 		const url = 'https://openwebui.com';
 
@@ -46,7 +48,7 @@
 	<div class="flex flex-col justify-between w-full overflow-y-auto">
 		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
 			<div class="mb-6 flex justify-between items-center">
-				<div class=" text-2xl font-semibold self-center">My Prompts</div>
+				<div class=" text-2xl font-semibold self-center">{$i18n.t('My Prompts')}</div>
 			</div>
 
 			<div class=" flex w-full space-x-2">
@@ -245,7 +247,7 @@
 							document.getElementById('prompts-import-input')?.click();
 						}}
 					>
-						<div class=" self-center mr-2 font-medium">Import Prompts</div>
+						<div class=" self-center mr-2 font-medium">{$i18n.t('Import Prompts')}</div>
 
 						<div class=" self-center">
 							<svg
@@ -273,7 +275,7 @@
 							saveAs(blob, `prompts-export-${Date.now()}.json`);
 						}}
 					>
-						<div class=" self-center mr-2 font-medium">Export Prompts</div>
+						<div class=" self-center mr-2 font-medium">{$i18n.t('Export Prompts')}</div>
 
 						<div class=" self-center">
 							<svg
@@ -302,7 +304,7 @@
 			</div>
 
 			<div class=" my-16">
-				<div class=" text-2xl font-semibold mb-3">Made by OpenWebUI Community</div>
+				<div class=" text-2xl font-semibold mb-3">{$i18n.t('Made by OpenWebUI Community')}</div>
 
 				<a
 					class=" flex space-x-4 cursor-pointer w-full mb-3 px-3 py-2"
@@ -329,8 +331,8 @@
 					</div>
 
 					<div class=" self-center">
-						<div class=" font-bold">Discover a prompt</div>
-						<div class=" text-sm">Discover, download, and explore custom prompts</div>
+						<div class=" font-bold">{$i18n.t('Discover a prompt')}</div>
+						<div class=" text-sm">{$i18n.t('Discover, download, and explore custom prompts')}</div>
 					</div>
 				</a>
 			</div>

+ 9 - 7
src/routes/(app)/prompts/create/+page.svelte

@@ -3,10 +3,12 @@
 
 	import { goto } from '$app/navigation';
 	import { prompts } from '$lib/stores';
-	import { onMount, tick } from 'svelte';
+	import { onMount, tick, getContext } from 'svelte';
 
 	import { createNewPrompt, getPrompts } from '$lib/apis/prompts';
 
+	const i18n = getContext('i18n');
+
 	let loading = false;
 
 	// ///////////
@@ -92,7 +94,7 @@
 <div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
 	<div class=" flex flex-col justify-between w-full overflow-y-auto">
 		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
-			<div class=" text-2xl font-semibold mb-6">My Prompts</div>
+			<div class=" text-2xl font-semibold mb-6">{$i18n.t('My Prompts')}</div>
 
 			<button
 				class="flex space-x-1"
@@ -114,7 +116,7 @@
 						/>
 					</svg>
 				</div>
-				<div class=" self-center font-medium text-sm">Back</div>
+				<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
 			</button>
 			<hr class="my-3 dark:border-gray-700" />
 
@@ -125,7 +127,7 @@
 				}}
 			>
 				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">Title*</div>
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Title')}*</div>
 
 					<div>
 						<input
@@ -138,7 +140,7 @@
 				</div>
 
 				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">Command*</div>
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Command')}*</div>
 
 					<div class="flex items-center mb-1">
 						<div
@@ -168,7 +170,7 @@
 
 				<div class="my-2">
 					<div class="flex w-full justify-between">
-						<div class=" self-center text-sm font-semibold">Prompt Content*</div>
+						<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt Content')}*</div>
 					</div>
 
 					<div class="mt-2">
@@ -207,7 +209,7 @@
 						type="submit"
 						disabled={loading}
 					>
-						<div class=" self-center font-medium">Save & Create</div>
+						<div class=" self-center font-medium">{$i18n.t('Save & Create')}</div>
 
 						{#if loading}
 							<div class="ml-1.5 self-center">

+ 9 - 7
src/routes/(app)/prompts/edit/+page.svelte

@@ -3,7 +3,9 @@
 
 	import { goto } from '$app/navigation';
 	import { prompts } from '$lib/stores';
-	import { onMount, tick } from 'svelte';
+	import { onMount, tick, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
 
 	import { getPrompts, updatePromptByCommand } from '$lib/apis/prompts';
 	import { page } from '$app/stores';
@@ -74,7 +76,7 @@
 <div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
 	<div class="flex flex-col justify-between w-full overflow-y-auto">
 		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
-			<div class=" text-2xl font-semibold mb-6">My Prompts</div>
+			<div class=" text-2xl font-semibold mb-6">{$i18n.t('My Prompts')}</div>
 
 			<button
 				class="flex space-x-1"
@@ -96,7 +98,7 @@
 						/>
 					</svg>
 				</div>
-				<div class=" self-center font-medium text-sm">Back</div>
+				<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
 			</button>
 			<hr class="my-3 dark:border-gray-700" />
 
@@ -107,7 +109,7 @@
 				}}
 			>
 				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">Title*</div>
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Title')}*</div>
 
 					<div>
 						<input
@@ -120,7 +122,7 @@
 				</div>
 
 				<div class="my-2">
-					<div class=" text-sm font-semibold mb-2">Command*</div>
+					<div class=" text-sm font-semibold mb-2">{$i18n.t('Command')}*</div>
 
 					<div class="flex items-center mb-1">
 						<div
@@ -151,7 +153,7 @@
 
 				<div class="my-2">
 					<div class="flex w-full justify-between">
-						<div class=" self-center text-sm font-semibold">Prompt Content*</div>
+						<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt Content')}*</div>
 					</div>
 
 					<div class="mt-2">
@@ -184,7 +186,7 @@
 						type="submit"
 						disabled={loading}
 					>
-						<div class=" self-center font-medium">Save & Update</div>
+						<div class=" self-center font-medium">{$i18n.t('Save & Update')}</div>
 
 						{#if loading}
 							<div class="ml-1.5 self-center">

+ 7 - 5
src/routes/auth/+page.svelte

@@ -3,9 +3,11 @@
 	import { userSignIn, userSignUp } from '$lib/apis/auths';
 	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
 	import { WEBUI_NAME, config, user } from '$lib/stores';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 
+	const i18n = getContext('i18n');
+
 	let loaded = false;
 	let mode = 'signin';
 
@@ -16,7 +18,7 @@
 	const setSessionUser = async (sessionUser) => {
 		if (sessionUser) {
 			console.log(sessionUser);
-			toast.success(`You're now logged in.`);
+			toast.success($i18n.t(`You're now logged in.`));
 			localStorage.token = sessionUser.token;
 			await user.set(sessionUser);
 			goto('/');
@@ -109,7 +111,7 @@
 					<div class="flex flex-col mt-4">
 						{#if mode === 'signup'}
 							<div>
-								<div class=" text-sm font-semibold text-left mb-1">Name</div>
+								<div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Name')}</div>
 								<input
 									bind:value={name}
 									type="text"
@@ -124,7 +126,7 @@
 						{/if}
 
 						<div class="mb-2">
-							<div class=" text-sm font-semibold text-left mb-1">Email</div>
+							<div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Email')}</div>
 							<input
 								bind:value={email}
 								type="email"
@@ -136,7 +138,7 @@
 						</div>
 
 						<div>
-							<div class=" text-sm font-semibold text-left mb-1">Password</div>
+							<div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Password')}</div>
 							<input
 								bind:value={password}
 								type="password"

+ 13 - 8
src/routes/error/+page.svelte

@@ -1,7 +1,9 @@
 <script>
 	import { goto } from '$app/navigation';
 	import { WEBUI_NAME, config } from '$lib/stores';
-	import { onMount } from 'svelte';
+	import { onMount, getContext } from 'svelte';
+
+	const i18n = getContext('i18n');
 
 	let loaded = false;
 
@@ -19,22 +21,25 @@
 		<div class="absolute rounded-xl w-full h-full backdrop-blur flex justify-center">
 			<div class="m-auto pb-44 flex flex-col justify-center">
 				<div class="max-w-md">
-					<div class="text-center text-2xl font-medium z-50">{$WEBUI_NAME} Backend Required</div>
+					<div class="text-center text-2xl font-medium z-50">
+						{$i18n.t('{{webui_name}} Backend Required', { webui_name: $WEBUI_NAME })}
+					</div>
 
 					<div class=" mt-4 text-center text-sm w-full">
-						Oops! You're using an unsupported method (frontend only). Please serve the WebUI from
-						the backend.
+						{$i18n.t(
+							"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend."
+						)}
 
 						<br class=" " />
 						<br class=" " />
 						<a
 							class=" font-semibold underline"
 							href="https://github.com/open-webui/open-webui#how-to-install-"
-							target="_blank">See readme.md for instructions</a
+							target="_blank">{$i18n.t('See readme.md for instructions')}</a
 						>
-						or
+						{$i18n.t('or')}
 						<a class=" font-semibold underline" href="https://discord.gg/5rJgQTnV4s" target="_blank"
-							>join our Discord for help.</a
+							>{$i18n.t('join our Discord for help.')}</a
 						>
 					</div>
 
@@ -45,7 +50,7 @@
 								location.href = '/';
 							}}
 						>
-							Check Again
+							{$i18n.t('Check Again')}
 						</button>
 					</div>
 				</div>