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