Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
6313a982
Unverified
Commit
6313a982
authored
Mar 20, 2024
by
Timothy Jaeryang Baek
Committed by
GitHub
Mar 20, 2024
Browse files
Merge branch 'dev' into main
parents
b22ed647
ae347bb5
Changes
26
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1258 additions
and
14 deletions
+1258
-14
backend/apps/ollama/main.py
backend/apps/ollama/main.py
+1
-1
backend/apps/web/main.py
backend/apps/web/main.py
+2
-0
backend/apps/web/routers/auths.py
backend/apps/web/routers/auths.py
+13
-1
backend/config.py
backend/config.py
+7
-1
backend/constants.py
backend/constants.py
+8
-1
backend/data/config.json
backend/data/config.json
+1
-1
backend/main.py
backend/main.py
+27
-1
backend/utils/webhook.py
backend/utils/webhook.py
+20
-0
src/lib/apis/index.ts
src/lib/apis/index.ts
+57
-0
src/lib/components/admin/Settings/General.svelte
src/lib/components/admin/Settings/General.svelte
+26
-1
src/lib/components/chat/MessageInput.svelte
src/lib/components/chat/MessageInput.svelte
+1
-1
src/lib/components/chat/Messages/ResponseMessage.svelte
src/lib/components/chat/Messages/ResponseMessage.svelte
+1
-1
src/lib/components/chat/Messages/UserMessage.svelte
src/lib/components/chat/Messages/UserMessage.svelte
+1
-1
src/lib/components/documents/AddDocModal.svelte
src/lib/components/documents/AddDocModal.svelte
+1
-1
src/lib/i18n/locales/ca-ES/translation.json
src/lib/i18n/locales/ca-ES/translation.json
+363
-0
src/lib/i18n/locales/de-DE/translation.json
src/lib/i18n/locales/de-DE/translation.json
+1
-1
src/lib/i18n/locales/en-US/translation.json
src/lib/i18n/locales/en-US/translation.json
+1
-1
src/lib/i18n/locales/es-ES/translation.json
src/lib/i18n/locales/es-ES/translation.json
+363
-0
src/lib/i18n/locales/fa-IR/translation.json
src/lib/i18n/locales/fa-IR/translation.json
+1
-1
src/lib/i18n/locales/fr-CA/translation.json
src/lib/i18n/locales/fr-CA/translation.json
+363
-0
No files found.
backend/apps/ollama/main.py
View file @
6313a982
...
@@ -709,7 +709,7 @@ class GenerateChatCompletionForm(BaseModel):
...
@@ -709,7 +709,7 @@ class GenerateChatCompletionForm(BaseModel):
format
:
Optional
[
str
]
=
None
format
:
Optional
[
str
]
=
None
options
:
Optional
[
dict
]
=
None
options
:
Optional
[
dict
]
=
None
template
:
Optional
[
str
]
=
None
template
:
Optional
[
str
]
=
None
stream
:
Optional
[
bool
]
=
Tru
e
stream
:
Optional
[
bool
]
=
Non
e
keep_alive
:
Optional
[
Union
[
int
,
str
]]
=
None
keep_alive
:
Optional
[
Union
[
int
,
str
]]
=
None
...
...
backend/apps/web/main.py
View file @
6313a982
...
@@ -19,6 +19,7 @@ from config import (
...
@@ -19,6 +19,7 @@ from config import (
DEFAULT_USER_ROLE
,
DEFAULT_USER_ROLE
,
ENABLE_SIGNUP
,
ENABLE_SIGNUP
,
USER_PERMISSIONS
,
USER_PERMISSIONS
,
WEBHOOK_URL
,
)
)
app
=
FastAPI
()
app
=
FastAPI
()
...
@@ -32,6 +33,7 @@ app.state.DEFAULT_MODELS = DEFAULT_MODELS
...
@@ -32,6 +33,7 @@ app.state.DEFAULT_MODELS = DEFAULT_MODELS
app
.
state
.
DEFAULT_PROMPT_SUGGESTIONS
=
DEFAULT_PROMPT_SUGGESTIONS
app
.
state
.
DEFAULT_PROMPT_SUGGESTIONS
=
DEFAULT_PROMPT_SUGGESTIONS
app
.
state
.
DEFAULT_USER_ROLE
=
DEFAULT_USER_ROLE
app
.
state
.
DEFAULT_USER_ROLE
=
DEFAULT_USER_ROLE
app
.
state
.
USER_PERMISSIONS
=
USER_PERMISSIONS
app
.
state
.
USER_PERMISSIONS
=
USER_PERMISSIONS
app
.
state
.
WEBHOOK_URL
=
WEBHOOK_URL
app
.
add_middleware
(
app
.
add_middleware
(
...
...
backend/apps/web/routers/auths.py
View file @
6313a982
...
@@ -27,7 +27,8 @@ from utils.utils import (
...
@@ -27,7 +27,8 @@ from utils.utils import (
create_token
,
create_token
,
)
)
from
utils.misc
import
parse_duration
,
validate_email_format
from
utils.misc
import
parse_duration
,
validate_email_format
from
constants
import
ERROR_MESSAGES
from
utils.webhook
import
post_webhook
from
constants
import
ERROR_MESSAGES
,
WEBHOOK_MESSAGES
router
=
APIRouter
()
router
=
APIRouter
()
...
@@ -155,6 +156,17 @@ async def signup(request: Request, form_data: SignupForm):
...
@@ -155,6 +156,17 @@ async def signup(request: Request, form_data: SignupForm):
)
)
# response.set_cookie(key='token', value=token, httponly=True)
# response.set_cookie(key='token', value=token, httponly=True)
if
request
.
app
.
state
.
WEBHOOK_URL
:
post_webhook
(
request
.
app
.
state
.
WEBHOOK_URL
,
WEBHOOK_MESSAGES
.
USER_SIGNUP
(
user
.
name
),
{
"action"
:
"signup"
,
"message"
:
WEBHOOK_MESSAGES
.
USER_SIGNUP
(
user
.
name
),
"user"
:
user
.
model_dump_json
(
exclude_none
=
True
),
},
)
return
{
return
{
"token"
:
token
,
"token"
:
token
,
"token_type"
:
"Bearer"
,
"token_type"
:
"Bearer"
,
...
...
backend/config.py
View file @
6313a982
...
@@ -290,13 +290,19 @@ DEFAULT_PROMPT_SUGGESTIONS = (
...
@@ -290,13 +290,19 @@ DEFAULT_PROMPT_SUGGESTIONS = (
DEFAULT_USER_ROLE
=
os
.
getenv
(
"DEFAULT_USER_ROLE"
,
"pending"
)
DEFAULT_USER_ROLE
=
os
.
getenv
(
"DEFAULT_USER_ROLE"
,
"pending"
)
USER_PERMISSIONS
=
{
"chat"
:
{
"deletion"
:
True
}}
USER_PERMISSIONS_CHAT_DELETION
=
(
os
.
environ
.
get
(
"USER_PERMISSIONS_CHAT_DELETION"
,
"True"
).
lower
()
==
"true"
)
USER_PERMISSIONS
=
{
"chat"
:
{
"deletion"
:
USER_PERMISSIONS_CHAT_DELETION
}}
MODEL_FILTER_ENABLED
=
os
.
environ
.
get
(
"MODEL_FILTER_ENABLED"
,
False
)
MODEL_FILTER_ENABLED
=
os
.
environ
.
get
(
"MODEL_FILTER_ENABLED"
,
False
)
MODEL_FILTER_LIST
=
os
.
environ
.
get
(
"MODEL_FILTER_LIST"
,
""
)
MODEL_FILTER_LIST
=
os
.
environ
.
get
(
"MODEL_FILTER_LIST"
,
""
)
MODEL_FILTER_LIST
=
[
model
.
strip
()
for
model
in
MODEL_FILTER_LIST
.
split
(
";"
)]
MODEL_FILTER_LIST
=
[
model
.
strip
()
for
model
in
MODEL_FILTER_LIST
.
split
(
";"
)]
WEBHOOK_URL
=
os
.
environ
.
get
(
"WEBHOOK_URL"
,
""
)
####################################
####################################
# WEBUI_VERSION
# WEBUI_VERSION
...
...
backend/constants.py
View file @
6313a982
...
@@ -5,6 +5,13 @@ class MESSAGES(str, Enum):
...
@@ -5,6 +5,13 @@ class MESSAGES(str, Enum):
DEFAULT
=
lambda
msg
=
""
:
f
"
{
msg
if
msg
else
''
}
"
DEFAULT
=
lambda
msg
=
""
:
f
"
{
msg
if
msg
else
''
}
"
class
WEBHOOK_MESSAGES
(
str
,
Enum
):
DEFAULT
=
lambda
msg
=
""
:
f
"
{
msg
if
msg
else
''
}
"
USER_SIGNUP
=
lambda
username
=
""
:
(
f
"New user signed up:
{
username
}
"
if
username
else
"New user signed up"
)
class
ERROR_MESSAGES
(
str
,
Enum
):
class
ERROR_MESSAGES
(
str
,
Enum
):
def
__str__
(
self
)
->
str
:
def
__str__
(
self
)
->
str
:
return
super
().
__str__
()
return
super
().
__str__
()
...
@@ -46,7 +53,7 @@ class ERROR_MESSAGES(str, Enum):
...
@@ -46,7 +53,7 @@ class ERROR_MESSAGES(str, Enum):
PANDOC_NOT_INSTALLED
=
"Pandoc is not installed on the server. Please contact your administrator for assistance."
PANDOC_NOT_INSTALLED
=
"Pandoc is not installed on the server. Please contact your administrator for assistance."
INCORRECT_FORMAT
=
(
INCORRECT_FORMAT
=
(
lambda
err
=
""
:
f
"Invalid format. Please use the correct format
{
err
if
err
else
''
}
"
lambda
err
=
""
:
f
"Invalid format. Please use the correct format
{
err
}
"
)
)
RATE_LIMIT_EXCEEDED
=
"API rate limit exceeded"
RATE_LIMIT_EXCEEDED
=
"API rate limit exceeded"
...
...
backend/data/config.json
View file @
6313a982
{
{
"version"
:
"0.0.1"
,
"version"
:
0
,
"ui"
:
{
"ui"
:
{
"prompt_suggestions"
:
[
"prompt_suggestions"
:
[
{
{
...
...
backend/main.py
View file @
6313a982
...
@@ -38,6 +38,7 @@ from config import (
...
@@ -38,6 +38,7 @@ from config import (
FRONTEND_BUILD_DIR
,
FRONTEND_BUILD_DIR
,
MODEL_FILTER_ENABLED
,
MODEL_FILTER_ENABLED
,
MODEL_FILTER_LIST
,
MODEL_FILTER_LIST
,
WEBHOOK_URL
,
)
)
from
constants
import
ERROR_MESSAGES
from
constants
import
ERROR_MESSAGES
...
@@ -58,6 +59,9 @@ app = FastAPI(docs_url="/docs" if ENV == "dev" else None, redoc_url=None)
...
@@ -58,6 +59,9 @@ app = FastAPI(docs_url="/docs" if ENV == "dev" else None, redoc_url=None)
app
.
state
.
MODEL_FILTER_ENABLED
=
MODEL_FILTER_ENABLED
app
.
state
.
MODEL_FILTER_ENABLED
=
MODEL_FILTER_ENABLED
app
.
state
.
MODEL_FILTER_LIST
=
MODEL_FILTER_LIST
app
.
state
.
MODEL_FILTER_LIST
=
MODEL_FILTER_LIST
app
.
state
.
WEBHOOK_URL
=
WEBHOOK_URL
origins
=
[
"*"
]
origins
=
[
"*"
]
...
@@ -178,7 +182,7 @@ class ModelFilterConfigForm(BaseModel):
...
@@ -178,7 +182,7 @@ class ModelFilterConfigForm(BaseModel):
@
app
.
post
(
"/api/config/model/filter"
)
@
app
.
post
(
"/api/config/model/filter"
)
async
def
get
_model_filter_config
(
async
def
update
_model_filter_config
(
form_data
:
ModelFilterConfigForm
,
user
=
Depends
(
get_admin_user
)
form_data
:
ModelFilterConfigForm
,
user
=
Depends
(
get_admin_user
)
):
):
...
@@ -197,6 +201,28 @@ async def get_model_filter_config(
...
@@ -197,6 +201,28 @@ async def get_model_filter_config(
}
}
@
app
.
get
(
"/api/webhook"
)
async
def
get_webhook_url
(
user
=
Depends
(
get_admin_user
)):
return
{
"url"
:
app
.
state
.
WEBHOOK_URL
,
}
class
UrlForm
(
BaseModel
):
url
:
str
@
app
.
post
(
"/api/webhook"
)
async
def
update_webhook_url
(
form_data
:
UrlForm
,
user
=
Depends
(
get_admin_user
)):
app
.
state
.
WEBHOOK_URL
=
form_data
.
url
webui_app
.
state
.
WEBHOOK_URL
=
app
.
state
.
WEBHOOK_URL
return
{
"url"
:
app
.
state
.
WEBHOOK_URL
,
}
@
app
.
get
(
"/api/version"
)
@
app
.
get
(
"/api/version"
)
async
def
get_app_config
():
async
def
get_app_config
():
...
...
backend/utils/webhook.py
0 → 100644
View file @
6313a982
import
requests
def
post_webhook
(
url
:
str
,
message
:
str
,
event_data
:
dict
)
->
bool
:
try
:
payload
=
{}
if
"https://hooks.slack.com"
in
url
:
payload
[
"text"
]
=
message
elif
"https://discord.com/api/webhooks"
in
url
:
payload
[
"content"
]
=
message
else
:
payload
=
{
**
event_data
}
r
=
requests
.
post
(
url
,
json
=
payload
)
r
.
raise_for_status
()
return
True
except
Exception
as
e
:
print
(
e
)
return
False
src/lib/apis/index.ts
View file @
6313a982
...
@@ -139,3 +139,60 @@ export const updateModelFilterConfig = async (
...
@@ -139,3 +139,60 @@ export const updateModelFilterConfig = async (
return
res
;
return
res
;
};
};
export
const
getWebhookUrl
=
async
(
token
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_BASE_URL
}
/api/webhook`
,
{
method
:
'
GET
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
.
url
;
};
export
const
updateWebhookUrl
=
async
(
token
:
string
,
url
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_BASE_URL
}
/api/webhook`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
url
:
url
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
.
url
;
};
src/lib/components/admin/Settings/General.svelte
View file @
6313a982
<script lang="ts">
<script lang="ts">
import { getWebhookUrl, updateWebhookUrl } from '$lib/apis';
import {
import {
getDefaultUserRole,
getDefaultUserRole,
getJWTExpiresDuration,
getJWTExpiresDuration,
...
@@ -16,6 +17,8 @@
...
@@ -16,6 +17,8 @@
let defaultUserRole = 'pending';
let defaultUserRole = 'pending';
let JWTExpiresIn = '';
let JWTExpiresIn = '';
let webhookUrl = '';
const toggleSignUpEnabled = async () => {
const toggleSignUpEnabled = async () => {
signUpEnabled = await toggleSignUpEnabledStatus(localStorage.token);
signUpEnabled = await toggleSignUpEnabledStatus(localStorage.token);
};
};
...
@@ -28,18 +31,23 @@
...
@@ -28,18 +31,23 @@
JWTExpiresIn = await updateJWTExpiresDuration(localStorage.token, duration);
JWTExpiresIn = await updateJWTExpiresDuration(localStorage.token, duration);
};
};
const updateWebhookUrlHandler = async () => {
webhookUrl = await updateWebhookUrl(localStorage.token, webhookUrl);
};
onMount(async () => {
onMount(async () => {
signUpEnabled = await getSignUpEnabledStatus(localStorage.token);
signUpEnabled = await getSignUpEnabledStatus(localStorage.token);
defaultUserRole = await getDefaultUserRole(localStorage.token);
defaultUserRole = await getDefaultUserRole(localStorage.token);
JWTExpiresIn = await getJWTExpiresDuration(localStorage.token);
JWTExpiresIn = await getJWTExpiresDuration(localStorage.token);
webhookUrl = await getWebhookUrl(localStorage.token);
});
});
</script>
</script>
<form
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={() => {
on:submit|preventDefault={() => {
// console.log('submit');
updateJWTExpiresDurationHandler(JWTExpiresIn);
updateJWTExpiresDurationHandler(JWTExpiresIn);
updateWebhookUrlHandler();
saveHandler();
saveHandler();
}}
}}
>
>
...
@@ -108,6 +116,23 @@
...
@@ -108,6 +116,23 @@
<hr class=" dark:border-gray-700 my-3" />
<hr class=" dark:border-gray-700 my-3" />
<div class=" w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Webhook URL')}</div>
</div>
<div class="flex mt-2 space-x-2">
<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"
type="text"
placeholder={`https://example.com/webhook`}
bind:value={webhookUrl}
/>
</div>
</div>
<hr class=" dark:border-gray-700 my-3" />
<div class=" w-full justify-between">
<div class=" w-full justify-between">
<div class="flex w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</div>
<div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</div>
...
...
src/lib/components/chat/MessageInput.svelte
View file @
6313a982
...
@@ -673,7 +673,7 @@
...
@@ -673,7 +673,7 @@
? chatInputPlaceholder
? chatInputPlaceholder
: isRecording
: isRecording
? $i18n.t('Listening...')
? $i18n.t('Listening...')
: $i18n.t('Send a Mess
s
age')}
: $i18n.t('Send a Message')}
bind:value={prompt}
bind:value={prompt}
on:keypress={(e) => {
on:keypress={(e) => {
if (e.keyCode == 13 && !e.shiftKey) {
if (e.keyCode == 13 && !e.shiftKey) {
...
...
src/lib/components/chat/Messages/ResponseMessage.svelte
View file @
6313a982
...
@@ -422,7 +422,7 @@
...
@@ -422,7 +422,7 @@
class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
>
>
{#if siblings.length > 1}
{#if siblings.length > 1}
<div class="flex self-center min-w-fit">
<div class="flex self-center min-w-fit
-mt-1
">
<button
<button
class="self-center dark:hover:text-white hover:text-black transition"
class="self-center dark:hover:text-white hover:text-black transition"
on:click={() => {
on:click={() => {
...
...
src/lib/components/chat/Messages/UserMessage.svelte
View file @
6313a982
...
@@ -203,7 +203,7 @@
...
@@ -203,7 +203,7 @@
<div class=" flex justify-start space-x-1 text-gray-700 dark:text-gray-500">
<div class=" flex justify-start space-x-1 text-gray-700 dark:text-gray-500">
{#if siblings.length > 1}
{#if siblings.length > 1}
<div class="flex self-center">
<div class="flex self-center
-mt-1
">
<button
<button
class="self-center dark:hover:text-white hover:text-black transition"
class="self-center dark:hover:text-white hover:text-black transition"
on:click={() => {
on:click={() => {
...
...
src/lib/components/documents/AddDocModal.svelte
View file @
6313a982
...
@@ -138,7 +138,7 @@
...
@@ -138,7 +138,7 @@
/>
/>
<button
<button
class="w-full text-sm font-medium py-3 bg-gray-
850
hover:bg-gray-800 text-center rounded-xl"
class="w-full text-sm font-medium py-3 bg-gray-
100 hover:bg-gray-200 dark:bg-gray-850 dark:
hover:bg-gray-800 text-center rounded-xl"
type="button"
type="button"
on:click={() => {
on:click={() => {
uploadDocInputElement.click();
uploadDocInputElement.click();
...
...
src/lib/i18n/locales/ca-ES/translation.json
0 → 100644
View file @
6313a982
This diff is collapsed.
Click to expand it.
src/lib/i18n/locales/de-DE/translation.json
View file @
6313a982
...
@@ -276,7 +276,7 @@
...
@@ -276,7 +276,7 @@
"Select a mode"
:
""
,
"Select a mode"
:
""
,
"Select a model"
:
"Ein Modell auswählen"
,
"Select a model"
:
"Ein Modell auswählen"
,
"Select an Ollama instance"
:
""
,
"Select an Ollama instance"
:
""
,
"Send a Mess
s
age"
:
"Eine Nachricht senden"
,
"Send a Message"
:
"Eine Nachricht senden"
,
"Send message"
:
"Nachricht senden"
,
"Send message"
:
"Nachricht senden"
,
"Server connection verified"
:
"Serververbindung überprüft"
,
"Server connection verified"
:
"Serververbindung überprüft"
,
"Set as default"
:
"Als Standard festlegen"
,
"Set as default"
:
"Als Standard festlegen"
,
...
...
src/lib/i18n/locales/en-US/translation.json
View file @
6313a982
...
@@ -276,7 +276,7 @@
...
@@ -276,7 +276,7 @@
"Select a mode"
:
""
,
"Select a mode"
:
""
,
"Select a model"
:
"Select a model"
,
"Select a model"
:
"Select a model"
,
"Select an Ollama instance"
:
""
,
"Select an Ollama instance"
:
""
,
"Send a Mess
s
age"
:
"Send a Mess
s
age"
,
"Send a Message"
:
"Send a Message"
,
"Send message"
:
"Send message"
,
"Send message"
:
"Send message"
,
"Server connection verified"
:
"Server connection verified"
,
"Server connection verified"
:
"Server connection verified"
,
"Set as default"
:
"Set as default"
,
"Set as default"
:
"Set as default"
,
...
...
src/lib/i18n/locales/es-ES/translation.json
0 → 100644
View file @
6313a982
This diff is collapsed.
Click to expand it.
src/lib/i18n/locales/fa-IR/translation.json
View file @
6313a982
...
@@ -276,7 +276,7 @@
...
@@ -276,7 +276,7 @@
"Select a mode"
:
"یک حالت انتخاب کنید"
,
"Select a mode"
:
"یک حالت انتخاب کنید"
,
"Select a model"
:
"انتخاب یک مدل"
,
"Select a model"
:
"انتخاب یک مدل"
,
"Select an Ollama instance"
:
"انتخاب یک نمونه از اولاما"
,
"Select an Ollama instance"
:
"انتخاب یک نمونه از اولاما"
,
"Send a Mess
s
age"
:
"ارسال یک پیام"
,
"Send a Message"
:
"ارسال یک پیام"
,
"Send message"
:
"ارسال پیام"
,
"Send message"
:
"ارسال پیام"
,
"Server connection verified"
:
"اتصال سرور تأیید شد"
,
"Server connection verified"
:
"اتصال سرور تأیید شد"
,
"Set as default"
:
"تنظیم به عنوان پیشفرض"
,
"Set as default"
:
"تنظیم به عنوان پیشفرض"
,
...
...
src/lib/i18n/locales/fr-CA/translation.json
0 → 100644
View file @
6313a982
This diff is collapsed.
Click to expand it.
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment