diff --git a/docs/docs/changelog/v0.5.0.md b/docs/docs/changelog/v0.5.0.md
index 252a8568ab3c..5db08118ba78 100644
--- a/docs/docs/changelog/v0.5.0.md
+++ b/docs/docs/changelog/v0.5.0.md
@@ -17,6 +17,8 @@
- Fixes #281 - Slow Handling of Large Sets of Recipes
## Features and Improvements
+- 'Dinner this week' shows a warning when no meal is planned yet
+- 'Dinner today' shows a warning when no meal is planned yet
### General
- New Toolbox Page!
@@ -37,4 +39,7 @@
### Behind the Scenes
- Unified Sidebar Components
-- Refactor UI components to fit Vue best practices (WIP)
\ No newline at end of file
+- Refactor UI components to fit Vue best practices (WIP)
+- The API returns more consistent status codes
+- The API returns error code instead of error text when appropriate
+ - ⚠️ May cause side-effects if you were directly consuming the API
\ No newline at end of file
diff --git a/frontend/src/api/api-utils.js b/frontend/src/api/api-utils.js
index 47868d0f7eb7..0e42f23337dc 100644
--- a/frontend/src/api/api-utils.js
+++ b/frontend/src/api/api-utils.js
@@ -1,75 +1,57 @@
const baseURL = "/api/";
import axios from "axios";
-import utils from "@/utils";
import { store } from "../store";
+import utils from "@/utils";
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${store.getters.getToken}`;
-function processResponse(response) {
- try {
- utils.notify.show(response.data.snackbar.text, response.data.snackbar.type);
- } catch (err) {
- return;
+function handleError(error, getText) {
+ if(getText) {
+ utils.notify.error(getText(error.response));
}
+ return false;
+}
+function handleResponse(response, getText) {
+ if(response && getText) {
+ const successText = getText(response);
+ utils.notify.success(successText);
+ }
+ return response;
+}
- return;
+function defaultErrorText(response) {
+ return response.statusText;
+}
+
+function defaultSuccessText(response) {
+ return response.statusText;
}
const apiReq = {
- post: async function(url, data) {
- let response = await axios.post(url, data).catch(function(error) {
- if (error.response) {
- processResponse(error.response);
- return error.response;
- }
- });
- processResponse(response);
- return response;
+ post: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
+ const response = await axios.post(url, data).catch(function(error) { handleError(error, getErrorText) });
+ return handleResponse(response, getSuccessText);
+ },
+
+ put: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
+ const response = await axios.put(url, data).catch(function(error) { handleError(error, getErrorText) });
+ return handleResponse(response, getSuccessText);
+ },
+
+ patch: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
+ const response = await axios.patch(url, data).catch(function(error) { handleError(error, getErrorText) });
+ return handleResponse(response, getSuccessText);
},
- put: async function(url, data) {
- let response = await axios.put(url, data).catch(function(error) {
- if (error.response) {
- processResponse(error.response);
- return response;
- } else return;
- });
- processResponse(response);
- return response;
- },
- patch: async function(url, data) {
- let response = await axios.patch(url, data).catch(function(error) {
- if (error.response) {
- processResponse(error.response);
- return response;
- } else return;
- });
- processResponse(response);
- return response;
+ get: function(url, data, getErrorText = defaultErrorText) {
+ return axios.get(url, data).catch(function(error) { handleError(error, getErrorText) });
},
- get: async function(url, data) {
- let response = await axios.get(url, data).catch(function(error) {
- if (error.response) {
- processResponse(error.response);
- return response;
- } else return;
- });
- processResponse(response);
- return response;
- },
-
- delete: async function(url, data) {
- let response = await axios.delete(url, data).catch(function(error) {
- if (error.response) {
- processResponse(error.response);
- return response;
- }
- });
- processResponse(response);
- return response;
+ delete: async function(url, data, getErrorText = defaultErrorText, getSuccessText = defaultSuccessText ) {
+ const response = await axios.delete(url, data).catch( function(error) { handleError(error, getErrorText) } );
+ return handleResponse(response, getSuccessText);
},
async download(url) {
diff --git a/frontend/src/api/backup.js b/frontend/src/api/backup.js
index b6afd6e98bb2..d95dd6d27bb4 100644
--- a/frontend/src/api/backup.js
+++ b/frontend/src/api/backup.js
@@ -1,6 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "@/store";
+import i18n from '@/i18n.js';
const backupBase = baseURL + "backups/";
@@ -40,7 +41,12 @@ export const backupAPI = {
* @param {string} fileName
*/
async delete(fileName) {
- await apiReq.delete(backupURLs.deleteBackup(fileName));
+ return apiReq.delete(
+ backupURLs.deleteBackup(fileName),
+ null,
+ function() { return i18n.t('settings.backup.unable-to-delete-backup'); },
+ function() { return i18n.t('settings.backup.backup-deleted'); }
+ );
},
/**
* Creates a backup on the serve given a set of options
@@ -48,8 +54,12 @@ export const backupAPI = {
* @returns
*/
async create(options) {
- let response = apiReq.post(backupURLs.createBackup, options);
- return response;
+ return apiReq.post(
+ backupURLs.createBackup,
+ options,
+ function() { return i18n.t('settings.backup.error-creating-backup-see-log-file'); },
+ function(response) { return i18n.t('settings.backup.backup-created-at-response-export_path', {path: response.data.export_path}); }
+ );
},
/**
* Downloads a file from the server. I don't actually think this is used?
diff --git a/frontend/src/api/category.js b/frontend/src/api/category.js
index 8c2219e58f4f..ed0e3752465c 100644
--- a/frontend/src/api/category.js
+++ b/frontend/src/api/category.js
@@ -1,6 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "@/store";
+import i18n from '@/i18n.js';
const prefix = baseURL + "categories";
@@ -22,29 +23,44 @@ export const categoryAPI = {
return response.data;
},
async create(name) {
- let response = await apiReq.post(categoryURLs.getAll, { name: name });
- store.dispatch("requestCategories");
- return response.data;
+ const response = await apiReq.post(
+ categoryURLs.getAll,
+ { name: name },
+ function() { return i18n.t('category.category-creation-failed'); },
+ function() { return i18n.t('category.category-created'); }
+ );
+ if(response) {
+ store.dispatch("requestCategories");
+ return response.data;
+ }
},
async getRecipesInCategory(category) {
let response = await apiReq.get(categoryURLs.getCategory(category));
return response.data;
},
async update(name, newName, overrideRequest = false) {
- let response = await apiReq.put(categoryURLs.updateCategory(name), {
- name: newName,
- });
- if (!overrideRequest) {
+ const response = await apiReq.put(
+ categoryURLs.updateCategory(name),
+ { name: newName },
+ function() { return i18n.t('category.category-update-failed'); },
+ function() { return i18n.t('category.category-updated'); }
+ );
+ if (response && !overrideRequest) {
store.dispatch("requestCategories");
+ return response.data;
}
- return response.data;
},
async delete(category, overrideRequest = false) {
- let response = await apiReq.delete(categoryURLs.deleteCategory(category));
- if (!overrideRequest) {
+ const response = await apiReq.delete(
+ categoryURLs.deleteCategory(category),
+ null,
+ function() { return i18n.t('category.category-deletion-failed'); },
+ function() { return i18n.t('category.category-deleted'); }
+ );
+ if (response && !overrideRequest) {
store.dispatch("requestCategories");
}
- return response.data;
+ return response;
},
};
@@ -68,28 +84,48 @@ export const tagAPI = {
return response.data;
},
async create(name) {
- let response = await apiReq.post(tagURLs.getAll, { name: name });
- store.dispatch("requestTags");
- return response.data;
+ const response = await apiReq.post(
+ tagURLs.getAll,
+ { name: name },
+ function() { return i18n.t('tag.tag-creation-failed'); },
+ function() { return i18n.t('tag.tag-created'); }
+ );
+ if(response) {
+ store.dispatch("requestTags");
+ return response.data;
+ }
},
async getRecipesInTag(tag) {
let response = await apiReq.get(tagURLs.getTag(tag));
return response.data;
},
async update(name, newName, overrideRequest = false) {
- let response = await apiReq.put(tagURLs.updateTag(name), { name: newName });
+ const response = await apiReq.put(
+ tagURLs.updateTag(name),
+ { name: newName },
+ function() { return i18n.t('tag.tag-update-failed'); },
+ function() { return i18n.t('tag.tag-updated'); }
+ );
- if (!overrideRequest) {
- store.dispatch("requestTags");
+ if(response) {
+ if (!overrideRequest) {
+ store.dispatch("requestTags");
+ }
+ return response.data;
}
-
- return response.data;
},
async delete(tag, overrideRequest = false) {
- let response = await apiReq.delete(tagURLs.deleteTag(tag));
- if (!overrideRequest) {
- store.dispatch("requestTags");
+ const response = await apiReq.delete(
+ tagURLs.deleteTag(tag),
+ null,
+ function() { return i18n.t('tag.tag-deletion-failed'); },
+ function() { return i18n.t('tag.tag-deleted'); }
+ );
+ if(response) {
+ if (!overrideRequest) {
+ store.dispatch("requestTags");
+ }
+ return response.data;
}
- return response.data;
},
};
diff --git a/frontend/src/api/groups.js b/frontend/src/api/groups.js
index 7f67a16de531..011c3d104fc6 100644
--- a/frontend/src/api/groups.js
+++ b/frontend/src/api/groups.js
@@ -1,5 +1,6 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
+import i18n from '@/i18n.js';
const groupPrefix = baseURL + "groups";
const groupsURLs = {
@@ -10,25 +11,58 @@ const groupsURLs = {
update: id => `${groupPrefix}/${id}`,
};
+function deleteErrorText(response) {
+ switch(response.data.detail) {
+ case 'GROUP_WITH_USERS':
+ return i18n.t('group.cannot-delete-group-with-users');
+
+ case 'GROUP_NOT_FOUND':
+ return i18n.t('group.group-not-found');
+
+ case 'DEFAULT_GROUP':
+ return i18n.t('group.cannot-delete-default-group');
+
+ default:
+ return i18n.t('group.group-deletion-failed');
+ }
+}
+
export const groupAPI = {
async allGroups() {
let response = await apiReq.get(groupsURLs.groups);
return response.data;
},
- async create(name) {
- let response = await apiReq.post(groupsURLs.create, { name: name });
- return response.data;
+ create(name) {
+ return apiReq.post(
+ groupsURLs.create,
+ { name: name },
+ function() { return i18n.t('group.user-group-creation-failed'); },
+ function() { return i18n.t('group.user-group-created'); }
+ );
},
- async delete(id) {
- let response = await apiReq.delete(groupsURLs.delete(id));
- return response.data;
+ delete(id) {
+ return apiReq.delete(
+ groupsURLs.delete(id),
+ null,
+ deleteErrorText,
+ function() { return i18n.t('group.group-deleted'); }
+ );
},
async current() {
- let response = await apiReq.get(groupsURLs.current);
- return response.data;
+ const response = await apiReq.get(
+ groupsURLs.current,
+ null,
+ null);
+ if(response) {
+ return response.data;
+ }
},
- async update(data) {
- let response = await apiReq.put(groupsURLs.update(data.id), data);
- return response.data;
+ update(data) {
+ return apiReq.put(
+ groupsURLs.update(data.id),
+ data,
+ function() { return i18n.t('group.error-updating-group'); },
+ function() { return i18n.t('settings.group-settings-updated'); }
+ );
},
};
diff --git a/frontend/src/api/mealplan.js b/frontend/src/api/mealplan.js
index 172fc9339726..ff51da61e511 100644
--- a/frontend/src/api/mealplan.js
+++ b/frontend/src/api/mealplan.js
@@ -1,5 +1,6 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
+import i18n from '@/i18n.js';
const prefix = baseURL + "meal-plans/";
@@ -15,9 +16,13 @@ const mealPlanURLs = {
};
export const mealplanAPI = {
- async create(postBody) {
- let response = await apiReq.post(mealPlanURLs.create, postBody);
- return response;
+ create(postBody) {
+ return apiReq.post(
+ mealPlanURLs.create,
+ postBody,
+ function() { return i18n.t('meal-plan.mealplan-creation-failed')},
+ function() { return i18n.t('meal-plan.mealplan-created'); }
+ );
},
async all() {
@@ -35,14 +40,21 @@ export const mealplanAPI = {
return response;
},
- async delete(id) {
- let response = await apiReq.delete(mealPlanURLs.delete(id));
- return response;
+ delete(id) {
+ return apiReq.delete(mealPlanURLs.delete(id),
+ null,
+ function() { return i18n.t('meal-plan.mealplan-deletion-failed'); },
+ function() { return i18n.t('meal-plan.mealplan-deleted'); }
+ );
},
- async update(id, body) {
- let response = await apiReq.put(mealPlanURLs.update(id), body);
- return response;
+ update(id, body) {
+ return apiReq.put(
+ mealPlanURLs.update(id),
+ body,
+ function() { return i18n.t('meal-plan.mealplan-update-failed'); },
+ function() { return i18n.t('meal-plan.mealplan-updated'); }
+ );
},
async shoppingList(id) {
diff --git a/frontend/src/api/migration.js b/frontend/src/api/migration.js
index e09c07cd96a5..5d967c56c110 100644
--- a/frontend/src/api/migration.js
+++ b/frontend/src/api/migration.js
@@ -1,6 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "../store";
+import i18n from '@/i18n.js';
const migrationBase = baseURL + "migrations";
@@ -17,8 +18,13 @@ export const migrationAPI = {
return response.data;
},
async delete(folder, file) {
- let response = await apiReq.delete(migrationURLs.delete(folder, file));
- return response.data;
+ const response = await apiReq.delete(
+ migrationURLs.delete(folder, file),
+ null,
+ function() { return i18n.t('general.file-folder-not-found'); },
+ function() { return i18n.t('migration.migration-data-removed'); }
+ );
+ return response;
},
async import(folder, file) {
let response = await apiReq.post(migrationURLs.import(folder, file));
diff --git a/frontend/src/api/recipe.js b/frontend/src/api/recipe.js
index 3dd8c1a6f8dc..b11a73ddb3b7 100644
--- a/frontend/src/api/recipe.js
+++ b/frontend/src/api/recipe.js
@@ -1,7 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "../store";
-import { router } from "../main";
+import i18n from '@/i18n.js';
const prefix = baseURL + "recipes/";
@@ -26,9 +26,12 @@ export const recipeAPI = {
* @returns {string} Recipe Slug
*/
async createByURL(recipeURL) {
- let response = await apiReq.post(recipeURLs.createByURL, {
- url: recipeURL,
- });
+ const response = await apiReq.post(
+ recipeURLs.createByURL,
+ { url: recipeURL },
+ function() { return i18n.t('recipe.recipe-creation-failed'); },
+ function() { return i18n.t('recipe.recipe-created'); }
+ );
store.dispatch("requestRecentRecipes");
return response;
@@ -43,7 +46,12 @@ export const recipeAPI = {
},
async create(recipeData) {
- let response = await apiReq.post(recipeURLs.create, recipeData);
+ const response = await apiReq.post(
+ recipeURLs.create,
+ recipeData,
+ function() { return i18n.t('recipe.recipe-creation-failed'); },
+ function() { return i18n.t('recipe.recipe-created'); }
+ );
store.dispatch("requestRecentRecipes");
return response.data;
},
@@ -53,14 +61,24 @@ export const recipeAPI = {
return response.data;
},
- async updateImage(recipeSlug, fileObject) {
- const fd = new FormData();
- fd.append("image", fileObject);
- fd.append("extension", fileObject.name.split(".").pop());
- let response = apiReq.put(recipeURLs.updateImage(recipeSlug), fd);
- return response;
- },
+ updateImage(recipeSlug, fileObject, overrideSuccessMsg = false) {
+ const formData = new FormData();
+ formData.append("image", fileObject);
+ formData.append("extension", fileObject.name.split(".").pop());
+ let successMessage = null;
+ if(!overrideSuccessMsg) {
+ successMessage = function() { return overrideSuccessMsg ? null : i18n.t('recipe.recipe-image-updated'); };
+ }
+
+ return apiReq.put(
+ recipeURLs.updateImage(recipeSlug),
+ formData,
+ function() { return i18n.t('general.image-upload-failed'); },
+ successMessage
+ );
+ },
+
async createAsset(recipeSlug, fileObject, name, icon) {
const fd = new FormData();
fd.append("file", fileObject);
@@ -70,17 +88,27 @@ export const recipeAPI = {
let response = apiReq.post(recipeURLs.createAsset(recipeSlug), fd);
return response;
},
-
- async updateImagebyURL(slug, url) {
- const response = apiReq.post(recipeURLs.updateImage(slug), { url: url });
- return response;
+
+ updateImagebyURL(slug, url) {
+ return apiReq.post(
+ recipeURLs.updateImage(slug),
+ { url: url },
+ function() { return i18n.t('general.image-upload-failed'); },
+ function() { return i18n.t('recipe.recipe-image-updated'); }
+ );
},
async update(data) {
- console.log(data)
- let response = await apiReq.put(recipeURLs.update(data.slug), data);
- store.dispatch("patchRecipe", response.data);
- return response.data.slug; // ! Temporary until I rewrite to refresh page without additional request
+ let response = await apiReq.put(
+ recipeURLs.update(data.slug),
+ data,
+ function() { return i18n.t('recipe.recipe-update-failed'); },
+ function() { return i18n.t('recipe.recipe-updated'); }
+ );
+ if(response) {
+ store.dispatch("patchRecipe", response.data);
+ return response.data.slug; // ! Temporary until I rewrite to refresh page without additional request
+ }
},
async patch(data) {
@@ -89,10 +117,13 @@ export const recipeAPI = {
return response.data;
},
- async delete(recipeSlug) {
- await apiReq.delete(recipeURLs.delete(recipeSlug));
- store.dispatch("requestRecentRecipes");
- router.push(`/`);
+ delete(recipeSlug) {
+ return apiReq.delete(
+ recipeURLs.delete(recipeSlug),
+ null,
+ function() { return i18n.t('recipe.unable-to-delete-recipe'); },
+ function() { return i18n.t('recipe.recipe-deleted'); }
+ );
},
async allSummary(start = 0, limit = 9999) {
diff --git a/frontend/src/api/signUps.js b/frontend/src/api/signUps.js
index 91b982caa113..8500a0cb97a4 100644
--- a/frontend/src/api/signUps.js
+++ b/frontend/src/api/signUps.js
@@ -1,5 +1,6 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
+import i18n from '@/i18n.js';
const signUpPrefix = baseURL + "users/sign-ups";
@@ -16,15 +17,25 @@ export const signupAPI = {
return response.data;
},
async createToken(data) {
- let response = await apiReq.post(signUpURLs.createToken, data);
+ let response = await apiReq.post(
+ signUpURLs.createToken,
+ data,
+ function() { return i18n.t('signup.sign-up-link-creation-failed'); },
+ function() { return i18n.t('signup.sign-up-link-created'); }
+ );
return response.data;
},
async deleteToken(token) {
- let response = await apiReq.delete(signUpURLs.deleteToken(token));
- return response.data;
+ return await apiReq.delete(signUpURLs.deleteToken(token),
+ null,
+ function() { return i18n.t('signup.sign-up-token-deletion-failed'); },
+ function() { return i18n.t('signup.sign-up-token-deleted'); }
+ );
},
async createUser(token, data) {
- let response = await apiReq.post(signUpURLs.createUser(token), data);
- return response.data;
+ return apiReq.post(signUpURLs.createUser(token), data,
+ function() { return i18n.t('user.you-are-not-allowed-to-create-a-user'); },
+ function() { return i18n.t('user.user-created'); }
+ );
},
};
diff --git a/frontend/src/api/siteSettings.js b/frontend/src/api/siteSettings.js
index 1b511e8b33f8..f5c234af7cfe 100644
--- a/frontend/src/api/siteSettings.js
+++ b/frontend/src/api/siteSettings.js
@@ -1,6 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "@/store";
+import i18n from '@/i18n.js';
const settingsBase = baseURL + "site-settings";
@@ -19,9 +20,16 @@ export const siteSettingsAPI = {
},
async update(body) {
- let response = await apiReq.put(settingsURLs.updateSiteSettings, body);
- store.dispatch("requestSiteSettings");
- return response.data;
+ const response = await apiReq.put(
+ settingsURLs.updateSiteSettings,
+ body,
+ function() { return i18n.t('settings.settings-update-failed'); },
+ function() { return i18n.t('settings.settings-updated'); }
+ );
+ if(response) {
+ store.dispatch("requestSiteSettings");
+ }
+ return response;
},
async getPages() {
@@ -34,23 +42,39 @@ export const siteSettingsAPI = {
return response.data;
},
- async createPage(body) {
- let response = await apiReq.post(settingsURLs.customPages, body);
- return response.data;
+ createPage(body) {
+ return apiReq.post(
+ settingsURLs.customPages,
+ body,
+ function() { return i18n.t('page.page-creation-failed'); },
+ function() { return i18n.t('page.new-page-created'); }
+ );
},
async deletePage(id) {
- let response = await apiReq.delete(settingsURLs.customPage(id));
- return response.data;
+ return await apiReq.delete(
+ settingsURLs.customPage(id),
+ null,
+ function() { return i18n.t('page.page-deletion-failed'); },
+ function() { return i18n.t('page.page-deleted'); });
},
- async updatePage(body) {
- let response = await apiReq.put(settingsURLs.customPage(body.id), body);
- return response.data;
+ updatePage(body) {
+ return apiReq.put(
+ settingsURLs.customPage(body.id),
+ body,
+ function() { return i18n.t('page.page-update-failed'); },
+ function() { return i18n.t('page.page-updated'); }
+ );
},
async updateAllPages(allPages) {
- let response = await apiReq.put(settingsURLs.customPages, allPages);
+ let response = await apiReq.put(
+ settingsURLs.customPages,
+ allPages,
+ function() { return i18n.t('page.pages-update-failed'); },
+ function() { return i18n.t('page.pages-updated'); }
+ );
return response;
},
};
diff --git a/frontend/src/api/themes.js b/frontend/src/api/themes.js
index d29568216890..321091754387 100644
--- a/frontend/src/api/themes.js
+++ b/frontend/src/api/themes.js
@@ -1,5 +1,6 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
+import i18n from '@/i18n.js';
const prefix = baseURL + "themes";
@@ -23,21 +24,31 @@ export const themeAPI = {
},
async create(postBody) {
- let response = await apiReq.post(settingsURLs.createTheme, postBody);
- return response.data;
+ return await apiReq.post(
+ settingsURLs.createTheme,
+ postBody,
+ function() { return i18n.t('settings.theme.error-creating-theme-see-log-file'); },
+ function() { return i18n.t('settings.theme.theme-saved'); });
},
- async update(themeName, colors) {
+ update(themeName, colors) {
const body = {
name: themeName,
colors: colors,
};
- let response = await apiReq.put(settingsURLs.updateTheme(themeName), body);
- return response.data;
+ return apiReq.put(
+ settingsURLs.updateTheme(themeName),
+ body,
+ function() { return i18n.t('settings.theme.error-updating-theme'); },
+ function() { return i18n.t('settings.theme.theme-updated'); });
},
- async delete(themeName) {
- let response = await apiReq.delete(settingsURLs.deleteTheme(themeName));
- return response.data;
+ delete(themeName) {
+ return apiReq.delete(
+ settingsURLs.deleteTheme(themeName),
+ null,
+ function() { return i18n.t('settings.theme.error-deleting-theme'); },
+ function() { return i18n.t('settings.theme.theme-deleted'); }
+ );
},
};
diff --git a/frontend/src/api/upload.js b/frontend/src/api/upload.js
index d5475080ecb2..14ac790f8a3f 100644
--- a/frontend/src/api/upload.js
+++ b/frontend/src/api/upload.js
@@ -1,15 +1,16 @@
import { apiReq } from "./api-utils";
+import i18n from '@/i18n.js';
export const utilsAPI = {
// import { api } from "@/api";
- async uploadFile(url, fileObject) {
+ uploadFile(url, fileObject) {
console.log("API Called");
- let response = await apiReq.post(url, fileObject, {
- headers: {
- "Content-Type": "multipart/form-data",
- },
- });
- return response.data;
+ return apiReq.post(
+ url,
+ fileObject,
+ function() { return i18n.t('general.failure-uploading-file'); },
+ function() { return i18n.t('general.file-uploaded'); }
+ );
},
};
diff --git a/frontend/src/api/users.js b/frontend/src/api/users.js
index b312e463f9dd..edf85bc3a9e8 100644
--- a/frontend/src/api/users.js
+++ b/frontend/src/api/users.js
@@ -1,6 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import axios from "axios";
+import i18n from '@/i18n.js';
const authPrefix = baseURL + "auth";
const userPrefix = baseURL + "users";
@@ -17,13 +18,23 @@ const usersURLs = {
resetPassword: id => `${userPrefix}/${id}/reset-password`,
};
+function deleteErrorText(response) {
+ switch(response.data.detail) {
+ case 'SUPER_USER':
+ return i18n.t('user.error-cannot-delete-super-user');
+
+ default:
+ return i18n.t('user.you-are-not-allowed-to-delete-this-user');
+ }
+}
export const userAPI = {
async login(formData) {
- let response = await apiReq.post(authURLs.token, formData, {
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- },
- });
+ let response = await apiReq.post(
+ authURLs.token,
+ formData,
+ function() { return i18n.t('user.incorrect-username-or-password'); },
+ function() { return i18n.t('user.user-successfully-logged-in'); }
+ );
return response;
},
async refresh() {
@@ -36,9 +47,13 @@ export const userAPI = {
let response = await apiReq.get(usersURLs.users);
return response.data;
},
- async create(user) {
- let response = await apiReq.post(usersURLs.users, user);
- return response.data;
+ create(user) {
+ return apiReq.post(
+ usersURLs.users,
+ user,
+ function() { return i18n.t('user.user-creation-failed'); },
+ function() { return i18n.t('user.user-created'); }
+ );
},
async self() {
let response = await apiReq.get(usersURLs.self);
@@ -48,20 +63,37 @@ export const userAPI = {
let response = await apiReq.get(usersURLs.userID(id));
return response.data;
},
- async update(user) {
- let response = await apiReq.put(usersURLs.userID(user.id), user);
- return response.data;
+ update(user) {
+ return apiReq.put(
+ usersURLs.userID(user.id),
+ user,
+ function() { return i18n.t('user.user-update-failed'); },
+ function() { return i18n.t('user.user-updated'); }
+ );
},
- async changePassword(id, password) {
- let response = await apiReq.put(usersURLs.password(id), password);
- return response.data;
+ changePassword(id, password) {
+ return apiReq.put(
+ usersURLs.password(id),
+ password,
+ function() { return i18n.t('user.existing-password-does-not-match'); },
+ function() { return i18n.t('user.password-updated'); }
+ );
},
- async delete(id) {
- let response = await apiReq.delete(usersURLs.userID(id));
- return response.data;
+
+ delete(id) {
+ return apiReq.delete(
+ usersURLs.userID(id),
+ null,
+ deleteErrorText,
+ function() { return i18n.t('user.user-deleted'); }
+ );
},
- async resetPassword(id) {
- let response = await apiReq.put(usersURLs.resetPassword(id));
- return response.data;
+ resetPassword(id) {
+ return apiReq.put(
+ usersURLs.resetPassword(id),
+ null,
+ function() { return i18n.t('user.password-reset-failed'); },
+ function() { return i18n.t('user.password-has-been-reset-to-the-default-password'); }
+ );
},
};
diff --git a/frontend/src/components/FormHelpers/CategoryTagSelector.vue b/frontend/src/components/FormHelpers/CategoryTagSelector.vue
index c31f0a017389..5266990865df 100644
--- a/frontend/src/components/FormHelpers/CategoryTagSelector.vue
+++ b/frontend/src/components/FormHelpers/CategoryTagSelector.vue
@@ -90,7 +90,7 @@ export default {
computed: {
inputLabel() {
if (!this.showLabel) return null;
- return this.tagSelector ? this.$t('recipe.tags') : this.$t('recipe.categories');
+ return this.tagSelector ? this.$t('tag.tags') : this.$t('recipe.categories');
},
activeItems() {
let ItemObjects = [];
diff --git a/frontend/src/components/ImportSummaryDialog/index.vue b/frontend/src/components/ImportSummaryDialog/index.vue
index 5088e1491950..ef069ddab225 100644
--- a/frontend/src/components/ImportSummaryDialog/index.vue
+++ b/frontend/src/components/ImportSummaryDialog/index.vue
@@ -7,7 +7,7 @@
mdi-import
- Import Summary
+ {{ $t("settings.backup.import-summary") }}
@@ -18,8 +18,8 @@
{{ values.title }}
- Success: {{ values.success }}
- Failed: {{ values.failure }}
+ {{ $t("general.success-count", { count: values.success }) }}
+ {{ $t("general.failed-count", { count: values.failure }) }}
@@ -28,7 +28,7 @@
{{ $t("general.recipes") }}
{{ $t("general.themes") }}
{{ $t("general.settings") }}
- Pages
+ {{ $t("settings.pages") }}
{{ $t("general.users") }}
{{ $t("general.groups") }}
@@ -59,24 +59,30 @@ export default {
userData: [],
groupData: [],
pageData: [],
- importHeaders: [
- {
- text: "Status",
- value: "status",
- },
- {
- text: "Name",
- align: "start",
- sortable: true,
- value: "name",
- },
-
- { text: "Exception", value: "data-table-expand", align: "center" },
- ],
allDataTables: [],
}),
computed: {
+
+ importHeaders() {
+ return [
+ {
+ text: this.$t('general.status'),
+ value: "status",
+ },
+ {
+ text: this.$t('general.name'),
+ align: "start",
+ sortable: true,
+ value: "name",
+ },
+ {
+ text: this.$t('general.exception'),
+ value: "data-table-expand",
+ align: "center"
+ },
+ ]
+ },
recipeNumbers() {
return this.calculateNumbers(this.$t("general.recipes"), this.recipeData);
},
@@ -96,7 +102,7 @@ export default {
return this.calculateNumbers(this.$t("general.groups"), this.groupData);
},
pageNumbers() {
- return this.calculateNumbers("Pages", this.pageData);
+ return this.calculateNumbers(this.$t("settings.pages"), this.pageData);
},
allNumbers() {
return [
diff --git a/frontend/src/components/Login/LoginForm.vue b/frontend/src/components/Login/LoginForm.vue
index 1cb48b729e26..52eca4c546a1 100644
--- a/frontend/src/components/Login/LoginForm.vue
+++ b/frontend/src/components/Login/LoginForm.vue
@@ -91,18 +91,12 @@ export default {
let formData = new FormData();
formData.append("username", this.user.email);
formData.append("password", this.user.password);
- let key;
- try {
- key = await api.users.login(formData);
- } catch {
+ const response = await api.users.login(formData);
+ if (!response) {
this.error = true;
- }
- if (key.status != 200) {
- this.error = true;
- this.loading = false;
} else {
this.clear();
- this.$store.commit("setToken", key.data.access_token);
+ this.$store.commit("setToken", response.data.access_token);
this.$emit("logged-in");
}
diff --git a/frontend/src/components/Login/SignUpForm.vue b/frontend/src/components/Login/SignUpForm.vue
index 19ce3a64e9bd..0995bd3b06bc 100644
--- a/frontend/src/components/Login/SignUpForm.vue
+++ b/frontend/src/components/Login/SignUpForm.vue
@@ -13,13 +13,13 @@
class="mr-2"
>
- Sign Up
+
+ {{$t('signup.sign-up')}}
+
- Welcome to Mealie! To become a user of this instance you are required to
- have a valid invitation link. If you haven't recieved an invitation you
- are unable to sign-up. To recieve a link, contact the sites administrator.
+ {{$t('signup.welcome-to-mealie')}}
@@ -71,11 +71,11 @@
block="block"
type="submit"
>
- Sign Up
+ {{$t('signup.sign-up')}}
- Error Signing Up
+ {{$t('signup.error-signing-up')}}
@@ -132,18 +132,16 @@ export default {
admin: false,
};
- let successUser = false;
if (this.$refs.signUpForm.validate()) {
- let response = await api.signUps.createUser(this.token, userData);
- successUser = response.snackbar.text.includes("Created");
+ if (await api.signUps.createUser(this.token, userData)) {
+ this.$emit("user-created");
+ this.$router.push("/");
+ }
}
-
- this.$emit("user-created");
-
+
this.loading = false;
- if (successUser) {
- this.$router.push("/");
- }
+
+
},
},
};
diff --git a/frontend/src/components/MealPlan/MealPlanEditor.vue b/frontend/src/components/MealPlan/MealPlanEditor.vue
index 9168dc5562c0..f49405bc07f5 100644
--- a/frontend/src/components/MealPlan/MealPlanEditor.vue
+++ b/frontend/src/components/MealPlan/MealPlanEditor.vue
@@ -36,8 +36,9 @@ export default {
return utils.getDateAsPythonDate(dateObject);
},
async update() {
- await api.mealPlans.update(this.mealPlan.uid, this.mealPlan);
- this.$emit("updated");
+ if (await api.mealPlans.update(this.mealPlan.uid, this.mealPlan)) {
+ this.$emit("updated");
+ }
},
},
};
diff --git a/frontend/src/components/MealPlan/MealPlanNew.vue b/frontend/src/components/MealPlan/MealPlanNew.vue
index 5bdd403f94b5..212462ff6c69 100644
--- a/frontend/src/components/MealPlan/MealPlanNew.vue
+++ b/frontend/src/components/MealPlan/MealPlanNew.vue
@@ -197,11 +197,12 @@ export default {
endDate: this.endDate,
meals: this.meals,
};
- await api.mealPlans.create(mealBody);
- this.$emit(CREATE_EVENT);
- this.meals = [];
- this.startDate = null;
- this.endDate = null;
+ if (await api.mealPlans.create(mealBody)) {
+ this.$emit(CREATE_EVENT);
+ this.meals = [];
+ this.startDate = null;
+ this.endDate = null;
+ }
},
getImage(image) {
diff --git a/frontend/src/components/Recipe/Parts/Helpers/ImageUploadBtn.vue b/frontend/src/components/Recipe/Parts/Helpers/ImageUploadBtn.vue
index 5f8f5483aa0e..8b4086a1474f 100644
--- a/frontend/src/components/Recipe/Parts/Helpers/ImageUploadBtn.vue
+++ b/frontend/src/components/Recipe/Parts/Helpers/ImageUploadBtn.vue
@@ -6,7 +6,7 @@
mdi-image
- {{ $t("recipe.image") }}
+ {{ $t("general.image") }}
@@ -71,8 +71,9 @@ export default {
},
async getImageFromURL() {
this.loading = true;
- const response = await api.recipes.updateImagebyURL(this.slug, this.url);
- if (response) this.$emit(REFRESH_EVENT);
+ if (await api.recipes.updateImagebyURL(this.slug, this.url)) {
+ this.$emit(REFRESH_EVENT);
+ }
this.loading = false;
},
},
diff --git a/frontend/src/components/Recipe/RecipeEditor/index.vue b/frontend/src/components/Recipe/RecipeEditor/index.vue
index b8b894d78af2..cd675c5db65d 100644
--- a/frontend/src/components/Recipe/RecipeEditor/index.vue
+++ b/frontend/src/components/Recipe/RecipeEditor/index.vue
@@ -74,7 +74,7 @@
:show-label="false"
/>
- {{ $t("recipe.tags") }}
+ {{ $t("tag.tags") }}
- {{ $t("recipe.tags") }}
+ {{ $t("tag.tags") }}
@@ -69,7 +69,7 @@
diff --git a/frontend/src/components/UI/Buttons/TheUploadBtn.vue b/frontend/src/components/UI/Buttons/TheUploadBtn.vue
index 12ca01db48fd..534734fc37f2 100644
--- a/frontend/src/components/UI/Buttons/TheUploadBtn.vue
+++ b/frontend/src/components/UI/Buttons/TheUploadBtn.vue
@@ -55,10 +55,10 @@ export default {
let formData = new FormData();
formData.append(this.fileName, this.file);
- await api.utils.uploadFile(this.url, formData);
-
+ if(await api.utils.uploadFile(this.url, formData)) {
+ this.$emit(UPLOAD_EVENT);
+ }
this.isSelecting = false;
- this.$emit(UPLOAD_EVENT);
}
},
onButtonClick() {
diff --git a/frontend/src/components/UI/TheRecipeFab.vue b/frontend/src/components/UI/TheRecipeFab.vue
index c9a7c603328d..30219b80dfe4 100644
--- a/frontend/src/components/UI/TheRecipeFab.vue
+++ b/frontend/src/components/UI/TheRecipeFab.vue
@@ -105,17 +105,15 @@ export default {
async createRecipe() {
if (this.$refs.urlForm.validate()) {
this.processing = true;
- let response = await api.recipes.createByURL(this.recipeURL);
- if (response.status !== 201) {
- this.error = true;
- this.processing = false;
- return;
- }
-
- this.addRecipe = false;
+ const response = await api.recipes.createByURL(this.recipeURL);
this.processing = false;
- this.recipeURL = "";
- this.$router.push(`/recipe/${response.data}`);
+ if (response) {
+ this.addRecipe = false;
+ this.recipeURL = "";
+ this.$router.push(`/recipe/${response.data}`);
+ } else {
+ this.error = true;
+ }
}
},
diff --git a/frontend/src/components/UI/TheSiteMenu.vue b/frontend/src/components/UI/TheSiteMenu.vue
index 10b429bf132d..d936b9b52b75 100644
--- a/frontend/src/components/UI/TheSiteMenu.vue
+++ b/frontend/src/components/UI/TheSiteMenu.vue
@@ -44,9 +44,9 @@ export default {
components: {
LoginDialog,
},
- data: function() {
- return {
- items: [
+ computed: {
+ items() {
+ return [
{
icon: "mdi-account",
title: "Login",
@@ -83,10 +83,8 @@ export default {
nav: "/admin",
restricted: true,
},
- ],
- };
- },
- computed: {
+ ]
+ },
filteredItems() {
if (this.loggedIn) {
return this.items.filter(x => x.restricted == true);
diff --git a/frontend/src/locales/messages/en-US.json b/frontend/src/locales/messages/en-US.json
index 60f27c723a94..2c3a7e748354 100644
--- a/frontend/src/locales/messages/en-US.json
+++ b/frontend/src/locales/messages/en-US.json
@@ -13,11 +13,22 @@
"demo": "Demo",
"demo-status": "Demo Status",
"development": "Development",
+ "download-log": "Download Log",
+ "download-recipe-json": "Download Recipe JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"version": "Version"
},
+ "category": {
+ "category-created": "Category created",
+ "category-creation-failed": "Category creation failed",
+ "category-deleted": "Category Deleted",
+ "category-deletion-failed": "Category deletion failed",
+ "category-filter": "Category Filter",
+ "category-update-failed": "Category update failed",
+ "category-updated": "Category updated"
+ },
"general": {
"apply": "Apply",
"cancel": "Cancel",
@@ -30,29 +41,40 @@
"download": "Download",
"edit": "Edit",
"enabled": "Enabled",
+ "exception": "Exception",
+ "failed-count": "Failed: {count}",
+ "failure-uploading-file": "Failure uploading file",
"field-required": "Field Required",
+ "file-folder-not-found": "File/folder not found",
+ "file-uploaded": "File uploaded",
"filter": "Filter",
"friday": "Friday",
"get": "Get",
"groups": "Groups",
+ "image": "Image",
+ "image-upload-failed": "Image upload failed",
"import": "Import",
+ "keyword": "Keyword",
"monday": "Monday",
"name": "Name",
"no": "No",
"ok": "OK",
- "options": "Options",
+ "options": "Options:",
"random": "Random",
"recent": "Recent",
"recipes": "Recipes",
+ "rename-object": "Rename {0}",
"reset": "Reset",
"saturday": "Saturday",
"save": "Save",
"settings": "Settings",
"sort": "Sort",
"sort-alphabetically": "A-Z",
+ "status": "Status",
"submit": "Submit",
+ "success-count": "Success: {count}",
"sunday": "Sunday",
- "templates": "Templates",
+ "templates": "Templates:",
"themes": "Themes",
"thursday": "Thursday",
"token": "Token",
@@ -64,6 +86,25 @@
"wednesday": "Wednesday",
"yes": "Yes"
},
+ "group": {
+ "are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete {groupName}?",
+ "cannot-delete-default-group": "Cannot delete default group",
+ "cannot-delete-group-with-users": "Cannot delete group with users",
+ "confirm-group-deletion": "Confirm Group Deletion",
+ "create-group": "Create Group",
+ "error-updating-group": "Error updating group",
+ "group": "Group",
+ "group-deleted": "Group deleted",
+ "group-deletion-failed": "Group deletion failed",
+ "group-id-with-value": "Group ID: {groupID}",
+ "group-name": "Group Name",
+ "group-not-found": "Group not found",
+ "groups": "Groups",
+ "groups-can-only-be-set-by-administrators": "Groups can only be set by administrators",
+ "user-group": "User Group",
+ "user-group-created": "User Group Created",
+ "user-group-creation-failed": "User Group Creation Failed"
+ },
"meal-plan": {
"create-a-new-meal-plan": "Create a New Meal Plan",
"dinner-this-week": "Dinner This Week",
@@ -73,6 +114,14 @@
"group": "Group (Beta)",
"meal-planner": "Meal Planner",
"meal-plans": "Meal Plans",
+ "mealplan-created": "Mealplan created",
+ "mealplan-creation-failed": "Mealplan creation failed",
+ "mealplan-deleted": "Mealplan Deleted",
+ "mealplan-deletion-failed": "Mealplan deletion failed",
+ "mealplan-update-failed": "Mealplan update failed",
+ "mealplan-updated": "Mealplan Updated",
+ "no-meal-plan-defined-yet": "No meal plan defined yet",
+ "no-meal-planned-for-today": "No meal planned for today",
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Only recipes with these categories will be used in Meal Plans",
"planner": "Planner",
"quick-week": "Quick Week",
@@ -84,6 +133,7 @@
"description": "Migrate data from Chowdown",
"title": "Chowdown"
},
+ "migration-data-removed": "Migration data removed",
"nextcloud": {
"description": "Migrate data from a Nextcloud Cookbook intance",
"title": "Nextcloud Cookbook"
@@ -102,23 +152,30 @@
"page": {
"all-recipes": "All Recipes",
"home-page": "Home Page",
+ "new-page-created": "New page created",
+ "page-creation-failed": "Page creation failed",
+ "page-deleted": "Page deleted",
+ "page-deletion-failed": "Page deletion failed",
+ "page-update-failed": "Page update failed",
+ "page-updated": "Page updated",
+ "pages-update-failed": "Pages update failed",
+ "pages-updated": "Pages updated",
"recent": "Recent"
},
"recipe": {
- "assets": "Assets",
"add-key": "Add Key",
"api-extras": "API Extras",
+ "assets": "Assets",
"calories": "Calories",
"calories-suffix": "calories",
+ "carbohydrate-content": "Carbohydrate",
"categories": "Categories",
"delete-confirmation": "Are you sure you want to delete this recipe?",
"delete-recipe": "Delete Recipe",
"description": "Description",
"fat-content": "Fat",
"fiber-content": "Fiber",
- "carbohydrate-content": "Carbohydrate",
"grams": "grams",
- "image": "Image",
"ingredient": "Ingredient",
"ingredients": "Ingredients",
"instructions": "Instructions",
@@ -135,27 +192,32 @@
"perform-time": "Cook Time",
"prep-time": "Prep Time",
"protein-content": "Protein",
+ "recipe-created": "Recipe created",
+ "recipe-creation-failed": "Recipe creation failed",
+ "recipe-deleted": "Recipe deleted",
"recipe-image": "Recipe Image",
+ "recipe-image-updated": "Recipe image updated",
"recipe-name": "Recipe Name",
+ "recipe-update-failed": "Recipe update failed",
+ "recipe-updated": "Recipe updated",
"servings": "Servings",
"sodium-content": "Sodium",
"step-index": "Step: {step}",
"sugar-content": "Sugar",
- "tags": "Tags",
"title": "Title",
"total-time": "Total Time",
+ "unable-to-delete-recipe": "Unable to Delete Recipe",
"view-recipe": "View Recipe"
},
"search": {
- "search-mealie": "Search Mealie (press /)",
- "search-placeholder": "Search...",
- "max-results": "Max Results",
- "category-filter": "Category Filter",
+ "and": "and",
"exclude": "Exclude",
"include": "Include",
+ "max-results": "Max Results",
"or": "Or",
- "and": "and",
"search": "Search",
+ "search-mealie": "Search Mealie (press /)",
+ "search-placeholder": "Search...",
"tag-filter": "Tag Filter"
},
"settings": {
@@ -163,10 +225,15 @@
"admin-settings": "Admin Settings",
"available-backups": "Available Backups",
"backup": {
+ "backup-created-at-response-export_path": "Backup Created at {path}",
+ "backup-deleted": "Backup deleted",
"backup-tag": "Backup Tag",
"create-heading": "Create a Backup",
+ "error-creating-backup-see-log-file": "Error Creating Backup. See Log File",
"full-backup": "Full Backup",
- "partial-backup": "Partial Backup"
+ "import-summary": "Import Summary",
+ "partial-backup": "Partial Backup",
+ "unable-to-delete-backup": "Unable to Delete Backup."
},
"backup-and-exports": "Backups",
"backup-info": "Backups are exported in standard JSON format along with all the images stored on the file system. In your backup folder you'll find a .zip file that contains all of the recipe JSON and images from the database. Additionally, if you selected a markdown file, those will also be stored in the .zip file. To import a backup, it must be located in your backups folder. Automated backups are done each day at 3:00 AM.",
@@ -175,6 +242,7 @@
"custom-pages": "Custom Pages",
"edit-page": "Edit Page",
"first-day-of-week": "First day of the week",
+ "group-settings-updated": "Group Settings Updated",
"homepage": {
"all-categories": "All Categories",
"card-per-section": "Card Per Section",
@@ -190,9 +258,12 @@
"migrations": "Migrations",
"new-page": "New Page",
"page-name": "Page Name",
+ "pages": "Pages",
"profile": "Profile",
"remove-existing-entries-matching-imported-entries": "Remove existing entries matching imported entries",
"set-new-time": "Set New Time",
+ "settings-update-failed": "Settings update failed",
+ "settings-updated": "Settings updated",
"site-settings": "Site Settings",
"theme": {
"accent": "Accent",
@@ -203,6 +274,9 @@
"default-to-system": "Default to system",
"delete-theme": "Delete Theme",
"error": "Error",
+ "error-creating-theme-see-log-file": "Error creating theme. See log file.",
+ "error-deleting-theme": "Error deleting theme",
+ "error-updating-theme": "Error updating theme",
"info": "Info",
"light": "Light",
"primary": "Primary",
@@ -210,51 +284,69 @@
"select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference": "Select a theme from the dropdown or create a new theme. Note that the default theme will be served to all users who have not set a theme preference.",
"success": "Success",
"theme": "Theme",
+ "theme-deleted": "Theme deleted",
"theme-name": "Theme Name",
"theme-name-is-required": "Theme Name is required.",
+ "theme-saved": "Theme Saved",
"theme-settings": "Theme Settings",
+ "theme-updated": "Theme updated",
"warning": "Warning"
},
+ "toolbox": {
+ "assign-all": "Assign All",
+ "bulk-assign": "Bulk Assign",
+ "new-name": "New Name",
+ "no-unused-items": "No Unused Items",
+ "recipes-affected": "No Recipes Affected|One Recipe Affected|{count} Recipes Affected",
+ "remove-unused": "Remove Unused",
+ "title-case-all": "Title Case All",
+ "toolbox": "Toolbox"
+ },
"webhooks": {
"meal-planner-webhooks": "Meal Planner Webhooks",
"test-webhooks": "Test Webhooks",
"the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "The URLs listed below will receive webhooks containing the recipe data for the meal plan on it's scheduled day. Currently Webhooks will execute at",
"webhook-url": "Webhook URL"
- },
- "toolbox": {
- "toolbox": "Toolbox",
- "new-name": "New Name",
- "recipes-effected": "Recipes Effected",
- "title-case-all": "Title Case All",
- "no-unused-items": "No Unused Items",
- "remove-unused": "Remove Unused",
- "assign-all": "Assign All",
- "bulk-assign": "Bulk Assign"
}
},
+ "signup": {
+ "display-name": "Display Name",
+ "error-signing-up": "Error Signing Up",
+ "sign-up": "Sign Up",
+ "sign-up-link-created": "Sign up link created",
+ "sign-up-link-creation-failed": "Sign up link creation failed",
+ "sign-up-links": "Sign Up Links",
+ "sign-up-token-deleted": "Sign Up Token Deleted",
+ "sign-up-token-deletion-failed": "Sign up token deletion failed",
+ "welcome-to-mealie": "Welcome to Mealie! To become a user of this instance you are required to have a valid invitation link. If you haven't recieved an invitation you are unable to sign-up. To recieve a link, contact the sites administrator."
+ },
+ "tag": {
+ "tag-created": "Tag created",
+ "tag-creation-failed": "Tag creation failed",
+ "tag-deleted": "Tag deleted",
+ "tag-deletion-failed": "Tag deletion failed",
+ "tag-update-failed": "Tag update failed",
+ "tag-updated": "Tag updated",
+ "tags": "Tags"
+ },
"user": {
"admin": "Admin",
- "are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete {groupName}?",
"are-you-sure-you-want-to-delete-the-link": "Are you sure you want to delete the link {link}?",
"are-you-sure-you-want-to-delete-the-user": "Are you sure you want to delete the user {activeName} ID: {activeId}?",
- "confirm-group-deletion": "Confirm Group Deletion",
"confirm-link-deletion": "Confirm Link Deletion",
"confirm-password": "Confirm Password",
"confirm-user-deletion": "Confirm User Deletion",
"could-not-validate-credentials": "Could Not Validate Credentials",
- "create-group": "Create Group",
"create-link": "Create Link",
"create-user": "Create User",
"current-password": "Current Password",
"e-mail-must-be-valid": "E-mail must be valid",
"edit-user": "Edit User",
"email": "Email",
+ "error-cannot-delete-super-user": "Error! Cannot Delete Super User",
+ "existing-password-does-not-match": "Existing password does not match",
"full-name": "Full Name",
- "group": "Group",
- "group-id-with-value": "Group ID: {groupID}",
- "group-name": "Group Name",
- "groups": "Groups",
- "groups-can-only-be-set-by-administrators": "Groups can only be set by administrators",
+ "incorrect-username-or-password": "Incorrect username or password",
"link-id": "Link ID",
"link-name": "Link Name",
"login": "Login",
@@ -262,20 +354,29 @@
"new-password": "New Password",
"new-user": "New User",
"password": "Password",
+ "password-has-been-reset-to-the-default-password": "Password has been reset to the default password",
"password-must-match": "Password must match",
+ "password-reset-failed": "Password reset failed",
+ "password-updated": "Password updated",
"reset-password": "Reset Password",
"sign-in": "Sign in",
- "sign-up-links": "Sign Up Links",
"total-mealplans": "Total MealPlans",
"total-users": "Total Users",
"upload-photo": "Upload Photo",
"use-8-characters-or-more-for-your-password": "Use 8 characters or more for your password",
- "user-group": "User Group",
+ "user-created": "User created",
+ "user-creation-failed": "User creation failed",
+ "user-deleted": "User deleted",
"user-id": "User ID",
"user-id-with-value": "User ID: {id}",
"user-password": "User Password",
+ "user-successfully-logged-in": "User Successfully Logged In",
+ "user-update-failed": "User update failed",
+ "user-updated": "User updated",
"users": "Users",
"webhook-time": "Webhook Time",
- "webhooks-enabled": "Webhooks Enabled"
+ "webhooks-enabled": "Webhooks Enabled",
+ "you-are-not-allowed-to-create-a-user": "You are not allowed to create a user",
+ "you-are-not-allowed-to-delete-this-user": "You are not allowed to delete this user"
}
}
\ No newline at end of file
diff --git a/frontend/src/pages/Admin/About/index.vue b/frontend/src/pages/Admin/About/index.vue
index e018bb8d211a..b2b188a7efcc 100644
--- a/frontend/src/pages/Admin/About/index.vue
+++ b/frontend/src/pages/Admin/About/index.vue
@@ -23,11 +23,11 @@
diff --git a/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue b/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue
index b5f1e7d347a3..e7208ee43a70 100644
--- a/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue
+++ b/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue
@@ -62,17 +62,21 @@ export default {
},
async importBackup(data) {
this.$emit("loading");
- let response = await api.backups.import(data.name, data);
+ const response = await api.backups.import(data.name, data);
+ if(response) {
+ let importData = response.data;
+ this.$emit("finished", importData);
+ } else {
+ this.$emit("finished");
+ }
- let importData = response.data;
-
- this.$emit("finished", importData);
},
- deleteBackup(data) {
+ async deleteBackup(data) {
this.$emit("loading");
- api.backups.delete(data.name);
- this.selectedBackup = null;
+ if (await api.backups.delete(data.name)) {
+ this.selectedBackup = null;
+ }
this.backupLoading = false;
this.$emit("finished");
diff --git a/frontend/src/pages/Admin/Backup/ImportOptions.vue b/frontend/src/pages/Admin/Backup/ImportOptions.vue
index 9228f20d9ea8..fcb72508ccd2 100644
--- a/frontend/src/pages/Admin/Backup/ImportOptions.vue
+++ b/frontend/src/pages/Admin/Backup/ImportOptions.vue
@@ -28,7 +28,7 @@ export default {
},
pages: {
value: true,
- text: "Pages",
+ text: this.$t("settings.pages"),
},
themes: {
value: true,
diff --git a/frontend/src/pages/Admin/Backup/NewBackupCard.vue b/frontend/src/pages/Admin/Backup/NewBackupCard.vue
index f522596a4fd4..1d4a09de1cd7 100644
--- a/frontend/src/pages/Admin/Backup/NewBackupCard.vue
+++ b/frontend/src/pages/Admin/Backup/NewBackupCard.vue
@@ -20,11 +20,11 @@
- {{ $t("general.options") }}:
+ {{ $t("general.options") }}
- {{ $t("general.templates") }}:
+ {{ $t("general.templates") }}
{{ group.name }}
{{
- $t("user.group-id-with-value", { groupID: group.id })
+ $t("group.group-id-with-value", { groupID: group.id })
}}
@@ -91,8 +91,9 @@ export default {
this.$refs.deleteGroupConfirm.open();
},
async deleteGroup() {
- await api.groups.delete(this.group.id);
- this.$emit(RENDER_EVENT);
+ if (await api.groups.delete(this.group.id)) {
+ this.$emit(RENDER_EVENT);
+ }
},
closeGroupDelete() {
console.log("Close Delete");
diff --git a/frontend/src/pages/Admin/ManageUsers/GroupDashboard.vue b/frontend/src/pages/Admin/ManageUsers/GroupDashboard.vue
index 845f2c904a27..f8158a6dda46 100644
--- a/frontend/src/pages/Admin/ManageUsers/GroupDashboard.vue
+++ b/frontend/src/pages/Admin/ManageUsers/GroupDashboard.vue
@@ -24,7 +24,7 @@
v-bind="attrs"
v-on="on"
>
- {{ $t("user.create-group") }}
+ {{ $t("group.create-group") }}
@@ -34,7 +34,7 @@
- {{ $t("user.create-group") }}
+ {{ $t("group.create-group") }}
@@ -43,7 +43,7 @@
@@ -104,12 +104,11 @@ export default {
methods: {
async createGroup() {
this.groupLoading = true;
- let response = await api.groups.create(this.newGroupName);
- if (response.created) {
- this.groupLoading = false;
+ if (await api.groups.create(this.newGroupName)) {
this.groupDialog = false;
this.$store.dispatch("requestAllGroups");
}
+ this.groupLoading = false;
},
},
};
diff --git a/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue b/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue
index 7a507fd9fa15..e77d812a65b5 100644
--- a/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue
+++ b/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue
@@ -1,7 +1,7 @@
@@ -18,7 +18,7 @@
mdi-link-variant
- {{ $t("user.sign-up-links") }}
+ {{ $t("signup.sign-up-links") }}
@@ -181,9 +181,10 @@ export default {
this.links = await api.signUps.getAll();
},
- async deleteUser() {
- await api.signUps.deleteToken(this.activeId);
- this.initialize();
+ async deleteToken() {
+ if (await api.signUps.deleteToken(this.activeId)) {
+ this.initialize();
+ }
},
editItem(item) {
@@ -197,7 +198,7 @@ export default {
this.activeName = item.name;
this.editedIndex = this.links.indexOf(item);
this.editedItem = Object.assign({}, item);
- this.$refs.deleteUserDialog.open();
+ this.$refs.deleteTokenDialog.open();
},
deleteItemConfirm() {
diff --git a/frontend/src/pages/Admin/ManageUsers/TheUserTable.vue b/frontend/src/pages/Admin/ManageUsers/TheUserTable.vue
index c6f45162fe55..f177082e7fbd 100644
--- a/frontend/src/pages/Admin/ManageUsers/TheUserTable.vue
+++ b/frontend/src/pages/Admin/ManageUsers/TheUserTable.vue
@@ -72,7 +72,7 @@
dense
v-model="editedItem.group"
:items="existingGroups"
- :label="$t('user.user-group')"
+ :label="$t('group.user-group')"
>
@@ -94,7 +94,7 @@
- Reset Password
+ {{$t('user.reset-password')}}
@@ -165,7 +165,7 @@ export default {
},
{ 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("group.group"), value: "group" },
{ text: this.$t("user.admin"), value: "admin" },
{ text: "", value: "actions", sortable: false, align: "center" },
],
@@ -223,8 +223,9 @@ export default {
},
async deleteUser() {
- await api.users.delete(this.activeId);
- this.initialize();
+ if (await api.users.delete(this.activeId)) {
+ this.initialize();
+ }
},
editItem(item) {
@@ -264,17 +265,27 @@ export default {
async save() {
if (this.editedIndex > -1) {
- await api.users.update(this.editedItem);
- this.close();
+ this.updateUser();
} else if (this.$refs.newUser.validate()) {
- await api.users.create(this.editedItem);
- this.close();
+ this.createUser();
}
await this.initialize();
},
resetPassword() {
api.users.resetPassword(this.editedItem.id);
},
+
+ async createUser() {
+ if(await api.users.create(this.editedItem)) {
+ this.close();
+ }
+ },
+
+ async updateUser() {
+ if(await api.users.update(this.editedItem)) {
+ this.close();
+ }
+ }
},
};
diff --git a/frontend/src/pages/Admin/ManageUsers/index.vue b/frontend/src/pages/Admin/ManageUsers/index.vue
index b795ebba03cc..5c465e59415e 100644
--- a/frontend/src/pages/Admin/ManageUsers/index.vue
+++ b/frontend/src/pages/Admin/ManageUsers/index.vue
@@ -16,12 +16,12 @@
- {{ $t("user.sign-up-links") }}
+ {{ $t("signup.sign-up-links") }}
mdi-account-plus-outline
- {{ $t("user.groups") }}
+ {{ $t("group.groups") }}
mdi-account-group
diff --git a/frontend/src/pages/Admin/MealPlanner/index.vue b/frontend/src/pages/Admin/MealPlanner/index.vue
index 3e03abdffd15..1445ba899e5e 100644
--- a/frontend/src/pages/Admin/MealPlanner/index.vue
+++ b/frontend/src/pages/Admin/MealPlanner/index.vue
@@ -135,9 +135,10 @@ export default {
this.groupSettings.webhookUrls.splice(index, 1);
},
async saveGroupSettings() {
- await api.groups.update(this.groupSettings);
- await this.$store.dispatch("requestCurrentGroup");
- this.getSiteSettings();
+ if (await api.groups.update(this.groupSettings)) {
+ await this.$store.dispatch("requestCurrentGroup");
+ this.getSiteSettings();
+ }
},
testWebhooks() {
api.settings.testWebhooks();
diff --git a/frontend/src/pages/Admin/Migration/MigrationCard.vue b/frontend/src/pages/Admin/Migration/MigrationCard.vue
index 055d8de76e05..70ee1e67c815 100644
--- a/frontend/src/pages/Admin/Migration/MigrationCard.vue
+++ b/frontend/src/pages/Admin/Migration/MigrationCard.vue
@@ -86,9 +86,10 @@ export default {
};
},
methods: {
- deleteMigration(file_name) {
- api.migrations.delete(this.folder, file_name);
- this.$emit("refresh");
+ async deleteMigration(file_name) {
+ if (await api.migrations.delete(this.folder, file_name)) {
+ this.$emit("refresh");
+ }
},
async importMigration(file_name) {
this.loading = true;
diff --git a/frontend/src/pages/Admin/Profile/index.vue b/frontend/src/pages/Admin/Profile/index.vue
index 0348c53e9244..d25b1d003337 100644
--- a/frontend/src/pages/Admin/Profile/index.vue
+++ b/frontend/src/pages/Admin/Profile/index.vue
@@ -55,11 +55,11 @@
>
@@ -201,11 +201,13 @@ export default {
},
async updateUser() {
this.loading = true;
- let newKey = await api.users.update(this.user);
- this.$store.commit("setToken", newKey.access_token);
- this.refreshProfile();
- this.loading = false;
- this.$store.dispatch("requestUserData");
+ const response = await api.users.update(this.user);
+ if(response) {
+ this.$store.commit("setToken", response.data.access_token);
+ this.refreshProfile();
+ this.loading = false;
+ this.$store.dispatch("requestUserData");
+ }
},
async changePassword() {
this.paswordLoading = true;
@@ -215,7 +217,9 @@ export default {
};
if (this.$refs.passChange.validate()) {
- await api.users.changePassword(this.user.id, data);
+ if (await api.users.changePassword(this.user.id, data)) {
+ this.$emit("refresh");
+ }
}
this.paswordLoading = false;
},
diff --git a/frontend/src/pages/Admin/Settings/CreatePageDialog.vue b/frontend/src/pages/Admin/Settings/CreatePageDialog.vue
index 2754e58d03f8..dff3f3fe3224 100644
--- a/frontend/src/pages/Admin/Settings/CreatePageDialog.vue
+++ b/frontend/src/pages/Admin/Settings/CreatePageDialog.vue
@@ -82,14 +82,18 @@ export default {
this.$refs.categoryFormSelector.setInit(this.page.categories);
},
async submitForm() {
+ let response;
if (this.create) {
- await api.siteSettings.createPage(this.page);
+ response = await api.siteSettings.createPage(this.page);
} else {
- await api.siteSettings.updatePage(this.page);
+ response = await api.siteSettings.updatePage(this.page);
+ }
+
+ if (response) {
+ this.pageDialog = false;
+ this.page.categories = [];
+ this.$emit(NEW_PAGE_EVENT);
}
- this.pageDialog = false;
- this.page.categories = [];
- this.$emit(NEW_PAGE_EVENT);
},
},
};
diff --git a/frontend/src/pages/Admin/Settings/CustomPageCreator.vue b/frontend/src/pages/Admin/Settings/CustomPageCreator.vue
index f5644d0d12c4..3244a0197a91 100644
--- a/frontend/src/pages/Admin/Settings/CustomPageCreator.vue
+++ b/frontend/src/pages/Admin/Settings/CustomPageCreator.vue
@@ -109,9 +109,10 @@ export default {
element.position = index;
});
- await api.siteSettings.updateAllPages(this.customPages);
+ if (await api.siteSettings.updateAllPages(this.customPages)) {
+ this.getPages();
+ }
- this.getPages();
},
editPage(index) {
this.editPageData.data = this.customPages[index];
diff --git a/frontend/src/pages/Admin/Settings/HomePageSettings.vue b/frontend/src/pages/Admin/Settings/HomePageSettings.vue
index 583f84d823a1..950ee01f52a5 100644
--- a/frontend/src/pages/Admin/Settings/HomePageSettings.vue
+++ b/frontend/src/pages/Admin/Settings/HomePageSettings.vue
@@ -214,7 +214,7 @@ export default {
this.settings.language = val;
},
deleteCategoryfromDatabase(category) {
- api.categories.delete(category);
+ api.categories.delete(category);
},
async getOptions() {
this.settings = await api.siteSettings.get();
@@ -223,8 +223,9 @@ export default {
this.settings.categories.splice(index, 1);
},
async saveSettings() {
- await api.siteSettings.update(this.settings);
- this.getOptions();
+ if (await api.siteSettings.update(this.settings)) {
+ this.getOptions();
+ }
},
},
};
diff --git a/frontend/src/pages/Admin/Theme/ThemeCard.vue b/frontend/src/pages/Admin/Theme/ThemeCard.vue
index 9d81fd9b19b4..084789ed6546 100644
--- a/frontend/src/pages/Admin/Theme/ThemeCard.vue
+++ b/frontend/src/pages/Admin/Theme/ThemeCard.vue
@@ -70,11 +70,11 @@ export default {
},
async deleteSelectedTheme() {
//Delete Theme from DB
- await api.themes.delete(this.theme.name);
-
- //Get the new list of available from DB
- this.availableThemes = await api.themes.requestAll();
- this.$emit(DELETE_EVENT);
+ if (await api.themes.delete(this.theme.name)) {
+ //Get the new list of available from DB
+ this.availableThemes = await api.themes.requestAll();
+ this.$emit(DELETE_EVENT);
+ }
},
async saveThemes() {
this.$store.commit("setTheme", this.theme);
diff --git a/frontend/src/pages/Admin/Theme/index.vue b/frontend/src/pages/Admin/Theme/index.vue
index bcdce92a8a19..e75c3055a476 100644
--- a/frontend/src/pages/Admin/Theme/index.vue
+++ b/frontend/src/pages/Admin/Theme/index.vue
@@ -171,9 +171,11 @@ export default {
* Create the new Theme and select it.
*/
async appendTheme(NewThemeDialog) {
- await api.themes.create(NewThemeDialog);
- this.availableThemes.push(NewThemeDialog);
- this.$store.commit("setTheme", NewThemeDialog);
+ const response = await api.themes.create(NewThemeDialog);
+ if (response) {
+ this.availableThemes.push(NewThemeDialog);
+ this.$store.commit("setTheme", NewThemeDialog);
+ }
},
setStoresDarkMode() {
this.$store.commit("setDarkMode", this.selectedDarkMode);
@@ -181,8 +183,8 @@ export default {
/**
* This will save the current colors and make the selected theme live.
*/
- async saveThemes() {
- await api.themes.update(
+ saveThemes() {
+ api.themes.update(
this.selectedTheme.name,
this.selectedTheme.colors
);
diff --git a/frontend/src/pages/Admin/ToolBox/CategoryTagEditor/BulkAssign.vue b/frontend/src/pages/Admin/ToolBox/CategoryTagEditor/BulkAssign.vue
index f04b11334116..2dcc7c750f90 100644
--- a/frontend/src/pages/Admin/ToolBox/CategoryTagEditor/BulkAssign.vue
+++ b/frontend/src/pages/Admin/ToolBox/CategoryTagEditor/BulkAssign.vue
@@ -4,7 +4,7 @@
ref="assignDialog"
title-icon="mdi-tag"
color="primary"
- title="Bulk Assign"
+ :title="$t('settings.toolbox.bulk-assign')"
:loading="loading"
modal-width="700"
:top="true"
@@ -13,7 +13,7 @@
- {{ renameTarget.recipes.length || 0 }}
- {{ $t("settings.toolbox.recipes-effected") }}
+ {{ $tc("settings.toolbox.recipes-affected", renameTarget.recipes.length || 0) }}
{{ item.name }}
- Edit
+ {{$t('general.edit')}}
- Delete
+
+ {{$t('general.delete')}}
@@ -177,7 +176,7 @@ export default {
}
this.renameTarget = {
- title: `Rename ${item.name}`,
+ title:this.$t('general.rename-object', [item.name]),
name: item.name,
slug: item.slug,
newName: "",
diff --git a/frontend/src/pages/Admin/ToolBox/index.vue b/frontend/src/pages/Admin/ToolBox/index.vue
index a65bc552f757..3e422e2374b9 100644
--- a/frontend/src/pages/Admin/ToolBox/index.vue
+++ b/frontend/src/pages/Admin/ToolBox/index.vue
@@ -16,7 +16,7 @@
- {{ $t("recipe.tags") }}
+ {{ $t("tag.tags") }}
mdi-tag-multiple-outline
diff --git a/frontend/src/pages/MealPlan/Planner.vue b/frontend/src/pages/MealPlan/Planner.vue
index 78feb98051de..8aa00182c9d8 100644
--- a/frontend/src/pages/MealPlan/Planner.vue
+++ b/frontend/src/pages/MealPlan/Planner.vue
@@ -128,8 +128,9 @@ export default {
this.requestMeals();
},
async deletePlan(id) {
- await api.mealPlans.delete(id);
- this.requestMeals();
+ if (await api.mealPlans.delete(id)) {
+ this.requestMeals();
+ }
},
openShoppingList(id) {
this.$refs.shoppingList.openDialog(id);
diff --git a/frontend/src/pages/MealPlan/ThisWeek.vue b/frontend/src/pages/MealPlan/ThisWeek.vue
index 9043d549c817..ab7e9b5438b6 100644
--- a/frontend/src/pages/MealPlan/ThisWeek.vue
+++ b/frontend/src/pages/MealPlan/ThisWeek.vue
@@ -52,6 +52,7 @@