Add Locale URL
This commit is contained in:
parent
741db1778b
commit
d910b21185
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -1,7 +1,5 @@
|
||||
{
|
||||
"i18n-ally.localesPaths": ["locales"],
|
||||
"i18n-ally.localesPaths": ["static/locales"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.extract.ignoredByFiles": {
|
||||
"src\\routes\\__layout.svelte": ["Coolify", "coolLabs logo"]
|
||||
}
|
||||
"i18n-ally.extract.ignored": ["Coolify", "coolLabs logo"]
|
||||
}
|
||||
|
@ -51,7 +51,6 @@
|
||||
"prisma": "3.11.1",
|
||||
"svelte": "3.46.4",
|
||||
"svelte-check": "2.4.6",
|
||||
"svelte-i18n": "^3.3.13",
|
||||
"svelte-preprocess": "4.10.4",
|
||||
"svelte-select": "^4.4.7",
|
||||
"tailwindcss": "3.0.23",
|
||||
@ -82,6 +81,7 @@
|
||||
"mustache": "^4.2.0",
|
||||
"node-forge": "1.3.0",
|
||||
"svelte-kit-cookie-session": "2.1.2",
|
||||
"sveltekit-i18n": "^2.1.2",
|
||||
"tailwindcss-scrollbar": "^0.1.0",
|
||||
"unique-names-generator": "4.7.1"
|
||||
},
|
||||
|
124
pnpm-lock.yaml
generated
124
pnpm-lock.yaml
generated
@ -46,10 +46,10 @@ specifiers:
|
||||
prisma: 3.11.1
|
||||
svelte: 3.46.4
|
||||
svelte-check: 2.4.6
|
||||
svelte-i18n: ^3.3.13
|
||||
svelte-kit-cookie-session: 2.1.2
|
||||
svelte-preprocess: 4.10.4
|
||||
svelte-select: ^4.4.7
|
||||
sveltekit-i18n: ^2.1.2
|
||||
tailwindcss: 3.0.23
|
||||
tailwindcss-scrollbar: ^0.1.0
|
||||
ts-node: 10.7.0
|
||||
@ -79,6 +79,7 @@ dependencies:
|
||||
mustache: 4.2.0
|
||||
node-forge: 1.3.0
|
||||
svelte-kit-cookie-session: 2.1.2
|
||||
sveltekit-i18n: 2.1.2_svelte@3.46.4
|
||||
tailwindcss-scrollbar: 0.1.0_tailwindcss@3.0.23
|
||||
unique-names-generator: 4.7.1
|
||||
|
||||
@ -108,7 +109,6 @@ devDependencies:
|
||||
prisma: 3.11.1
|
||||
svelte: 3.46.4
|
||||
svelte-check: 2.4.6_postcss@8.4.12+svelte@3.46.4
|
||||
svelte-i18n: 3.3.13_svelte@3.46.4
|
||||
svelte-preprocess: 4.10.4_296873641a0ad9f42fe92172d27bcedd
|
||||
svelte-select: 4.4.7
|
||||
tailwindcss: 3.0.23_b89136460714832cdda11d1e9d57d1ff
|
||||
@ -184,55 +184,6 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@formatjs/ecma402-abstract/1.11.4:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==
|
||||
}
|
||||
dependencies:
|
||||
'@formatjs/intl-localematcher': 0.2.25
|
||||
tslib: 2.3.1
|
||||
dev: true
|
||||
|
||||
/@formatjs/fast-memoize/1.2.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==
|
||||
}
|
||||
dependencies:
|
||||
tslib: 2.3.1
|
||||
dev: true
|
||||
|
||||
/@formatjs/icu-messageformat-parser/2.0.19:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-8HsLm9YLyVVIDMyBJb7wmve2wGd461cUwJ470eUog5YH5ZsF4p5lgvaJ+oGKxz1mrSMNNdDHU9v/NDsS+z+ilg==
|
||||
}
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 1.11.4
|
||||
'@formatjs/icu-skeleton-parser': 1.3.6
|
||||
tslib: 2.3.1
|
||||
dev: true
|
||||
|
||||
/@formatjs/icu-skeleton-parser/1.3.6:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==
|
||||
}
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 1.11.4
|
||||
tslib: 2.3.1
|
||||
dev: true
|
||||
|
||||
/@formatjs/intl-localematcher/0.2.25:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==
|
||||
}
|
||||
dependencies:
|
||||
tslib: 2.3.1
|
||||
dev: true
|
||||
|
||||
/@humanwhocodes/config-array/0.5.0:
|
||||
resolution:
|
||||
{
|
||||
@ -495,6 +446,26 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@sveltekit-i18n/base/1.1.1_svelte@3.46.4:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-J/sMU0OwS3dCLOuilHMBqu8vZHuuXiNV9vFJx8Nb4/b5BlR/KCZ4bCXI8wZR02GHeCOYKZxWus07CM1scxa/jw==
|
||||
}
|
||||
peerDependencies:
|
||||
svelte: ^3.x
|
||||
dependencies:
|
||||
svelte: 3.46.4
|
||||
optionalDependencies:
|
||||
'@sveltekit-i18n/parser-default': 1.0.3
|
||||
dev: false
|
||||
|
||||
/@sveltekit-i18n/parser-default/1.0.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-HheveklTjp3hxpYQhoHfyA6B4bQaUeSV5MQf2usIv/58UF2jY/YqhCAWj9bDBjufbuZc5pSz4BXvdX3WVT+viA==
|
||||
}
|
||||
dev: false
|
||||
|
||||
/@szmarczak/http-timer/5.0.1:
|
||||
resolution:
|
||||
{
|
||||
@ -2254,14 +2225,6 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/deepmerge/4.2.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||
}
|
||||
engines: { node: '>=0.10.0' }
|
||||
dev: true
|
||||
|
||||
/defer-to-connect/2.0.1:
|
||||
resolution:
|
||||
{
|
||||
@ -3462,18 +3425,6 @@ packages:
|
||||
integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
}
|
||||
|
||||
/intl-messageformat/9.12.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-5Q9j21JreB1G27/CqMYsA+pvJ19JjHyhiTSeUuvZK9BCDJGHtOLgpUUcGM+GLHiUuoVMKVeeX1smamiVHQrSKQ==
|
||||
}
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 1.11.4
|
||||
'@formatjs/fast-memoize': 1.2.1
|
||||
'@formatjs/icu-messageformat-parser': 2.0.19
|
||||
tslib: 2.3.1
|
||||
dev: true
|
||||
|
||||
/invariant/2.2.4:
|
||||
resolution:
|
||||
{
|
||||
@ -5243,24 +5194,6 @@ packages:
|
||||
svelte: 3.46.4
|
||||
dev: true
|
||||
|
||||
/svelte-i18n/3.3.13_svelte@3.46.4:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-RQM+ys4+Y9ztH//tX22H1UL2cniLNmIR+N4xmYygV6QpQ6EyQvloZiENRew8XrVzfvJ8HaE8NU6/yurLkl7z3g==
|
||||
}
|
||||
engines: { node: '>= 11.15.0' }
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
svelte: ^3.25.1
|
||||
dependencies:
|
||||
deepmerge: 4.2.2
|
||||
estree-walker: 2.0.2
|
||||
intl-messageformat: 9.12.0
|
||||
sade: 1.7.4
|
||||
svelte: 3.46.4
|
||||
tiny-glob: 0.2.9
|
||||
dev: true
|
||||
|
||||
/svelte-kit-cookie-session/2.1.2:
|
||||
resolution:
|
||||
{
|
||||
@ -5338,6 +5271,19 @@ packages:
|
||||
engines: { node: '>= 8' }
|
||||
dev: true
|
||||
|
||||
/sveltekit-i18n/2.1.2_svelte@3.46.4:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-s5YxcbNd2EWNZaZR1A4Drt8s53E4fpUkN4XIWd3VRpw1pihZVWssqmBW1qkjQ6AB0kiu1Qwule+vt1HkbQOjrg==
|
||||
}
|
||||
peerDependencies:
|
||||
svelte: ^3.x
|
||||
dependencies:
|
||||
'@sveltekit-i18n/base': 1.1.1_svelte@3.46.4
|
||||
'@sveltekit-i18n/parser-default': 1.0.3
|
||||
svelte: 3.46.4
|
||||
dev: false
|
||||
|
||||
/table/6.7.2:
|
||||
resolution:
|
||||
{
|
||||
|
43
src/hooks.ts
43
src/hooks.ts
@ -6,6 +6,9 @@ import { getUserDetails, sentry } from '$lib/common';
|
||||
import { version } from '$lib/common';
|
||||
import cookie from 'cookie';
|
||||
import { dev } from '$app/env';
|
||||
import { locales } from '$lib/translations';
|
||||
|
||||
const routeRegex = new RegExp(/^\/[^.]*([?#].*)?$/);
|
||||
|
||||
export const handle = handleSession(
|
||||
{
|
||||
@ -62,9 +65,45 @@ export const handle = handleSession(
|
||||
expires: new Date('Thu, 01 Jan 1970 00:00:01 GMT')
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
return response;
|
||||
}
|
||||
|
||||
const { url, request } = event;
|
||||
const { pathname } = url;
|
||||
|
||||
// If this request is a route request
|
||||
if (routeRegex.test(pathname)) {
|
||||
// Get defined locales
|
||||
const supportedLocales = locales.get();
|
||||
|
||||
// Try to get locale from `pathname`.
|
||||
let locale = supportedLocales.find(
|
||||
(l) => `${l}`.toLowerCase() === `${pathname.match(/[^/]+?(?=\/|$)/)}`.toLowerCase()
|
||||
);
|
||||
|
||||
// If route locale is not supported
|
||||
if (!locale) {
|
||||
// Get user preferred locale
|
||||
locale = `${`${request.headers['accept-language']}`.match(
|
||||
/[a-zA-Z]+?(?=-|_|,|;)/
|
||||
)}`.toLowerCase();
|
||||
|
||||
// Set default locale if user preferred locale does not match
|
||||
if (!supportedLocales.includes(locale)) locale = 'en';
|
||||
|
||||
// 301 redirect
|
||||
return new Response(undefined, {
|
||||
headers: { location: `/${locale}${pathname}` },
|
||||
status: 301
|
||||
});
|
||||
}
|
||||
|
||||
// Add html `lang` attribute
|
||||
const body = await response.text();
|
||||
|
||||
return new Response(`${body}`.replace(/<html.*>/, `<html lang="${locale}">`), response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
export function errorNotification(message: string) {
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let formatMessage;
|
||||
t.subscribe((storeFormat) => (formatMessage = storeFormat));
|
||||
|
||||
export function errorNotification(message: string): void {
|
||||
console.error(message);
|
||||
if (typeof message !== 'string') {
|
||||
toast.push('Ooops, something is not okay, are you okay?');
|
||||
toast.push(formatMessage('error.generic_message'));
|
||||
} else {
|
||||
toast.push(message);
|
||||
toast.push(formatMessage(message));
|
||||
}
|
||||
}
|
||||
export function enhance(
|
||||
|
4
src/lib/lang.json
Normal file
4
src/lib/lang.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"en": "English",
|
||||
"fr": "Français"
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export const publicPaths = [
|
||||
const publicPaths = [
|
||||
'/login',
|
||||
'/register',
|
||||
'/reset',
|
||||
@ -8,3 +8,7 @@ export const publicPaths = [
|
||||
'/webhooks/github/install',
|
||||
'/webhooks/gitlab'
|
||||
];
|
||||
|
||||
export function isPublicPath(path: string): boolean {
|
||||
return publicPaths.includes(path);
|
||||
}
|
||||
|
26
src/lib/translations.ts
Normal file
26
src/lib/translations.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import i18n from 'sveltekit-i18n';
|
||||
import lang from './lang.json';
|
||||
|
||||
/** @type {import('sveltekit-i18n').Config} */
|
||||
export const config = {
|
||||
fallbackLocale: 'en',
|
||||
translations: {
|
||||
en: { lang },
|
||||
fr: { lang }
|
||||
},
|
||||
loaders: [
|
||||
{
|
||||
locale: 'en',
|
||||
key: '',
|
||||
loader: async () => (await import('../../static/locales/en.json')).default
|
||||
},
|
||||
{
|
||||
locale: 'fr',
|
||||
key: '',
|
||||
loader: async () => (await import('../../static/locales/fr.json')).default
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const { t, loading, locales, locale, loadTranslations } = new i18n(config);
|
||||
loading.subscribe(($loading) => $loading && console.log('Loading translations...'));
|
@ -2,6 +2,7 @@ import { getUserDetails } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
export const post: RequestHandler = async (event) => {
|
||||
const { teamId, status, body } = await getUserDetails(event);
|
||||
@ -14,8 +15,7 @@ export const post: RequestHandler = async (event) => {
|
||||
const isDouble = await db.checkDoubleBranch(branch, projectId);
|
||||
if (isDouble && autodeploy) {
|
||||
throw {
|
||||
message:
|
||||
'Cannot activate automatic deployments until only one application is defined for this repository / branch.'
|
||||
message: $_('application.app.error_double_app_for_one_branch')
|
||||
};
|
||||
}
|
||||
await db.setApplicationSettings({ id, debug, previews, dualCerts, autodeploy });
|
@ -15,6 +15,7 @@
|
||||
import Docker from '$lib/components/svg/applications/Docker.svelte';
|
||||
import Astro from '$lib/components/svg/applications/Astro.svelte';
|
||||
import Eleventy from '$lib/components/svg/applications/Eleventy.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
const buildPack = application?.buildPack?.toLowerCase();
|
||||
</script>
|
||||
@ -59,7 +60,7 @@
|
||||
{/if}
|
||||
{#if !application.gitSourceId || !application.destinationDockerId}
|
||||
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||
Configuration missing
|
||||
{$t('application.configuration_missing')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
@ -4,6 +4,7 @@
|
||||
import Application from './_Application.svelte';
|
||||
import { post } from '$lib/api';
|
||||
import { goto } from '$app/navigation';
|
||||
import { t } from '$lib/translations';
|
||||
async function newApplication() {
|
||||
const { id } = await post('/applications/new', {});
|
||||
return await goto(`/applications/${id}`, { replaceState: true });
|
||||
@ -11,7 +12,7 @@
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 p-6 font-bold">
|
||||
<div class="mr-4 text-2xl ">Applications</div>
|
||||
<div class="mr-4 text-2xl ">{$t('index.applications')}</div>
|
||||
{#if $session.isAdmin}
|
||||
<div on:click={newApplication} class="add-icon cursor-pointer bg-green-600 hover:bg-green-500">
|
||||
<svg
|
||||
@ -33,7 +34,7 @@
|
||||
<div class="flex flex-wrap justify-center">
|
||||
{#if !applications || applications.length === 0}
|
||||
<div class="flex-col">
|
||||
<div class="text-center text-xl font-bold">No applications found</div>
|
||||
<div class="text-center text-xl font-bold">{$t('application.no_applications_found')}</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#each applications as application}
|
@ -5,6 +5,8 @@
|
||||
import { post } from '$lib/api';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let loading = false;
|
||||
let emailEl;
|
||||
let email, password;
|
||||
@ -39,7 +41,7 @@
|
||||
|
||||
<div class="flex h-screen flex-col items-center justify-center">
|
||||
{#if $session.userId}
|
||||
<div class="flex justify-center px-4 text-xl font-bold">Already logged in...</div>
|
||||
<div class="flex justify-center px-4 text-xl font-bold">{$t('login.already_logged_in')}</div>
|
||||
{:else}
|
||||
<div class="flex justify-center px-4">
|
||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2">
|
||||
@ -48,7 +50,7 @@
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
placeholder={$t('forms.email')}
|
||||
autocomplete="off"
|
||||
required
|
||||
bind:this={emailEl}
|
||||
@ -57,7 +59,7 @@
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
placeholder={$t('forms.password')}
|
||||
bind:value={password}
|
||||
required
|
||||
/>
|
||||
@ -69,16 +71,19 @@
|
||||
class="hover:opacity-90 text-white"
|
||||
class:bg-transparent={loading}
|
||||
class:text-stone-600={loading}
|
||||
class:bg-coollabs={!loading}>{loading ? 'Authenticating...' : 'Login'}</button
|
||||
class:bg-coollabs={!loading}
|
||||
>{loading ? $t('login.authenticating') : $t('login.login')}</button
|
||||
>
|
||||
|
||||
<button
|
||||
on:click|preventDefault={() => goto('/register')}
|
||||
class="bg-transparent hover:bg-coolgray-300 text-white ">Register</button
|
||||
class="bg-transparent hover:bg-coolgray-300 text-white "
|
||||
>{$t('register.register')}</button
|
||||
>
|
||||
<button
|
||||
class="bg-transparent hover:bg-coolgray-300"
|
||||
on:click|preventDefault={() => goto('/reset')}>Reset password</button
|
||||
on:click|preventDefault={() => goto('/reset')}
|
||||
>{$t('reset_password.reset_password')}</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
@ -7,6 +7,7 @@
|
||||
import { post } from '$lib/api';
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '$lib/translations';
|
||||
|
||||
let loading = false;
|
||||
let emailEl;
|
||||
@ -20,7 +21,7 @@
|
||||
});
|
||||
async function handleSubmit() {
|
||||
if (password !== passwordCheck) {
|
||||
return errorNotification('Passwords do not match.');
|
||||
return errorNotification($t('register.passwords_do_not_match'));
|
||||
}
|
||||
loading = true;
|
||||
try {
|
||||
@ -57,7 +58,7 @@
|
||||
</div>
|
||||
<div class="flex h-screen flex-col items-center justify-center">
|
||||
{#if $session.userId}
|
||||
<div class="flex justify-center px-4 text-xl font-bold">Already logged in...</div>
|
||||
<div class="flex justify-center px-4 text-xl font-bold">{$t('login.already_logged_in')}</div>
|
||||
{:else}
|
||||
<div class="flex justify-center px-4">
|
||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2">
|
||||
@ -66,7 +67,7 @@
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
placeholder={$t('forms.email')}
|
||||
autocomplete="off"
|
||||
required
|
||||
bind:this={emailEl}
|
||||
@ -75,28 +76,28 @@
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
placeholder={$t('forms.password')}
|
||||
bind:value={password}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
name="passwordCheck"
|
||||
placeholder="Password again"
|
||||
placeholder={$t('forms.password_again')}
|
||||
bind:value={passwordCheck}
|
||||
required
|
||||
/>
|
||||
|
||||
<div class="flex space-x-2 h-8 items-center justify-center pt-8">
|
||||
<button type="submit" class="hover:bg-coollabs-100 text-white bg-coollabs"
|
||||
>Register</button
|
||||
>{$t('register.register')}</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{#if userCount === 0}
|
||||
<div class="pt-5">
|
||||
You are registering the first user. It will be the administrator of your Coolify instance.
|
||||
{$t('register.register_first_user')}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user