Merge branch 'dev' of https://github.com/hay-kot/mealie into feature/authentication

This commit is contained in:
hay-kot 2021-03-20 11:55:02 -08:00
commit e5e4846848
27 changed files with 317 additions and 204 deletions

View file

@ -9,11 +9,10 @@
"python.testing.nosetestsEnabled": false, "python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": true, "python.testing.pytestEnabled": true,
"python.testing.autoTestDiscoverOnSaveEnabled": false, "python.testing.autoTestDiscoverOnSaveEnabled": false,
"python.testing.pytestArgs": [ "python.testing.pytestArgs": ["tests"],
"tests"
],
"cSpell.enableFiletypes": ["!javascript", "!python"], "cSpell.enableFiletypes": ["!javascript", "!python"],
"i18n-ally.localesPaths": "frontend/src/locales", "i18n-ally.localesPaths": "frontend/src/locales/messages",
"i18n-ally.sourceLanguage": "en",
"i18n-ally.enabledFrameworks": ["vue"], "i18n-ally.enabledFrameworks": ["vue"],
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",
"cSpell.words": ["performant"], "cSpell.words": ["performant"],

View file

@ -70,7 +70,7 @@ export default {
this.search = !this.search; this.search = !this.search;
} }
}); });
this.$store.dispatch("initLang"); this.$store.dispatch("initLang", { currentVueComponent: this });
}, },
mounted() { mounted() {

View file

@ -92,39 +92,39 @@ export default {
{ {
icon: "mdi-cog", icon: "mdi-cog",
to: "/admin/settings", to: "/admin/settings",
title: "Site Settings", title: this.$t('settings.site-settings'),
}, },
{ {
icon: "mdi-account-group", icon: "mdi-account-group",
to: "/admin/manage-users", to: "/admin/manage-users",
title: "Manage Users", title: this.$t('settings.manage-users'),
}, },
{ {
icon: "mdi-backup-restore", icon: "mdi-backup-restore",
to: "/admin/backups", to: "/admin/backups",
title: "Backups", title: this.$t('settings.backup-and-exports'),
}, },
{ {
icon: "mdi-database-import", icon: "mdi-database-import",
to: "/admin/migrations", to: "/admin/migrations",
title: "Migrations", title: this.$t('settings.migrations'),
}, },
], ],
baseLinks: [ baseLinks: [
{ {
icon: "mdi-account", icon: "mdi-account",
to: "/admin/profile", to: "/admin/profile",
title: "Profile", title: this.$t('settings.profile'),
}, },
{ {
icon: "mdi-format-color-fill", icon: "mdi-format-color-fill",
to: "/admin/themes", to: "/admin/themes",
title: "Themes", title: this.$t('general.themes'),
}, },
{ {
icon: "mdi-food", icon: "mdi-food",
to: "/admin/meal-planner", to: "/admin/meal-planner",
title: "Meal Planner", title: this.$t('meal-plan.meal-planner'),
}, },
], ],
}; };

View file

@ -38,7 +38,7 @@
</v-icon> </v-icon>
<v-toolbar-title class="headline"> <v-toolbar-title class="headline">
Home Page Sections {{$t('settings.homepage.home-page-sections')}}
</v-toolbar-title> </v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
@ -80,7 +80,7 @@
</v-icon> </v-icon>
<v-toolbar-title class="headline"> <v-toolbar-title class="headline">
All Categories {{$t('settings.homepage.all-categories')}}
</v-toolbar-title> </v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>

View file

