Begin translation and finish i18n system

This commit is contained in:
Restray 2022-04-02 21:08:55 +02:00
parent a3241516cb
commit 269250ef3d
No known key found for this signature in database
GPG Key ID: 67C6DEF95A4DC812
13 changed files with 142 additions and 70 deletions

View File

@ -1,8 +1,8 @@
{ {
"i18n-ally.localesPaths": ["static/locales"], "i18n-ally.localesPaths": ["src/lib/locales"],
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",
"i18n-ally.extract.ignoredByFiles": { "i18n-ally.extract.ignoredByFiles": {
"src\\routes\\__layout.svelte": ["Coolify", "coolLabs logo"] "src\\routes\\__layout.svelte": ["Coolify", "coolLabs logo"]
}, },
"i18n-ally.sourceLanguage": "fr" "i18n-ally.sourceLanguage": "en"
} }

1
src/app.d.ts vendored
View File

@ -23,6 +23,7 @@ interface SessionData {
userId?: string | null; userId?: string | null;
teamId?: string | null; teamId?: string | null;
permission?: string; permission?: string;
lang?: string;
isAdmin?: boolean; isAdmin?: boolean;
expires?: string | null; expires?: string | null;
gitlabToken?: string | null; gitlabToken?: string | null;

View File

@ -18,27 +18,22 @@ export const handle = handleSession(
let response; let response;
const { url, request } = event; const { url, request } = event;
const { pathname } = url;
// Get defined locales // Get defined locales
const supportedLocales = locales.get(); const supportedLocales = locales.get();
let locale; let locale;
if (event.locals.cookies) { if (event.locals.cookies['lang']) {
locale = supportedLocales.find( locale = event.locals.cookies['lang'];
(l) => `${l}`.toLowerCase() === `${event.locals.cookies['lang']}`.toLowerCase() } else if (!locale) {
);
}
if (!locale) {
locale = `${`${request.headers.get('accept-language')}`.match( locale = `${`${request.headers.get('accept-language')}`.match(
/[a-zA-Z]+?(?=-|_|,|;)/ /[a-zA-Z]+?(?=-|_|,|;)/
)}`.toLowerCase(); )}`.toLowerCase();
// Set default locale if user preferred locale does not match
if (!supportedLocales.includes(locale)) locale = 'en';
} }
// Set default locale if user preferred locale does not match
if (!supportedLocales.includes(locale)) locale = 'en';
try { try {
if (event.locals.cookies) { if (event.locals.cookies) {
if (event.locals.cookies['kit.session']) { if (event.locals.cookies['kit.session']) {
@ -105,7 +100,18 @@ export const handle = handleSession(
); );
export const getSession: GetSession = function ({ locals }) { export const getSession: GetSession = function ({ locals }) {
// Get defined locales
const supportedLocales = locales.get();
let locale;
if (locals.cookies) {
locale = supportedLocales.find(
(l) => `${l}`.toLowerCase() === `${locals.cookies['lang']}`.toLowerCase()
);
}
return { return {
lang: locale,
version, version,
...locals.session.data ...locals.session.data
}; };

40
src/lib/locales/en.json Normal file
View File

@ -0,0 +1,40 @@
{
"layout": {
"update_done": "Update completed.",
"wait_new_version_startup": "Waiting for the new version to start...",
"new_version": "New version reachable. Reloading...",
"switch_to_a_different_team": "Switch to a different team...",
"update_available": "Update available"
},
"error": {
"you_can_find_your_way_back": "You can find your way back",
"here": "here"
},
"index": {
"dashboard": "Dashboard",
"applications": "Applications",
"destinations": "Destinations",
"git_sources": "Git Sources",
"databases": "Databases",
"services": "Services",
"teams": "Teams"
},
"login": {
"already_logged_in": "Already logged in...",
"authenticating": "Authenticating...",
"login": "Login"
},
"forms": {
"password": "Password",
"email": "Email",
"passwords_not_match": "Passwords do not match.",
"password_again": "Password again"
},
"register": {
"register": "Register",
"first_user": "You are registering the first user. It will be the administrator of your Coolify instance."
},
"reset": {
"reset_password": "Reset password"
}
}

40
src/lib/locales/fr.json Normal file
View File

@ -0,0 +1,40 @@
{
"index": {
"dashboard": "Tableau de bord",
"applications": "Applications",
"databases": "Bases de données",
"destinations": "Destinations",
"git_sources": "Sources Git",
"services": "Prestations de service",
"teams": "Équipes"
},
"error": {
"here": "ici",
"you_can_find_your_way_back": "Tu peux retrouver ton chemin"
},
"forms": {
"email": "E-mail",
"password": "Mot de passe",
"password_again": "Retaper le mot de passe",
"passwords_not_match": "Les mots de passe ne correspondent pas."
},
"layout": {
"new_version": "Nouvelle version disponible. \nActualisation...",
"switch_to_a_different_team": "Changer d'équipe...",
"update_available": "Mise à jour disponible",
"update_done": "Mise à jour terminée.",
"wait_new_version_startup": "Attente du lancement de la nouvelle version..."
},
"login": {
"already_logged_in": "Déjà connecté...",
"authenticating": "Authentification...",
"login": "Connexion"
},
"register": {
"first_user": "Vous enregistrez le premier utilisateur. \nCe sera l'administrateur de votre instance Coolify.",
"register": "S'inscrire"
},
"reset": {
"reset_password": "Réinitialiser le mot de passe"
}
}

View File

@ -3,7 +3,6 @@ import lang from './lang.json';
/** @type {import('sveltekit-i18n').Config} */ /** @type {import('sveltekit-i18n').Config} */
const config = { const config = {
defaultLocale: 'en',
fallbackLocale: 'en', fallbackLocale: 'en',
translations: { translations: {
en: { lang }, en: { lang },
@ -13,16 +12,16 @@ const config = {
{ {
locale: 'en', locale: 'en',
key: '', key: '',
loader: async () => (await import('../../static/locales/en.json')).default loader: async () => (await import('./locales/en.json')).default
}, },
{ {
locale: 'fr', locale: 'fr',
key: '', key: '',
loader: async () => (await import('../../static/locales/fr.json')).default loader: async () => (await import('./locales/fr.json')).default
} }
] ]
}; };
export const { t, locale, locales, loading, loadTranslations } = new i18n(config); export const { t, loading, locales, locale, loadTranslations } = new i18n(config);
loading.subscribe(($loading) => $loading && console.log('Loading translations...')); loading.subscribe(($loading) => $loading);

View File

@ -4,6 +4,11 @@
import { locale, loadTranslations } from '$lib/translations'; import { locale, loadTranslations } from '$lib/translations';
export const load: Load = async ({ fetch, url, session }) => { export const load: Load = async ({ fetch, url, session }) => {
const { pathname } = url;
const initLocale = locale.get() || session.lang || 'en';
await loadTranslations(initLocale, pathname);
if (!session.userId && !publicPaths.includes(url.pathname)) { if (!session.userId && !publicPaths.includes(url.pathname)) {
return { return {
status: 302, status: 302,
@ -16,12 +21,6 @@
const endpoint = `/teams.json`; const endpoint = `/teams.json`;
const res = await fetch(endpoint); const res = await fetch(endpoint);
const { pathname } = url;
const defaultLocale = session.lang;
const initLocale = locale.get() || defaultLocale;
await loadTranslations(initLocale, pathname);
if (res.ok) { if (res.ok) {
return { return {
props: { props: {

View File

@ -46,7 +46,7 @@
class="flex cursor-pointer flex-col rounded p-6 text-center text-green-500 no-underline transition duration-150 hover:bg-green-500 hover:text-white" class="flex cursor-pointer flex-col rounded p-6 text-center text-green-500 no-underline transition duration-150 hover:bg-green-500 hover:text-white"
> >
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white"> <dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
Applications {$t('index.applications')}
</dt> </dt>
<dd class="order-1 text-5xl font-extrabold "> <dd class="order-1 text-5xl font-extrabold ">
{applicationsCount} {applicationsCount}
@ -58,7 +58,7 @@
class="flex cursor-pointer flex-col rounded p-6 text-center text-sky-500 no-underline transition duration-150 hover:bg-sky-500 hover:text-white" class="flex cursor-pointer flex-col rounded p-6 text-center text-sky-500 no-underline transition duration-150 hover:bg-sky-500 hover:text-white"
> >
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white"> <dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
Destinations {$t('index.destinations')}
</dt> </dt>
<dd class="order-1 text-5xl font-extrabold "> <dd class="order-1 text-5xl font-extrabold ">
{destinationsCount} {destinationsCount}
@ -70,7 +70,7 @@
class="flex cursor-pointer flex-col rounded p-6 text-center text-orange-500 no-underline transition duration-150 hover:bg-orange-500 hover:text-white" class="flex cursor-pointer flex-col rounded p-6 text-center text-orange-500 no-underline transition duration-150 hover:bg-orange-500 hover:text-white"
> >
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white"> <dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
Git Sources {$t('index.git_sources')}
</dt> </dt>
<dd class="order-1 text-5xl font-extrabold "> <dd class="order-1 text-5xl font-extrabold ">
{sourcesCount} {sourcesCount}
@ -81,7 +81,9 @@
sveltekit:prefetch sveltekit:prefetch
class="flex cursor-pointer flex-col rounded p-6 text-center text-purple-500 no-underline transition duration-150 hover:bg-purple-500 hover:text-white" class="flex cursor-pointer flex-col rounded p-6 text-center text-purple-500 no-underline transition duration-150 hover:bg-purple-500 hover:text-white"
> >
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">Databases</dt> <dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
{$t('index.databases')}
</dt>
<dd class="order-1 text-5xl font-extrabold ">{databasesCount}</dd> <dd class="order-1 text-5xl font-extrabold ">{databasesCount}</dd>
</a> </a>
<a <a
@ -89,7 +91,9 @@
sveltekit:prefetch sveltekit:prefetch
class="flex cursor-pointer flex-col rounded p-6 text-center text-pink-500 no-underline transition duration-150 hover:bg-pink-500 hover:text-white" class="flex cursor-pointer flex-col rounded p-6 text-center text-pink-500 no-underline transition duration-150 hover:bg-pink-500 hover:text-white"
> >
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">Services</dt> <dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
{$t('index.services')}
</dt>
<dd class="order-1 text-5xl font-extrabold ">{servicesCount}</dd> <dd class="order-1 text-5xl font-extrabold ">{servicesCount}</dd>
</a> </a>
@ -98,7 +102,9 @@
sveltekit:prefetch sveltekit:prefetch
class="flex cursor-pointer flex-col rounded p-6 text-center text-cyan-500 no-underline transition duration-150 hover:bg-cyan-500 hover:text-white" class="flex cursor-pointer flex-col rounded p-6 text-center text-cyan-500 no-underline transition duration-150 hover:bg-cyan-500 hover:text-white"
> >
<dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">Teams</dt> <dt class="order-2 mt-2 text-sm font-bold uppercase leading-6 text-white">
{$t('index.teams')}
</dt>
<dd class="order-1 text-5xl font-extrabold "> <dd class="order-1 text-5xl font-extrabold ">
{teamsCount} {teamsCount}
</dd> </dd>

View File

@ -4,6 +4,7 @@
import { session } from '$app/stores'; import { session } from '$app/stores';
import { post } from '$lib/api'; import { post } from '$lib/api';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
let loading = false; let loading = false;
let emailEl; let emailEl;
@ -37,9 +38,13 @@
} }
</script> </script>
<svelt:head>
<title>{$t('login.login')}</title>
</svelt:head>
<div class="flex h-screen flex-col items-center justify-center"> <div class="flex h-screen flex-col items-center justify-center">
{#if $session.userId} {#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} {:else}
<div class="flex justify-center px-4"> <div class="flex justify-center px-4">
<form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2"> <form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2">
@ -48,7 +53,7 @@
<input <input
type="email" type="email"
name="email" name="email"
placeholder="Email" placeholder={$t('forms.email')}
autocomplete="off" autocomplete="off"
required required
bind:this={emailEl} bind:this={emailEl}
@ -57,7 +62,7 @@
<input <input
type="password" type="password"
name="password" name="password"
placeholder="Password" placeholder={$t('index.password')}
bind:value={password} bind:value={password}
required required
/> />
@ -69,16 +74,18 @@
class="hover:opacity-90 text-white" class="hover:opacity-90 text-white"
class:bg-transparent={loading} class:bg-transparent={loading}
class:text-stone-600={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 <button
on:click|preventDefault={() => goto('/register')} 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 <button
class="bg-transparent hover:bg-coolgray-300" class="bg-transparent hover:bg-coolgray-300"
on:click|preventDefault={() => goto('/reset')}>Reset password</button on:click|preventDefault={() => goto('/reset')}>{$t('reset.reset_password')}</button
> >
</div> </div>
</form> </form>

View File

@ -6,6 +6,7 @@
import { session } from '$app/stores'; import { session } from '$app/stores';
import { post } from '$lib/api'; import { post } from '$lib/api';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { t } from '$lib/translations';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
let loading = false; let loading = false;
@ -20,7 +21,7 @@
}); });
async function handleSubmit() { async function handleSubmit() {
if (password !== passwordCheck) { if (password !== passwordCheck) {
return errorNotification('Passwords do not match.'); return errorNotification($t('forms.passwords_not_match'));
} }
loading = true; loading = true;
try { try {
@ -57,7 +58,7 @@
</div> </div>
<div class="flex h-screen flex-col items-center justify-center"> <div class="flex h-screen flex-col items-center justify-center">
{#if $session.userId} {#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} {:else}
<div class="flex justify-center px-4"> <div class="flex justify-center px-4">
<form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2"> <form on:submit|preventDefault={handleSubmit} class="flex flex-col py-4 space-y-2">
@ -66,7 +67,7 @@
<input <input
type="email" type="email"
name="email" name="email"
placeholder="Email" placeholder={$t('forms.email')}
autocomplete="off" autocomplete="off"
required required
bind:this={emailEl} bind:this={emailEl}
@ -75,28 +76,28 @@
<input <input
type="password" type="password"
name="password" name="password"
placeholder="Password" placeholder={$t('forms.password')}
bind:value={password} bind:value={password}
required required
/> />
<input <input
type="password" type="password"
name="passwordCheck" name="passwordCheck"
placeholder="Password again" placeholder={$t('forms.password_again')}
bind:value={passwordCheck} bind:value={passwordCheck}
required required
/> />
<div class="flex space-x-2 h-8 items-center justify-center pt-8"> <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" <button type="submit" class="hover:bg-coollabs-100 text-white bg-coollabs"
>Register</button >{$t('register.register')}</button
> >
</div> </div>
</form> </form>
</div> </div>
{#if userCount === 0} {#if userCount === 0}
<div class="pt-5"> <div class="pt-5">
You are registering the first user. It will be the administrator of your Coolify instance. {$t('register.first_user')}
</div> </div>
{/if} {/if}
{/if} {/if}

View File

@ -1,16 +0,0 @@
{
"layout": {
"update_done": "Update completed.",
"wait_new_version_startup": "Waiting for the new version to start...",
"new_version": "New version reachable. Reloading...",
"switch_to_a_different_team": "Switch to a different team...",
"update_available": "Update available"
},
"error": {
"you_can_find_your_way_back": "You can find your way back",
"here": "here"
},
"index": {
"dashboard": "Dashboard"
}
}

View File

@ -1,5 +0,0 @@
{
"index": {
"dashboard": "Tableau de bord"
}
}

View File

@ -12,12 +12,6 @@ const config = {
vite: { vite: {
optimizeDeps: { optimizeDeps: {
exclude: ['svelte-kit-cookie-session'] exclude: ['svelte-kit-cookie-session']
},
server: {
fs: {
// Allow serving files from one level up to the project root
allow: ['../locales']
}
} }
} }
} }