feat: lang
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
- [x] list option: hide completed
|
- [x] list option: hide completed
|
||||||
- [x] options in cookies
|
- [x] options in cookies
|
||||||
- [ ] Empty list message
|
- [ ] Empty list message
|
||||||
- [ ] change lang
|
- [x] change lang
|
||||||
- [ ] toasts
|
- [ ] toasts
|
||||||
- [ ] handle errors
|
- [ ] handle errors
|
||||||
- [ ] list per URL
|
- [ ] list per URL
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"cron-parser": "^5.6.1",
|
"cron-parser": "^5.6.1",
|
||||||
"date-fns": "^4.4.0",
|
"date-fns": "^4.4.0",
|
||||||
"vue": "^3.5.38",
|
"vue": "^3.5.38",
|
||||||
|
"vue-i18n": "^11.4.6",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "^4.3.2",
|
"@tailwindcss/vite": "^4.3.2",
|
||||||
@@ -134,6 +135,14 @@
|
|||||||
|
|
||||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||||
|
|
||||||
|
"@intlify/core-base": ["@intlify/core-base@11.4.6", "", { "dependencies": { "@intlify/devtools-types": "11.4.6", "@intlify/message-compiler": "11.4.6", "@intlify/shared": "11.4.6" } }, "sha512-EOeHO95XESK9IFHgHeZXunsM/WBAoCA0DlaWODvx14vKmetAuS97t+l6Xe9hTUqntPpF93vtVSjjUDafw3wXMw=="],
|
||||||
|
|
||||||
|
"@intlify/devtools-types": ["@intlify/devtools-types@11.4.6", "", { "dependencies": { "@intlify/core-base": "11.4.6", "@intlify/shared": "11.4.6" } }, "sha512-wowQPpNem56b2d43IJmqbrzG2FeBKe5f/kUGlpNuBmXs6OSqncF8m1+1lxHuW8ISZJF0ma2RkW3iLkw0g0G4VA=="],
|
||||||
|
|
||||||
|
"@intlify/message-compiler": ["@intlify/message-compiler@11.4.6", "", { "dependencies": { "@intlify/shared": "11.4.6", "source-map-js": "^1.0.2" } }, "sha512-5nj3jULqeTAC1WovwMs1LQWgatTa2pM/rXN9T3XW8rdOtXW9ZF6/GLSNFTKDQmPLwclhPdgUWLJ/4w3fMeeC/Q=="],
|
||||||
|
|
||||||
|
"@intlify/shared": ["@intlify/shared@11.4.6", "", {}, "sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA=="],
|
||||||
|
|
||||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||||
|
|
||||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||||
@@ -340,6 +349,8 @@
|
|||||||
|
|
||||||
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.39", "", { "dependencies": { "@vue/compiler-dom": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw=="],
|
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.39", "", { "dependencies": { "@vue/compiler-dom": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw=="],
|
||||||
|
|
||||||
|
"@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
||||||
|
|
||||||
"@vue/devtools-core": ["@vue/devtools-core@8.1.5", "", { "dependencies": { "@vue/devtools-kit": "^8.1.5", "@vue/devtools-shared": "^8.1.5" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-5e5jQOEssCdZA1wlFEUkIDtb+cAOWuLNWJ52fm4PBWbF7e3oTnM2fneaL42E5lJoolAaUQ678tv/XEb3h4e86Q=="],
|
"@vue/devtools-core": ["@vue/devtools-core@8.1.5", "", { "dependencies": { "@vue/devtools-kit": "^8.1.5", "@vue/devtools-shared": "^8.1.5" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-5e5jQOEssCdZA1wlFEUkIDtb+cAOWuLNWJ52fm4PBWbF7e3oTnM2fneaL42E5lJoolAaUQ678tv/XEb3h4e86Q=="],
|
||||||
|
|
||||||
"@vue/devtools-kit": ["@vue/devtools-kit@8.1.5", "", { "dependencies": { "@vue/devtools-shared": "^8.1.5", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA=="],
|
"@vue/devtools-kit": ["@vue/devtools-kit@8.1.5", "", { "dependencies": { "@vue/devtools-shared": "^8.1.5", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA=="],
|
||||||
@@ -800,6 +811,8 @@
|
|||||||
|
|
||||||
"vue-eslint-parser": ["vue-eslint-parser@10.4.1", "", { "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0 || ^9.0.0", "eslint-visitor-keys": "^4.2.0 || ^5.0.0", "espree": "^10.3.0 || ^11.0.0", "esquery": "^1.6.0", "semver": "^7.6.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" } }, "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA=="],
|
"vue-eslint-parser": ["vue-eslint-parser@10.4.1", "", { "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0 || ^9.0.0", "eslint-visitor-keys": "^4.2.0 || ^5.0.0", "espree": "^10.3.0 || ^11.0.0", "esquery": "^1.6.0", "semver": "^7.6.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" } }, "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA=="],
|
||||||
|
|
||||||
|
"vue-i18n": ["vue-i18n@11.4.6", "", { "dependencies": { "@intlify/core-base": "11.4.6", "@intlify/devtools-types": "11.4.6", "@intlify/shared": "11.4.6", "@vue/devtools-api": "^6.5.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-l0gE7Rfy0phCa5ChKYkOq543Wgd39BCK6hkktfr1Ed4D99oRkgPK9ffShASZdeC8OJxGfdWmpYoAaAH6iLEuIg=="],
|
||||||
|
|
||||||
"vue-tsc": ["vue-tsc@3.3.6", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.6" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-ERXGgbKSBGFUkavrJ1Iwj0ZVxKqB/5UOx65IXy7fPf2UsoI21n3ssQLwdvx8xyUGgJe9PvSZTM/FYzIdwUDPFA=="],
|
"vue-tsc": ["vue-tsc@3.3.6", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.6" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-ERXGgbKSBGFUkavrJ1Iwj0ZVxKqB/5UOx65IXy7fPf2UsoI21n3ssQLwdvx8xyUGgJe9PvSZTM/FYzIdwUDPFA=="],
|
||||||
|
|
||||||
"which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
|
"which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
|
||||||
|
|||||||
+2
-1
@@ -23,7 +23,8 @@
|
|||||||
"@lucide/vue": "^1.23.0",
|
"@lucide/vue": "^1.23.0",
|
||||||
"cron-parser": "^5.6.1",
|
"cron-parser": "^5.6.1",
|
||||||
"date-fns": "^4.4.0",
|
"date-fns": "^4.4.0",
|
||||||
"vue": "^3.5.38"
|
"vue": "^3.5.38",
|
||||||
|
"vue-i18n": "^11.4.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "^4.3.2",
|
"@tailwindcss/vite": "^4.3.2",
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"cron": {
|
||||||
|
"type": {
|
||||||
|
"one_time": "One time",
|
||||||
|
"every_day": "Every day",
|
||||||
|
"every_week": "Every week",
|
||||||
|
"every_month": "Every month",
|
||||||
|
"every_year": "Every year",
|
||||||
|
"specific": "Specific"
|
||||||
|
},
|
||||||
|
"week": [
|
||||||
|
"Monday",
|
||||||
|
"Tuesday",
|
||||||
|
"Wednesday",
|
||||||
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday",
|
||||||
|
"Sunday"
|
||||||
|
],
|
||||||
|
"month": {
|
||||||
|
"st": "st",
|
||||||
|
"nd": "nd",
|
||||||
|
"rd": "rd",
|
||||||
|
"th": "th"
|
||||||
|
},
|
||||||
|
"year": [
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"checked": "Checked",
|
||||||
|
"resets": "Resets",
|
||||||
|
"completed": "Completed",
|
||||||
|
"new_task_hint": "New task",
|
||||||
|
"options": {
|
||||||
|
"title": "Options",
|
||||||
|
"sort_by": "Sort by",
|
||||||
|
"sort": {
|
||||||
|
"created_at": "Creation date",
|
||||||
|
"updated_at": "Creation or update date",
|
||||||
|
"check_date": "Last check date",
|
||||||
|
"reset_date": "Next reset date",
|
||||||
|
"name": "Name"
|
||||||
|
},
|
||||||
|
"reverse_sort": "Reverse sort",
|
||||||
|
"last_checked": "Show last checked time",
|
||||||
|
"next_reset": "Show next reset time",
|
||||||
|
"separate_completed": "Separate completed tasks below"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"cron": {
|
||||||
|
"type": {
|
||||||
|
"one_time": "Une seule fois",
|
||||||
|
"every_day": "Tous les jours",
|
||||||
|
"every_week": "Toutes les semaines",
|
||||||
|
"every_month": "Tous les mois",
|
||||||
|
"every_year": "Tous les ans",
|
||||||
|
"specific": "Spécifique"
|
||||||
|
},
|
||||||
|
"week": [
|
||||||
|
"Lundi",
|
||||||
|
"Mardi",
|
||||||
|
"Mercredi",
|
||||||
|
"Jeudi",
|
||||||
|
"Vendredi",
|
||||||
|
"Samedi",
|
||||||
|
"Dimanche"
|
||||||
|
],
|
||||||
|
"month": {
|
||||||
|
"st": "",
|
||||||
|
"nd": "",
|
||||||
|
"rd": "",
|
||||||
|
"th": ""
|
||||||
|
},
|
||||||
|
"year": [
|
||||||
|
"Janvier",
|
||||||
|
"Février",
|
||||||
|
"Mars",
|
||||||
|
"Avril",
|
||||||
|
"Mai",
|
||||||
|
"Juin",
|
||||||
|
"Juillet",
|
||||||
|
"Août",
|
||||||
|
"Septembre",
|
||||||
|
"Octobre",
|
||||||
|
"Novembre",
|
||||||
|
"Décembre"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"checked": "Complété",
|
||||||
|
"resets": "Reviens",
|
||||||
|
"completed": "Complété",
|
||||||
|
"new_task_hint": "Nouvelle tâche",
|
||||||
|
"options": {
|
||||||
|
"title": "Options",
|
||||||
|
"sort_by": "Trier par",
|
||||||
|
"sort": {
|
||||||
|
"created_at": "Date de création",
|
||||||
|
"updated_at": "Date de modification",
|
||||||
|
"check_date": "Dernière completion",
|
||||||
|
"reset_date": "Prochaine réinitialisation",
|
||||||
|
"name": "Nom"
|
||||||
|
},
|
||||||
|
"reverse_sort": "Inverser le tri",
|
||||||
|
"last_checked": "Afficher la dernière completion",
|
||||||
|
"next_reset": "Afficher la prochaine réinitialisation",
|
||||||
|
"separate_completed": "Séparer les tâches complétées plus bas"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted, defineAsyncComponent } from "vue";
|
||||||
import TaskList from "@/components/TaskList.vue";
|
const TaskList = defineAsyncComponent(() => import("@/components/TaskList.vue"));
|
||||||
|
const PageFooter = defineAsyncComponent(() => import("@/components/PageFooter.vue"));
|
||||||
|
|
||||||
const visible = ref<boolean>(false);
|
const visible = ref<boolean>(false);
|
||||||
|
|
||||||
@@ -12,8 +13,8 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main :style="{ display: visible ? 'inherit' : 'none' }">
|
<main :style="{ display: visible ? 'flex' : 'none' }" class="min-h-screen flex-col">
|
||||||
<div class="hero bg-base-200 min-h-screen">
|
<div class="hero bg-base-200 grow">
|
||||||
<div class="hero-content text-center">
|
<div class="hero-content text-center">
|
||||||
<div class="max-w-md min-w-96 bg-base-100 rounded-box shadow-md">
|
<div class="max-w-md min-w-96 bg-base-100 rounded-box shadow-md">
|
||||||
<h1 class="p-4 pb-2 text-4xl font-bold opacity-60 tracking-wide select-none">Tout Doux</h1>
|
<h1 class="p-4 pb-2 text-4xl font-bold opacity-60 tracking-wide select-none">Tout Doux</h1>
|
||||||
@@ -21,5 +22,6 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<page-footer />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -63,41 +63,24 @@ watch(cron, () => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<select v-model="cronType" class="select" @change="onChangeCronType">
|
<select v-model="cronType" class="select" @change="onChangeCronType">
|
||||||
<option :value="CronType.ONE_TIME">One time (do not repeat)</option>
|
<option :value="CronType.ONE_TIME">{{ $t('cron.type.one_time') }}</option>
|
||||||
<option :value="CronType.EVERY_DAY">Every day at</option>
|
<option :value="CronType.EVERY_DAY">{{ $t('cron.type.every_day') }}</option>
|
||||||
<option :value="CronType.EVERY_WEEK">Every week on</option>
|
<option :value="CronType.EVERY_WEEK">{{ $t('cron.type.every_week') }}</option>
|
||||||
<option :value="CronType.EVERY_MONTH">Every month on the</option>
|
<option :value="CronType.EVERY_MONTH">{{ $t('cron.type.every_month') }}</option>
|
||||||
<option :value="CronType.EVERY_YEAR">Every year on</option>
|
<option :value="CronType.EVERY_YEAR">{{ $t('cron.type.every_year') }}</option>
|
||||||
<option :value="CronType.SPECIFIC">Specific</option>
|
<option :value="CronType.SPECIFIC">{{ $t('cron.type.specific') }}</option>
|
||||||
</select>
|
</select>
|
||||||
<select v-if="cronType === CronType.EVERY_DAY" v-model="cronValue" class="select" @change="onChangeCronValue">
|
<select v-if="cronType === CronType.EVERY_DAY" v-model="cronValue" class="select" @change="onChangeCronValue">
|
||||||
<option v-for="i in Array.from(Array(24).keys())" :key="`h${i}`" :value="i">{{ i.toFixed(0).padStart(2, '0') }}:00</option>
|
<option v-for="i in Array.from(Array(24).keys())" :key="`h${i}`" :value="i">{{ i.toFixed(0).padStart(2, '0') }}:00</option>
|
||||||
</select>
|
</select>
|
||||||
<select v-if="cronType === CronType.EVERY_WEEK" v-model="cronValue" class="select" @change="onChangeCronValue">
|
<select v-if="cronType === CronType.EVERY_WEEK" v-model="cronValue" class="select" @change="onChangeCronValue">
|
||||||
<option :value="1">Monday</option>
|
<option v-for="i in Array.from(Array(7).keys())" :key="`w${i}`" :value="i + 1">{{ $t(`cron.week.${i.toFixed(0)}`) }}</option>
|
||||||
<option :value="2">Tuesday</option>
|
|
||||||
<option :value="3">Wednesday</option>
|
|
||||||
<option :value="4">Thursday</option>
|
|
||||||
<option :value="5">Friday</option>
|
|
||||||
<option :value="6">Saturday</option>
|
|
||||||
<option :value="7">Sunday</option>
|
|
||||||
</select>
|
</select>
|
||||||
<select v-if="cronType === CronType.EVERY_MONTH" v-model="cronValue" class="select" @change="onChangeCronValue">
|
<select v-if="cronType === CronType.EVERY_MONTH" v-model="cronValue" class="select" @change="onChangeCronValue">
|
||||||
<option v-for="i in Array.from(Array(32).keys())" :key="`d${i}`" :value="i">{{i}}{{ i % 10 == 1 ? 'st' : (i % 10 === 2 ? 'nd': (i % 10 === 3 ? 'rd' : 'th')) }}</option>
|
<option v-for="i in Array.from(Array(31).keys())" :key="`d${i}`" :value="i + 1">{{i + 1}}{{ i % 10 == 0 ? $t('cron.month.st') : (i % 10 === 1 ? $t('cron.month.nd'): (i % 10 === 2 ? $t('cron.month.rd') : $t('cron.month.th'))) }}</option>
|
||||||
</select>
|
</select>
|
||||||
<select v-if="cronType === CronType.EVERY_YEAR" v-model="cronValue" class="select" @change="onChangeCronValue">
|
<select v-if="cronType === CronType.EVERY_YEAR" v-model="cronValue" class="select" @change="onChangeCronValue">
|
||||||
<option :value="1">January</option>
|
<option v-for="i in Array.from(Array(12).keys())" :key="`m${i}`" :value="i + 1">{{ $t(`cron.year.${i.toFixed(0)}`) }}</option>
|
||||||
<option :value="2">February</option>
|
|
||||||
<option :value="3">March</option>
|
|
||||||
<option :value="4">April</option>
|
|
||||||
<option :value="5">May</option>
|
|
||||||
<option :value="6">June</option>
|
|
||||||
<option :value="7">July</option>
|
|
||||||
<option :value="8">August</option>
|
|
||||||
<option :value="9">September</option>
|
|
||||||
<option :value="10">October</option>
|
|
||||||
<option :value="11">November</option>
|
|
||||||
<option :value="12">December</option>
|
|
||||||
</select>
|
</select>
|
||||||
<input v-if="cronType === CronType.SPECIFIC" v-model="rawCron" type="text" class="input font-mono" :class="valid ? '' : 'input-error'" @input="onInputRawCron" />
|
<input v-if="cronType === CronType.SPECIFIC" v-model="rawCron" type="text" class="input font-mono" :class="valid ? '' : 'input-error'" @input="onInputRawCron" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { setCookie } from '@/lib/cookies';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
|
function onSetLang(lang: string) {
|
||||||
|
i18n.locale.value = lang;
|
||||||
|
setCookie('lang', lang);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a href="#" @click.prevent="onSetLang('fr')">🇫🇷</a> / <a href="#" @click.prevent="onSetLang('en')">🇺🇸</a>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { defineAsyncComponent } from "vue";
|
||||||
|
|
||||||
|
const LangInput = defineAsyncComponent(() => import("@/components/LangInput.vue"));
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<footer class="footer sm:footer-horizontal footer-center bg-base-300 text-base-content p-2">
|
||||||
|
<aside>
|
||||||
|
<p>
|
||||||
|
<lang-input /> - 2026 -
|
||||||
|
<a
|
||||||
|
href="https://git.klemek.fr/klemek/tout-doux"
|
||||||
|
class="underline">
|
||||||
|
klemek
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeMount, ref, watch, computed } from "vue";
|
import { onBeforeMount, ref, watch, computed, defineAsyncComponent } from "vue";
|
||||||
import type { Task } from "@/types";
|
import type { Task } from "@/types";
|
||||||
import { Copy, Trash, Save, X } from "@lucide/vue";
|
import { Copy, Trash, Save, X } from "@lucide/vue";
|
||||||
import { updateTask } from "@/api/tasks";
|
import { updateTask } from "@/api/tasks";
|
||||||
import CronInput from "@/components/CronInput.vue";
|
const CronInput = defineAsyncComponent(() => import("@/components/CronInput.vue"));
|
||||||
import { taskCompleted, taskNextReset } from "@/lib/tasks";
|
import { taskCompleted, taskNextReset } from "@/lib/tasks";
|
||||||
import { relativeTime } from "@/lib/dates";
|
import { relativeTime } from "@/lib/dates";
|
||||||
|
|
||||||
@@ -85,10 +85,10 @@ watch(task, () => {
|
|||||||
>
|
>
|
||||||
<div>{{ task.name }}</div>
|
<div>{{ task.name }}</div>
|
||||||
<div v-if="showLastChecked && !checked && task.check_date" class="font-light text-xs italic">
|
<div v-if="showLastChecked && !checked && task.check_date" class="font-light text-xs italic">
|
||||||
Checked {{ relativeTime(task.check_date) }}
|
{{ $t('checked') }} {{ relativeTime(task.check_date, $i18n.locale) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showNextReset && checked && nextReset" class="font-light text-xs italic">
|
<div v-if="showNextReset && checked && nextReset" class="font-light text-xs italic">
|
||||||
Resets {{ relativeTime(nextReset) }}
|
{{ $t('resets') }} {{ relativeTime(nextReset, $i18n.locale) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onBeforeMount, computed } from "vue";
|
import { ref, onBeforeMount, computed, defineAsyncComponent } from "vue";
|
||||||
import type { Task } from "@/types";
|
import type { Task } from "@/types";
|
||||||
import { getTasks, createTask, deleteTask } from "@/api/tasks";
|
import { getTasks, createTask, deleteTask } from "@/api/tasks";
|
||||||
import { CirclePlus } from "@lucide/vue";
|
import { CirclePlus } from "@lucide/vue";
|
||||||
import TaskItem from "@/components/TaskItem.vue";
|
const TaskItem = defineAsyncComponent(() => import("@/components/TaskItem.vue"));
|
||||||
import { taskCompleted, sortTasks } from "@/lib/tasks";
|
import { taskCompleted, sortTasks } from "@/lib/tasks";
|
||||||
import { SortType } from "@/enums";
|
import { SortType } from "@/enums";
|
||||||
import { booleanCookieRef, enumCookieRef } from "@/lib/cookies";
|
import { booleanCookieRef, enumCookieRef } from "@/lib/cookies";
|
||||||
@@ -93,7 +93,7 @@ onBeforeMount(fetchTasks);
|
|||||||
<input
|
<input
|
||||||
v-model="taskInput"
|
v-model="taskInput"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="New Task"
|
:placeholder="$t('new_task_hint')"
|
||||||
class="input"
|
class="input"
|
||||||
@keyup.enter="onNewTask"
|
@keyup.enter="onNewTask"
|
||||||
/>
|
/>
|
||||||
@@ -111,7 +111,7 @@ onBeforeMount(fetchTasks);
|
|||||||
</ul>
|
</ul>
|
||||||
<div v-if="separateCompleted && hasCompleted" class="collapse collapse-arrow">
|
<div v-if="separateCompleted && hasCompleted" class="collapse collapse-arrow">
|
||||||
<input id="completed-dropdown" type="checkbox" />
|
<input id="completed-dropdown" type="checkbox" />
|
||||||
<div class="collapse-title mb-0">Completed</div>
|
<div class="collapse-title mb-0">{{ $t('completed') }}</div>
|
||||||
<div class="collapse-content p-0">
|
<div class="collapse-content p-0">
|
||||||
<ul class="list">
|
<ul class="list">
|
||||||
<template v-for="(task, i) in tasks" :key="`${task.id}-completed`">
|
<template v-for="(task, i) in tasks" :key="`${task.id}-completed`">
|
||||||
@@ -130,34 +130,34 @@ onBeforeMount(fetchTasks);
|
|||||||
</div>
|
</div>
|
||||||
<div class="collapse collapse-arrow font-light text-sm">
|
<div class="collapse collapse-arrow font-light text-sm">
|
||||||
<input id="options-dropdown" type="checkbox"/>
|
<input id="options-dropdown" type="checkbox"/>
|
||||||
<div class="collapse-title">Options</div>
|
<div class="collapse-title">{{ $t('options.title') }}</div>
|
||||||
<div class="collapse-content text-sm">
|
<div class="collapse-content text-sm">
|
||||||
<fieldset class="fieldset flex flex-col gap-2">
|
<fieldset class="fieldset flex flex-col gap-2">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
Sort by
|
Sort by
|
||||||
<select v-model="sortType" class="select" @change="onChangeSortType">
|
<select v-model="sortType" class="select" @change="onChangeSortType">
|
||||||
<option :value="SortType.CREATED_AT">Creation date</option>
|
<option :value="SortType.CREATED_AT">{{ $t('options.sort.created_at') }}</option>
|
||||||
<option :value="SortType.UPDATED_AT">Creation or update date</option>
|
<option :value="SortType.UPDATED_AT">{{ $t('options.sort.updated_at') }}</option>
|
||||||
<option :value="SortType.CHECK_DATE">Last check date</option>
|
<option :value="SortType.CHECK_DATE">{{ $t('options.sort.check_date') }}</option>
|
||||||
<option :value="SortType.RESET_DATE">Next reset date</option>
|
<option :value="SortType.RESET_DATE">{{ $t('options.sort.reset_date') }}</option>
|
||||||
<option :value="SortType.NAME">Name</option>
|
<option :value="SortType.NAME">{{ $t('options.sort.name') }}</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<input v-model="sortReverse" type="checkbox" class="checkbox" @change="onChangeSortReverse" />
|
<input v-model="sortReverse" type="checkbox" class="checkbox" @change="onChangeSortReverse" />
|
||||||
Reverse sort
|
{{ $t('options.reverse_sort') }}
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<input v-model="showLastChecked" type="checkbox" class="checkbox" />
|
<input v-model="showLastChecked" type="checkbox" class="checkbox" />
|
||||||
Show last checked time
|
{{ $t('options.last_checked') }}
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<input v-model="showNextReset" type="checkbox" class="checkbox" />
|
<input v-model="showNextReset" type="checkbox" class="checkbox" />
|
||||||
Show next reset time
|
{{ $t('options.next_reset') }}
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<input v-model="separateCompleted" type="checkbox" class="checkbox" />
|
<input v-model="separateCompleted" type="checkbox" class="checkbox" />
|
||||||
Move completed tasks below
|
{{ $t('options.separate_completed') }}
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
|
import { enUS, fr } from 'date-fns/locale';
|
||||||
|
|
||||||
|
export function relativeTime(date: Date, locale: string): string {
|
||||||
export function relativeTime(date: Date): string {
|
return formatDistanceToNow(date, {addSuffix: true, includeSeconds: false, locale: locale === 'fr' ? fr : enUS});
|
||||||
return formatDistanceToNow(date, {addSuffix: true, includeSeconds: false});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { getCookie, setCookie } from "./cookies";
|
||||||
|
|
||||||
|
export function getLang() {
|
||||||
|
const params = new URLSearchParams(document.location.search);
|
||||||
|
const paramsLang = params.get("lang");
|
||||||
|
if (paramsLang !== null) {
|
||||||
|
setCookie("lang", paramsLang);
|
||||||
|
return paramsLang;
|
||||||
|
}
|
||||||
|
return getCookie("lang", "en");
|
||||||
|
}
|
||||||
+15
-2
@@ -1,4 +1,17 @@
|
|||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import App from "./App.vue";
|
import { createI18n } from "vue-i18n";
|
||||||
|
import App from "@/App.vue";
|
||||||
|
import { getLang } from "@/lib/lang";
|
||||||
|
const en = await import("@lang/en.json");
|
||||||
|
const fr = await import("@lang/fr.json");
|
||||||
|
|
||||||
createApp(App).mount("#app");
|
const i18n = createI18n({
|
||||||
|
locale: getLang(),
|
||||||
|
fallbackLocale: "en",
|
||||||
|
messages: {
|
||||||
|
en: en,
|
||||||
|
fr: fr,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
createApp(App).use(i18n).mount("#app");
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"vue.d.ts",
|
"vue.d.ts",
|
||||||
"resources/ts/**/*",
|
"resources/ts/**/*",
|
||||||
"resources/ts/**/*.vue",
|
"resources/ts/**/*.vue",
|
||||||
|
"resources/lang/*.json",
|
||||||
],
|
],
|
||||||
"exclude": ["resources/**/__tests__/*"],
|
"exclude": ["resources/**/__tests__/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./resources/ts/*"],
|
"@/*": ["./resources/ts/*"],
|
||||||
|
"@lang/*": ["./resources/lang/*"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export default defineConfig({
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": fileURLToPath(new URL("./resources/ts", import.meta.url)),
|
"@": fileURLToPath(new URL("./resources/ts", import.meta.url)),
|
||||||
|
"@lang": fileURLToPath(new URL("./resources/lang", import.meta.url)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user