@ -2,8 +2,8 @@
<div> <div>
<Confirmation <Confirmation
ref="deleteGroupConfirm" ref="deleteGroupConfirm"
title="Confirm Group Deletion" :title="$t('user.confirm-group-deletion')"
:message="`Are you sure you want to delete <b>${group.name}<b/>`" :message="$t('user.are-you-sure-you-want-to-delete-the-group', { groupName:group.name })"
icon="mdi-alert" icon="mdi-alert"
@confirm="deleteGroup" @confirm="deleteGroup"
:width="450" :width="450"
@ -13,7 +13,7 @@
<v-list dense> <v-list dense>
<v-card-title class="py-1">{{ group.name }}</v-card-title> <v-card-title class="py-1">{{ group.name }}</v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<v-subheader>Group ID: {{ group.id }}</v-subheader> <v-subheader>{{ $t('user.group-id-with-value', { groupID: group.id }) }}</v-subheader>
<v-list-item-group color="primary"> <v-list-item-group color="primary">
<v-list-item v-for="property in groupProps" :key="property.text"> <v-list-item v-for="property in groupProps" :key="property.text">
<v-list-item-icon> <v-list-item-icon>
@ -36,11 +36,11 @@
@click="confirmDelete" @click="confirmDelete"
:disabled="ableToDelete" :disabled="ableToDelete"
> >
Delete {{ $t('general.delete') }}
</v-btn> </v-btn>
<!-- Coming Soon! --> <!-- Coming Soon! -->
<v-btn small color="success" disabled> <v-btn small color="success" disabled>
Edit {{ $t('general.edit') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@ -94,22 +94,22 @@ export default {
buildData() { buildData() {
this.groupProps = [ this.groupProps = [
{ {
text: "Total Users", text: this.$t('user.total-users'),
icon: "mdi-account", icon: "mdi-account",
value: this.group.users.length, value: this.group.users.length,
}, },
{ {
text: "Total MealPlans", text: this.$t('user.total-mealplans'),
icon: "mdi-food", icon: "mdi-food",
value: this.group.mealplans.length, value: this.group.mealplans.length,
}, },
{ {
text: "Webhooks Enabled", text: this.$t('user.webhooks-enabled'),
icon: "mdi-webhook", icon: "mdi-webhook",
value: this.group.webhookEnable ? "True" : "False", value: this.group.webhookEnable ? this.$t('general.yes') : this.$t('general.no'),
}, },
{ {
text: "Webhook Time", text: this.$t('user.webhook-time'),
icon: "mdi-clock-outline", icon: "mdi-clock-outline",
value: this.group.webhookTime, value: this.group.webhookTime,
}, },

View file

@ -9,7 +9,7 @@
clearable clearable
class="mr-2 pt-0" class="mr-2 pt-0"
append-icon="mdi-filter" append-icon="mdi-filter"
label="Filter" :label="$t('general.filter')"
single-line single-line
hide-details hide-details
></v-text-field> ></v-text-field>
@ -24,7 +24,7 @@
v-bind="attrs" v-bind="attrs"
v-on="on" v-on="on"
> >
Create Group {{ $t('user.create-group') }}
</v-btn> </v-btn>
</template> </template>
<v-card> <v-card>
@ -34,7 +34,7 @@
</v-icon> </v-icon>
<v-toolbar-title class="headline"> <v-toolbar-title class="headline">
Create Group {{ $t('user.create-group') }}
</v-toolbar-title> </v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
@ -44,7 +44,7 @@
<v-form ref="newGroup"> <v-form ref="newGroup">
<v-text-field <v-text-field
v-model="newGroupName" v-model="newGroupName"
label="Group Name" :label="$t('user.group-name')"
:rules="[existsRule]" :rules="[existsRule]"
></v-text-field> ></v-text-field>
</v-form> </v-form>
@ -53,10 +53,10 @@
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="grey" text @click="groupDialog = false"> <v-btn color="grey" text @click="groupDialog = false">
Cancel {{ $t('general.cancel') }}
</v-btn> </v-btn>
<v-btn color="primary" @click="createGroup"> <v-btn color="primary" @click="createGroup">
Create {{ $t('general.create') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@ -67,7 +67,7 @@
<v-col <v-col
:sm="6" :sm="6"
:md="6" :md="6"
:lg="3" :lg="4"
:xl="3" :xl="3"
v-for="group in groups" v-for="group in groups"
:key="group.id" :key="group.id"

View file

@ -2,8 +2,8 @@
<v-card outlined class="mt-n1"> <v-card outlined class="mt-n1">
<Confirmation <Confirmation
ref="deleteUserDialog" ref="deleteUserDialog"
title="Confirm User Deletion" :title="$t('user.confirm-link-deletion')"
:message="`Are you sure you want to delete the link <b>${activeName}<b/>`" :message="$t('user.are-you-sure-you-want-to-delete-the-link', {link: activeName })"
icon="mdi-alert" icon="mdi-alert"
@confirm="deleteUser" @confirm="deleteUser"
:width="450" :width="450"
@ -14,14 +14,14 @@
mdi-link-variant mdi-link-variant
</v-icon> </v-icon>
<v-toolbar-title class="headine"> <v-toolbar-title class="headine">
Sign Up Links {{ $t('user.sign-up-links') }}
</v-toolbar-title> </v-toolbar-title>
<v-spacer> </v-spacer> <v-spacer> </v-spacer>
<v-dialog v-model="dialog" max-width="600px"> <v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn small color="success" dark v-bind="attrs" v-on="on"> <v-btn small color="success" dark v-bind="attrs" v-on="on">
Create Link {{ $t('user.create-link') }}
</v-btn> </v-btn>
</template> </template>
<v-card> <v-card>
@ -31,7 +31,7 @@
</v-icon> </v-icon>
<v-toolbar-title class="headline"> <v-toolbar-title class="headline">
Create Link {{ $t('user.create-link') }}
</v-toolbar-title> </v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
@ -43,13 +43,13 @@
<v-text-field <v-text-field
class="mr-2" class="mr-2"
v-model="editedItem.name" v-model="editedItem.name"
label="Link Name" :label="$t('user.link-name')"
:rules="[existsRule]" :rules="[existsRule]"
validate-on-blur validate-on-blur
></v-text-field> ></v-text-field>
<v-checkbox <v-checkbox
v-model="editedItem.admin" v-model="editedItem.admin"
label="Admin" :label="$t('user.admin')"
></v-checkbox> ></v-checkbox>
</v-row> </v-row>
</v-form> </v-form>
@ -58,10 +58,10 @@
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="grey" text @click="close"> <v-btn color="grey" text @click="close">
Cancel {{ $t('general.cancel') }}
</v-btn> </v-btn>
<v-btn color="primary" @click="save"> <v-btn color="primary" @click="save">
Save {{ $t('general.save') }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@ -90,7 +90,7 @@
<v-icon small left> <v-icon small left>
mdi-account-cog mdi-account-cog
</v-icon> </v-icon>
{{ item.admin ? "Yes" : "No" }} {{ item.admin ? $t('general.yes') : $t('general.no') }}
</v-btn> </v-btn>
</template> </template>
<template v-slot:item.actions="{ item }"> <template v-slot:item.actions="{ item }">
@ -98,7 +98,7 @@
<v-icon small left> <v-icon small left>
mdi-delete mdi-delete
</v-icon> </v-icon>
Delete {{ $t('general.delete') }}
</v-btn> </v-btn>
</template> </template>
</v-data-table> </v-data-table>
@ -113,37 +113,39 @@ import { validators } from "@/mixins/validators";
export default { export default {
components: { Confirmation }, components: { Confirmation },
mixins: [validators], mixins: [validators],
data: () => ({ data() {
dialog: false, return {
activeId: null, dialog: false,
activeName: null, activeId: null,
headers: [ activeName: null,
{ headers: [
text: "Link ID", {
align: "start", text: this.$t('user.link-id'),
sortable: false, align: "start",
value: "id", sortable: false,
value: "id",
},
{ text: this.$t('general.name'), value: "name" },
{ text: this.$t('general.token'), value: "token" },
{ text: this.$t('user.admin'), value: "admin", align: "center" },
{ text: "", value: "actions", sortable: false, align: "center" },
],
links: [],
editedIndex: -1,
editedItem: {
name: "",
admin: false,
token: "",
id: 0,
}, },
{ text: "Name", value: "name" }, defaultItem: {
{ text: "Token", value: "token" }, name: "",
{ text: "Admin", value: "admin", align: "center" }, token: "",
{ text: "", value: "actions", sortable: false, align: "center" }, admin: false,
], id: 0,
links: [], },
editedIndex: -1, }
editedItem: { },
name: "",
admin: false,
token: "",
id: 0,
},
defaultItem: {
name: "",
token: "",
admin: false,
id: 0,
},
}),
computed: { computed: {
baseURL() { baseURL() {

View file

@ -2,10 +2,8 @@
<v-card outlined class="mt-n1"> <v-card outlined class="mt-n1">
<Confirmation <Confirmation
ref="deleteUserDialog" ref="deleteUserDialog"
title="Confirm User Deletion" :title="$t('user.confirm-user-deletion')"
:message=" :message="$t('user.are-you-sure-you-want-to-delete-the-user', { activeName, activeId })"
`Are you sure you want to delete the user <b>${activeName} ID: ${activeId}<b/>`
"
icon="mdi-alert" icon="mdi-alert"
@confirm="deleteUser" @confirm="deleteUser"
:width="450" :width="450"
@ -18,7 +16,7 @@
v-model="search" v-model="search"
class="mr-2" class="mr-2"
append-icon="mdi-filter" append-icon="mdi-filter"
label="Filter" :label="$t('general.filter')"
single-line single-line
hide-details hide-details
></v-text-field> ></v-text-field>
@ -27,7 +25,7 @@
<v-dialog v-model="dialog" max-width="600px"> <v-dialog v-model="dialog" max-width="600px">
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn small color="success" dark v-bind="attrs" v-on="on"> <v-btn small color="success" dark v-bind="attrs" v-on="on">
Create User {{$t('user.create-user')}}
</v-btn> </v-btn>
</template> </template>
<v-card> <v-card>
@ -42,7 +40,7 @@
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-toolbar-title class="headline"> <v-toolbar-title class="headline">
User ID: {{ editedItem.id }} {{$t('user.user-id-with-value', {id: editedItem.id }) }}
</v-toolbar-title> </v-toolbar-title>
</v-app-bar> </v-app-bar>
@ -52,7 +50,7 @@
<v-col cols="12" sm="12" md="6"> <v-col cols="12" sm="12" md="6">
<v-text-field <v-text-field
v-model="editedItem.fullName" v-model="editedItem.fullName"
label="Full Name" :label="$t('user.full-name')"
:rules="[existsRule]" :rules="[existsRule]"
validate-on-blur validate-on-blur
></v-text-field> ></v-text-field>
@ -60,7 +58,7 @@
<v-col cols="12" sm="12" md="6"> <v-col cols="12" sm="12" md="6">
<v-text-field <v-text-field
v-model="editedItem.email" v-model="editedItem.email"
label="Email" :label="$t('user.email')"
:rules="[existsRule, emailRule]" :rules="[existsRule, emailRule]"
validate-on-blur validate-on-blur
></v-text-field> ></v-text-field>
@ -70,19 +68,19 @@
dense dense
v-model="editedItem.group" v-model="editedItem.group"
:items="existingGroups" :items="existingGroups"
label="User Group" :label="$t('user.user-group')"
></v-select> ></v-select>
</v-col> </v-col>
<v-col cols="12" sm="12" md="6" v-if="showPassword"> <v-col cols="12" sm="12" md="6" v-if="showPassword">
<v-text-field <v-text-field
dense dense
v-model="editedItem.password" v-model="editedItem.password"
label="User Password" :label="$t('user.user-password')"
:rules="[existsRule, minRule]" :rules="[existsRule, minRule]"
></v-text-field> ></v-text-field>
</v-col> </v-col>
<v-col cols="12" sm="12" md="3"> <v-col cols="12" sm="12" md="3">
<v-switch v-model="editedItem.admin" label="Admin"></v-switch> <v-switch v-model="editedItem.admin" :label="$t('user.admin')"></v-switch>
</v-col> </v-col>
</v-row> </v-row>
</v-form> </v-form>
@ -91,10 +89,10 @@
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="grey" text @click="close"> <v-btn color="grey" text @click="close">
Cancel {{$t('general.cancel')}}
</v-btn> </v-btn>
<v-btn color="primary" @click="save"> <v-btn color="primary" @click="save">
Save {{$t('general.save')}}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@ -113,13 +111,13 @@
<v-icon small left> <v-icon small left>
mdi-delete mdi-delete
</v-icon> </v-icon>
Delete {{$t('general.delete')}}
</v-btn> </v-btn>
<v-btn small color="success" @click="editItem(item)"> <v-btn small color="success" @click="editItem(item)">
<v-icon small left class="mr-2"> <v-icon small left class="mr-2">
mdi-pencil mdi-pencil
</v-icon> </v-icon>
Edit {{$t('general.edit')}}
</v-btn> </v-btn>
</template> </template>
<template v-slot:item.admin="{ item }"> <template v-slot:item.admin="{ item }">
@ -127,7 +125,7 @@
</template> </template>
<template v-slot:no-data> <template v-slot:no-data>
<v-btn color="primary" @click="initialize"> <v-btn color="primary" @click="initialize">
Reset {{$t('general.reset')}}
</v-btn> </v-btn>
</template> </template>
</v-data-table> </v-data-table>
@ -142,47 +140,49 @@ import { validators } from "@/mixins/validators";
export default { export default {
components: { Confirmation }, components: { Confirmation },
mixins: [validators], mixins: [validators],
data: () => ({ data() {
search: "", return {
dialog: false, search: "",
activeId: null, dialog: false,
activeName: null, activeId: null,
headers: [ activeName: null,
{ headers: [
text: "User ID", {
align: "start", text: this.$t("user.user-id"),
sortable: false, align: "start",
value: "id", sortable: false,
value: "id",
},
{ text: this.$t('user.full-name'), value: "fullName" },
{ text: this.$t('user.email'), value: "email" },
{ text: this.$t('user.group'), value: "group" },
{ text: this.$t('user.admin'), value: "admin" },
{ text: "", value: "actions", sortable: false, align: "center" },
],
users: [],
editedIndex: -1,
editedItem: {
id: 0,
fullName: "",
password: "",
email: "",
group: "",
admin: false,
}, },
{ text: "Full Name", value: "fullName" }, defaultItem: {
{ text: "Email", value: "email" }, id: 0,
{ text: "Group", value: "group" }, fullName: "",
{ text: "Admin", value: "admin" }, password: "",
{ text: "", value: "actions", sortable: false, align: "center" }, email: "",
], group: "",
users: [], admin: false,
editedIndex: -1, },
editedItem: { }
id: 0, },
fullName: "",
password: "",
email: "",
group: "",
admin: false,
},
defaultItem: {
id: 0,
fullName: "",
password: "",
email: "",
group: "",
admin: false,
},
}),
computed: { computed: {
formTitle() { formTitle() {
return this.editedIndex === -1 ? "New User" : "Edit User"; return this.editedIndex === -1 ? this.$t('user.new-user') : this.$t('user.edit-user');
}, },
showPassword() { showPassword() {
return this.editedIndex === -1 ? true : false; return this.editedIndex === -1 ? true : false;

View file

@ -10,7 +10,7 @@
/> />
<v-card flat outlined class="ma-2"> <v-card flat outlined class="ma-2">
<v-card-text class="mb-n5 mt-n2"> <v-card-text class="mb-n5 mt-n2">
<h3>{{ theme.name }} {{ current ? "(Current)" : "" }}</h3> <h3>{{ theme.name }} {{ current ? $t('general.current-parenthesis') : "" }}</h3>
</v-card-text> </v-card-text>
<v-card-text> <v-card-text>
<v-row flex align-center> <v-row flex align-center>
@ -27,10 +27,10 @@
</v-card-text> </v-card-text>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-actions> <v-card-actions>
<v-btn text color="error" @click="confirmDelete"> Delete </v-btn> <v-btn text color="error" @click="confirmDelete"> {{$t('general.delete')}} </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<!-- <v-btn text color="accent" @click="editTheme">Edit</v-btn> --> <!-- <v-btn text color="accent" @click="editTheme">Edit</v-btn> -->
<v-btn text color="success" @click="saveThemes">Apply</v-btn> <v-btn text color="success" @click="saveThemes">{{$t('general.apply')}}</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</div> </div>

View file

@ -13,7 +13,7 @@
class="mr-2" class="mr-2"
> >
</v-progress-circular> </v-progress-circular>
<v-toolbar-title class="headline"> Login </v-toolbar-title> <v-toolbar-title class="headline">{{$t('user.login')}}</v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
</v-app-bar> </v-app-bar>
<v-card-text> <v-card-text>
@ -30,7 +30,7 @@
light="light" light="light"
prepend-icon="mdi-email" prepend-icon="mdi-email"
validate-on-blur validate-on-blur
:label="$t('login.email')" :label="$t('user.email')"
type="email" type="email"
></v-text-field> ></v-text-field>
<v-text-field <v-text-field
@ -38,7 +38,7 @@
light="light" light="light"
class="mb-2s" class="mb-2s"
prepend-icon="mdi-lock" prepend-icon="mdi-lock"
:label="$t('login.password')" :label="$t('user.password')"
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"
:append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'" :append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
@click:append="showPassword = !showPassword" @click:append="showPassword = !showPassword"
@ -52,11 +52,11 @@
color="primary" color="primary"
block="block" block="block"
type="submit" type="submit"
>{{ $t("login.sign-in") }}</v-btn >{{ $t("user.sign-in") }}</v-btn
> >
</v-card-actions> </v-card-actions>
<v-alert v-if="error" outlined class="mt-3 mb-0" type="error"> <v-alert v-if="error" outlined class="mt-3 mb-0" type="error">
Could Not Validate Credentials {{$t('user.could-not-validate-credentials')}}
</v-alert> </v-alert>
</v-card-text> </v-card-text>
</v-card> </v-card>

View file

@ -37,7 +37,7 @@
prepend-icon="mdi-email" prepend-icon="mdi-email"
validate-on-blur validate-on-blur
:rules="[existsRule, emailRule]" :rules="[existsRule, emailRule]"
:label="$t('login.email')" :label="$t('user.email')"
type="email" type="email"
></v-text-field> ></v-text-field>
<v-text-field <v-text-field
@ -46,7 +46,7 @@
class="mb-2s" class="mb-2s"
prepend-icon="mdi-lock" prepend-icon="mdi-lock"
validate-on-blur validate-on-blur
:label="$t('login.password')" :label="$t('user.password')"
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"
:rules="[minRule]" :rules="[minRule]"
></v-text-field> ></v-text-field>
@ -55,7 +55,7 @@
light="light" light="light"
class="mb-2s" class="mb-2s"
prepend-icon="mdi-lock" prepend-icon="mdi-lock"
:label="$t('login.password')" :label="$t('user.password')"
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"
:append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'" :append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
:rules="[ :rules="[

View file

@ -15,15 +15,15 @@
<v-menu offset-y v-if="sortable"> <v-menu offset-y v-if="sortable">
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn-toggle group> <v-btn-toggle group>
<v-btn text v-bind="attrs" v-on="on"> Sort </v-btn> <v-btn text v-bind="attrs" v-on="on">{{$t('general.sort')}}</v-btn>
</v-btn-toggle> </v-btn-toggle>
</template> </template>
<v-list> <v-list>
<v-list-item @click="$emit('sort-recent')"> <v-list-item @click="$emit('sort-recent')">
<v-list-item-title> Recent </v-list-item-title> <v-list-item-title>{{$t('general.recent')}}</v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item @click="$emit('sort')"> <v-list-item @click="$emit('sort')">
<v-list-item-title> A-Z </v-list-item-title> <v-list-item-title>{{$t('general.sort-alphabetically')}}</v-list-item-title>
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-menu> </v-menu>

View file

@ -54,31 +54,31 @@ export default {
}, },
{ {
icon: "mdi-calendar-week", icon: "mdi-calendar-week",
title: this.$i18n.t("meal-plan.dinner-this-week"), title: this.$t("meal-plan.dinner-this-week"),
nav: "/meal-plan/this-week", nav: "/meal-plan/this-week",
restricted: true, restricted: true,
}, },
{ {
icon: "mdi-calendar-today", icon: "mdi-calendar-today",
title: this.$i18n.t("meal-plan.dinner-today"), title: this.$t("meal-plan.dinner-today"),
nav: "/meal-plan/today", nav: "/meal-plan/today",
restricted: true, restricted: true,
}, },
{ {
icon: "mdi-calendar-multiselect", icon: "mdi-calendar-multiselect",
title: this.$i18n.t("meal-plan.planner"), title: this.$t("meal-plan.planner"),
nav: "/meal-plan/planner", nav: "/meal-plan/planner",
restricted: true, restricted: true,
}, },
{ {
icon: "mdi-account", icon: "mdi-logout",
title: "Logout", title: this.$t('user.logout'),
restricted: true, restricted: true,
nav: "/logout", nav: "/logout",
}, },
{ {
icon: "mdi-cog", icon: "mdi-cog",
title: this.$i18n.t("general.settings"), title: this.$t("general.settings"),
nav: "/admin", nav: "/admin",
restricted: true, restricted: true,
}, },

View file

@ -30,7 +30,7 @@
"download": "Hent", "download": "Hent",
"import": "Importere" "import": "Importere"
}, },
"login": { "user": {
"email": "E-mail", "email": "E-mail",
"password": "Adgangskode", "password": "Adgangskode",
"sign-in": "Log ind", "sign-in": "Log ind",

View file

@ -41,7 +41,7 @@
"all-recipes": "Alle Rezepte", "all-recipes": "Alle Rezepte",
"recent": "Neueste" "recent": "Neueste"
}, },
"login": { "user": {
"stay-logged-in": "Eingeloggt bleiben?", "stay-logged-in": "Eingeloggt bleiben?",
"email": "E-Mail", "email": "E-Mail",
"password": "Passwort", "password": "Passwort",

View file

@ -1,12 +1,4 @@
{ {
"dateTimeFormats": {
"short": {
"month": "short",
"day": "numeric",
"weekday": "long"
}
},
"404": { "404": {
"page-not-found": "404 Page Not Found", "page-not-found": "404 Page Not Found",
"take-me-home": "Take me Home" "take-me-home": "Take me Home"
@ -43,19 +35,71 @@
"templates": "Templates", "templates": "Templates",
"recipes": "Recipes", "recipes": "Recipes",
"themes": "Themes", "themes": "Themes",
"confirm": "Confirm" "confirm": "Confirm",
"sort": "Sort",
"recent": "Recent",
"sort-alphabetically": "A-Z",
"reset": "Reset",
"filter": "Filter",
"yes": "Yes",
"no": "No",
"token": "Token",
"field-required": "Field Required",
"apply": "Apply",
"current-parenthesis": "(Current)"
}, },
"page": { "page": {
"home-page": "Home Page", "home-page": "Home Page",
"all-recipes": "All Recipes", "all-recipes": "All Recipes",
"recent": "Recent" "recent": "Recent"
}, },
"login": { "user": {
"stay-logged-in": "Stay logged in?", "stay-logged-in": "Stay logged in?",
"email": "Email", "email": "Email",
"password": "Password", "password": "Password",
"sign-in": "Sign in", "sign-in": "Sign in",
"sign-up": "Sign up" "sign-up": "Sign up",
"logout": "Logout",
"full-name": "Full Name",
"user-group": "User Group",
"user-password": "User Password",
"admin": "Admin",
"user-id": "User ID",
"user-id-with-value": "User ID: {id}",
"group": "Group",
"new-user": "New User",
"edit-user": "Edit User",
"create-user": "Create User",
"confirm-user-deletion": "Confirm User Deletion",
"are-you-sure-you-want-to-delete-the-user": "Are you sure you want to delete the user <b>{activeName} ID: {activeId}<b/>?",
"confirm-group-deletion": "Confirm Group Deletion",
"total-users": "Total Users",
"total-mealplans": "Total MealPlans",
"webhooks-enabled": "Webhooks Enabled",
"webhook-time": "Webhook Time",
"create-group": "Create Group",
"sign-up-links": "Sign Up Links",
"create-link": "Create Link",
"link-name": "Link Name",
"group-id-with-value": "Group ID: {groupID}",
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
"group-name": "Group Name",
"confirm-link-deletion": "Confirm Link Deletion",
"are-you-sure-you-want-to-delete-the-link": "Are you sure you want to delete the link <b>{link}<b/>?",
"link-id": "Link ID",
"users": "Users",
"groups": "Groups",
"could-not-validate-credentials": "Could Not Validate Credentials",
"login": "Login",
"groups-can-only-be-set-by-administrators": "Groups can only be set by administrators",
"upload-photo": "Upload Photo",
"reset-password": "Reset Password",
"current-password": "Current Password",
"new-password": "New Password",
"confirm-password": "Confirm Password",
"password-must-match": "Password must match",
"e-mail-must-be-valid": "E-mail must be valid",
"use-8-characters-or-more-for-your-password": "Use 8 characters or more for your password"
}, },
"meal-plan": { "meal-plan": {
"shopping-list": "Shopping List", "shopping-list": "Shopping List",
@ -163,8 +207,13 @@
"homepage-categories": "Homepage Categories", "homepage-categories": "Homepage Categories",
"home-page": "Home Page", "home-page": "Home Page",
"all-categories": "All Categories", "all-categories": "All Categories",
"show-recent": "Show Recent" "show-recent": "Show Recent",
} "home-page-sections": "Home Page Sections"
},
"site-settings": "Site Settings",
"manage-users": "Manage Users",
"migrations": "Migrations",
"profile": "Profile"
}, },
"migration": { "migration": {
"recipe-migration": "Recipe Migration", "recipe-migration": "Recipe Migration",

View file

@ -1,12 +1,4 @@
{ {
"dateTimeFormats": {
"short": {
"month": "short",
"day": "numeric",
"weekday": "long"
}
},
"404": { "404": {
"page-not-found": "404 Page introuvable", "page-not-found": "404 Page introuvable",
"take-me-home": "Retour à l'accueil" "take-me-home": "Retour à l'accueil"
@ -28,7 +20,7 @@
"save": "Sauvegarder", "save": "Sauvegarder",
"image-file": "Image", "image-file": "Image",
"update": "Mettre à jour", "update": "Mettre à jour",
"edit": "Editer", "edit": "Modifier",
"delete": "Supprimer", "delete": "Supprimer",
"select": "Sélectionner", "select": "Sélectionner",
"random": "Aléatoire", "random": "Aléatoire",
@ -43,19 +35,71 @@
"templates": "Modèles", "templates": "Modèles",
"recipes": "Recettes", "recipes": "Recettes",
"themes": "Thèmes", "themes": "Thèmes",
"confirm": "Confirmer" "confirm": "Confirmer",
"recent": "Récent",
"sort": "Trier",
"sort-alphabetically": "A-Z",
"reset": "Réinitialiser",
"filter": "Filtrer",
"no": "Non",
"yes": "Oui",
"token": "Jeton",
"field-required": "Champ obligatoire",
"apply": "Appliquer",
"current-parenthesis": "(Actuel)"
}, },
"page": { "page": {
"home-page": "Accueil", "home-page": "Accueil",
"all-recipes": "Toutes mes recettes", "all-recipes": "Toutes mes recettes",
"recent": "Récent" "recent": "Récent"
}, },
"login": { "user": {
"stay-logged-in": "Rester connecté(e) ?", "stay-logged-in": "Rester connecté(e) ?",
"email": "Email", "email": "E-mail",
"password": "Mot de passe", "password": "Mot de passe",
"sign-in": "Se connecter", "sign-in": "Se connecter",
"sign-up": "S'inscrire" "sign-up": "S'inscrire",
"logout": "Déconnexion",
"admin": "Admin",
"edit-user": "Modifier l'utilisateur",
"full-name": "Nom",
"group": "Groupe",
"new-user": "Nouvel utilisateur",
"user-group": "Groupe utilisateur",
"user-id": "ID utilisateur",
"user-password": "Mot de passe de l'utilisateur",
"create-user": "Créer utilisateur",
"are-you-sure-you-want-to-delete-the-user": "Êtes-vous sûr de vouloir supprimer l'utilisateur <b>{activeName} ID : {activeId}<b/> ?",
"confirm-user-deletion": "Confirmer la suppression",
"confirm-group-deletion": "Confirmer la suppression du groupe",
"create-group": "Créer un groupe",
"create-link": "Créer un lien",
"group-id-with-value": "ID groupe : {groupID}",
"are-you-sure-you-want-to-delete-the-group": "Êtes-vous sûr de vouloir supprimer <b>{groupName}<b/> ?",
"link-name": "Nom du lien",
"sign-up-links": "Liens d'inscription",
"total-mealplans": "Nombre de repas planifiés",
"total-users": "Nombre d'utilisateurs",
"user-id-with-value": "ID utilisateur : {id}",
"webhook-time": "Heure du Webhook",
"webhooks-enabled": "Webhooks activés",
"are-you-sure-you-want-to-delete-the-link": "Êtes-vous sûr de vouloir supprimer le lien <b>{link}<b/> ?",
"confirm-link-deletion": "Confirmer la suppresion du lien",
"group-name": "Nom du groupe",
"link-id": "ID du lien",
"groups": "Groupes",
"users": "Utilisateurs",
"could-not-validate-credentials": "La vérification de vos identifiants a échoué",
"login": "Connexion",
"groups-can-only-be-set-by-administrators": "Les groupes sont assignés par les administrateurs",
"confirm-password": "Confirmer mot de passe",
"current-password": "Mot de passe actuel",
"e-mail-must-be-valid": "L'e-mail doit être valide",
"new-password": "Nouveau mot de passe",
"password-must-match": "Les mots de passe doivent correspondre",
"reset-password": "Réinitialiser le mot de passe",
"upload-photo": "Importer une photo",
"use-8-characters-or-more-for-your-password": "Utiliser au moins 8 caractères pour votre mot de passe"
}, },
"meal-plan": { "meal-plan": {
"shopping-list": "Liste d'achats", "shopping-list": "Liste d'achats",
@ -108,12 +152,12 @@
"local-api": "API local", "local-api": "API local",
"language": "Langue", "language": "Langue",
"add-a-new-theme": "Ajouter un nouveau thème", "add-a-new-theme": "Ajouter un nouveau thème",
"set-new-time": "Indiquer un nouveau temps", "set-new-time": "Indiquer une nouvelle heure",
"current": "Version :", "current": "Version :",
"latest": "Dernière", "latest": "Dernière",
"explore-the-docs": "Parcourir la documentation", "explore-the-docs": "Parcourir la documentation",
"contribute": "Contribuer", "contribute": "Contribuer",
"backup-and-exports": "Sauver et exporter", "backup-and-exports": "Sauvegardes",
"backup-info": "Les sauvegardes sont exportées en format JSON standard, ainsi que toutes les images stockées sur le système. Dans votre dossier de sauvegarde, vous trouverez un dossier .zip qui contient toutes les recettes en JSON et les images de la base de données. De plus, si vous avez sélectionné le format de fichier markdown, il sera sauvegardé dans le même dossier .zip. Pour importer une sauvegarde, celle-ci doit être enregistrée dans votre dossier de sauvegardes. Une sauvegarde automatique est effectuée quotidiennement à 03h00.", "backup-info": "Les sauvegardes sont exportées en format JSON standard, ainsi que toutes les images stockées sur le système. Dans votre dossier de sauvegarde, vous trouverez un dossier .zip qui contient toutes les recettes en JSON et les images de la base de données. De plus, si vous avez sélectionné le format de fichier markdown, il sera sauvegardé dans le même dossier .zip. Pour importer une sauvegarde, celle-ci doit être enregistrée dans votre dossier de sauvegardes. Une sauvegarde automatique est effectuée quotidiennement à 03h00.",
"available-backups": "Sauvegardes disponibles", "available-backups": "Sauvegardes disponibles",
"theme": { "theme": {
@ -163,8 +207,13 @@
"card-per-section": "Tuiles par section", "card-per-section": "Tuiles par section",
"home-page": "Page d'accueil", "home-page": "Page d'accueil",
"homepage-categories": "Catégories de la page d'accueil", "homepage-categories": "Catégories de la page d'accueil",
"show-recent": "Afficher les récentes" "show-recent": "Afficher les récentes",
} "home-page-sections": "Sections de la page d'accueil"
},
"manage-users": "Utilisateurs",
"migrations": "Migrations",
"profile": "Profil",
"site-settings": "Paramètres site"
}, },
"migration": { "migration": {
"recipe-migration": "Migrer les recettes", "recipe-migration": "Migrer les recettes",
@ -180,5 +229,6 @@
"title": "Chowdown", "title": "Chowdown",
"description": "Importer des recettes depuis Chowdown" "description": "Importer des recettes depuis Chowdown"
} }
} },
"auth": {}
} }

View file

@ -36,7 +36,7 @@
"themes": "Motywy", "themes": "Motywy",
"confirm": "Potwierdź" "confirm": "Potwierdź"
}, },
"login": { "user": {
"stay-logged-in": "Pozostań zalogowany", "stay-logged-in": "Pozostań zalogowany",
"email": "Email", "email": "Email",
"password": "Hasło", "password": "Hasło",

View file

@ -30,7 +30,7 @@
"download": "Ladda ner", "download": "Ladda ner",
"import": "Importera" "import": "Importera"
}, },
"login": { "user": {
"email": "E-mail", "email": "E-mail",
"password": "Lösenord", "password": "Lösenord",
"sign-in": "Logga in", "sign-in": "Logga in",

View file

@ -36,7 +36,7 @@
"themes": "布景主题", "themes": "布景主题",
"confirm": "确定" "confirm": "确定"
}, },
"login": { "user": {
"stay-logged-in": "保持登录状态?", "stay-logged-in": "保持登录状态?",
"email": "电子邮件", "email": "电子邮件",
"password": "密码", "password": "密码",

View file

@ -36,7 +36,7 @@
"themes": "佈景主題", "themes": "佈景主題",
"confirm": "確定" "confirm": "確定"
}, },
"login": { "user": {
"stay-logged-in": "保持登錄狀態?", "stay-logged-in": "保持登錄狀態?",
"email": "電子郵件", "email": "電子郵件",
"password": "密碼", "password": "密碼",

View file

@ -4,12 +4,12 @@ export const validators = {
emailRule: v => emailRule: v =>
!v || !v ||
/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(v) || /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(v) ||
"E-mail must be valid", this.$t('user.e-mail-must-be-valid'),
existsRule: value => !!value || "Field Required", existsRule: value => !!value || this.$t('general.field-required'),
minRule: v => minRule: v =>
v.length >= 8 || "Use 8 characters or more for your password", v.length >= 8 || this.$t('user.use-8-characters-or-more-for-your-password'),
}; };
}, },
}; };

View file

@ -11,17 +11,17 @@
<v-tabs-slider></v-tabs-slider> <v-tabs-slider></v-tabs-slider>
<v-tab> <v-tab>
Users {{$t('user.users')}}
<v-icon>mdi-account</v-icon> <v-icon>mdi-account</v-icon>
</v-tab> </v-tab>
<v-tab> <v-tab>
Sign-Up Links {{$t('user.sign-up-links')}}
<v-icon>mdi-account-plus-outline</v-icon> <v-icon>mdi-account-plus-outline</v-icon>
</v-tab> </v-tab>
<v-tab> <v-tab>
Groups {{$t('user.groups')}}
<v-icon>mdi-account-group</v-icon> <v-icon>mdi-account-group</v-icon>
</v-tab> </v-tab>
</v-tabs> </v-tabs>

View file

@ -13,9 +13,9 @@
> >
</v-progress-circular> </v-progress-circular>
</span> </span>
Profile {{$t('settings.profile')}}
<v-spacer></v-spacer> <v-spacer></v-spacer>
User ID: {{ user.id }} {{$t('user.user-id-with-value', {id: user.id }) }}
</v-card-title> </v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-text> <v-card-text>
@ -39,7 +39,7 @@
<v-col cols="12" md="9"> <v-col cols="12" md="9">
<v-form> <v-form>
<v-text-field <v-text-field
label="Full Name" :label="$t('user.full-name')"
required required
v-model="user.fullName" v-model="user.fullName"
:rules="[existsRule]" :rules="[existsRule]"
@ -47,7 +47,7 @@
> >
</v-text-field> </v-text-field>
<v-text-field <v-text-field
label="Email" :label="$t('user.email')"
:rules="[emailRule]" :rules="[emailRule]"
validate-on-blur validate-on-blur
required required
@ -55,11 +55,11 @@
> >
</v-text-field> </v-text-field>
<v-text-field <v-text-field
label="Group" :label="$t('user.group')"
readonly readonly
v-model="user.group" v-model="user.group"
persistent-hint persistent-hint
hint="Group groups can only be set by administrators" :hint="$t('user.groups-can-only-be-set-by-administrators')"
> >
</v-text-field> </v-text-field>
</v-form> </v-form>
@ -70,7 +70,7 @@
<v-card-actions> <v-card-actions>
<UploadBtn <UploadBtn
icon="mdi-image-area" icon="mdi-image-area"
text="Upload Photo" :text="$t('user.upload-photo')"
:url="userProfileImage" :url="userProfileImage"
file-name="profile_image" file-name="profile_image"
/> />
@ -86,7 +86,7 @@
<v-col cols="12" md="4" sm="12"> <v-col cols="12" md="4" sm="12">
<v-card height="100%"> <v-card height="100%">
<v-card-title class="headline"> <v-card-title class="headline">
Reset Password {{$t('user.reset-password')}}
<v-spacer></v-spacer> <v-spacer></v-spacer>
</v-card-title> </v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
@ -95,7 +95,7 @@
<v-text-field <v-text-field
v-model="password.current" v-model="password.current"
prepend-icon="mdi-lock" prepend-icon="mdi-lock"
label="Current Password" :label="$t('user.current-password')"
:rules="[existsRule]" :rules="[existsRule]"
validate-on-blur validate-on-blur
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"
@ -104,7 +104,7 @@
<v-text-field <v-text-field
v-model="password.newOne" v-model="password.newOne"
prepend-icon="mdi-lock" prepend-icon="mdi-lock"
label="New Password" :label="$t('user.new-password')"
:rules="[minRule]" :rules="[minRule]"
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"
@click:append="showPassword.newOne = !showPassword.newOne" @click:append="showPassword.newOne = !showPassword.newOne"
@ -112,9 +112,9 @@
<v-text-field <v-text-field
v-model="password.newTwo" v-model="password.newTwo"
prepend-icon="mdi-lock" prepend-icon="mdi-lock"
label="Confirm Password" :label="$t('user.confirm-password')"
:rules="[ :rules="[
password.newOne === password.newTwo || 'Password must match', password.newOne === password.newTwo || $t('user.password-must-match'),
]" ]"
validate-on-blur validate-on-blur
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"

View file

@ -3,6 +3,12 @@ import Vuetify from "vuetify/lib";
Vue.use(Vuetify); Vue.use(Vuetify);
import fr from 'vuetify/es5/locale/fr';
import pl from 'vuetify/es5/locale/pl';
import sv from 'vuetify/es5/locale/sv';
import de from 'vuetify/es5/locale/de';
const vuetify = new Vuetify({ const vuetify = new Vuetify({
theme: { theme: {
dark: false, dark: false,
@ -29,6 +35,12 @@ const vuetify = new Vuetify({
}, },
}, },
}, },
lang: {
locales: {
fr, pl, sv, de
},
current: 'en',
},
}); });
export default vuetify; export default vuetify;

View file

@ -46,8 +46,9 @@ const mutations = {
}; };
const actions = { const actions = {
initLang({ getters }) { initLang({ getters }, { currentVueComponent }) {
VueI18n.locale = getters.getActiveLang; VueI18n.locale = getters.getActiveLang;
currentVueComponent.$vuetify.lang.current = getters.getActiveLang;
}, },
}; };

View file

@ -110,7 +110,7 @@ async def update_user_image(
shutil.copyfileobj(profile_image.file, buffer) shutil.copyfileobj(profile_image.file, buffer)
if dest.is_file: if dest.is_file:
return SnackResponse.success("Backup uploaded") return SnackResponse.success("File uploaded")
else: else:
return SnackResponse.error("Failure uploading file") return SnackResponse.error("Failure uploading file")