From 1e5edc7434a07f44f539983e3a9c993ef63b3f38 Mon Sep 17 00:00:00 2001 From: sephrat <34862846+sephrat@users.noreply.github.com> Date: Thu, 29 Apr 2021 18:22:45 +0200 Subject: [PATCH] More localization (#358) * Translate missing items on About page * Localize import summary dialog * Make site menu translation reactive * Localize import options * Include semi colon in string * Move API texts to frontend + better status codes * Provide feedback to user when no meal is planned * Fix API tests after latest rework * Add warning for API changes in changelog * Refactor API texts handling * Refactor API texts handling #2 * Better API feedback * Rearrange strings hierarchy * Add messages upon recipe updated * Fix 'recipe effected' typo * Remove snackbar usage in backend * Translate toolbox * Provide feedback for tags CRUD * Fix messed up merge * Translate sign-up form * Better feedback for sign-up CRUD * Refactor log-in API texts handling * No error message when user is not authenticated * Remove unimportant console log --- docs/docs/changelog/v0.5.0.md | 7 +- frontend/src/api/api-utils.js | 92 ++++------ frontend/src/api/backup.js | 16 +- frontend/src/api/category.js | 82 ++++++--- frontend/src/api/groups.js | 56 ++++-- frontend/src/api/mealplan.js | 30 +++- frontend/src/api/migration.js | 10 +- frontend/src/api/recipe.js | 79 ++++++--- frontend/src/api/signUps.js | 21 ++- frontend/src/api/siteSettings.js | 48 +++-- frontend/src/api/themes.js | 27 ++- frontend/src/api/upload.js | 15 +- frontend/src/api/users.js | 72 +++++--- .../FormHelpers/CategoryTagSelector.vue | 2 +- .../components/ImportSummaryDialog/index.vue | 44 +++-- frontend/src/components/Login/LoginForm.vue | 12 +- frontend/src/components/Login/SignUpForm.vue | 32 ++-- .../components/MealPlan/MealPlanEditor.vue | 5 +- .../src/components/MealPlan/MealPlanNew.vue | 11 +- .../Recipe/Parts/Helpers/ImageUploadBtn.vue | 7 +- .../components/Recipe/RecipeEditor/index.vue | 2 +- .../components/Recipe/RecipeViewer/index.vue | 4 +- .../components/UI/Buttons/TheUploadBtn.vue | 6 +- frontend/src/components/UI/TheRecipeFab.vue | 18 +- frontend/src/components/UI/TheSiteMenu.vue | 12 +- frontend/src/locales/messages/en-US.json | 167 ++++++++++++++---- frontend/src/pages/Admin/About/index.vue | 4 +- .../Admin/Backup/AvailableBackupCard.vue | 18 +- .../src/pages/Admin/Backup/ImportOptions.vue | 2 +- .../src/pages/Admin/Backup/NewBackupCard.vue | 9 +- .../src/pages/Admin/ManageUsers/GroupCard.vue | 11 +- .../Admin/ManageUsers/GroupDashboard.vue | 11 +- .../Admin/ManageUsers/TheSignUpTable.vue | 15 +- .../pages/Admin/ManageUsers/TheUserTable.vue | 29 ++- .../src/pages/Admin/ManageUsers/index.vue | 4 +- .../src/pages/Admin/MealPlanner/index.vue | 7 +- .../pages/Admin/Migration/MigrationCard.vue | 7 +- frontend/src/pages/Admin/Profile/index.vue | 20 ++- .../pages/Admin/Settings/CreatePageDialog.vue | 14 +- .../Admin/Settings/CustomPageCreator.vue | 5 +- .../pages/Admin/Settings/HomePageSettings.vue | 7 +- frontend/src/pages/Admin/Theme/ThemeCard.vue | 10 +- frontend/src/pages/Admin/Theme/index.vue | 12 +- .../ToolBox/CategoryTagEditor/BulkAssign.vue | 6 +- .../CategoryTagEditor/RemoveUnused.vue | 2 +- .../Admin/ToolBox/CategoryTagEditor/index.vue | 11 +- frontend/src/pages/Admin/ToolBox/index.vue | 2 +- frontend/src/pages/MealPlan/Planner.vue | 5 +- frontend/src/pages/MealPlan/ThisWeek.vue | 4 + frontend/src/pages/Recipe/NewRecipe.vue | 2 +- frontend/src/pages/Recipe/ViewRecipe.vue | 19 +- frontend/src/pages/SearchPage/index.vue | 2 +- frontend/src/routes/meal.js | 16 +- mealie/routes/backup_routes.py | 59 +++---- mealie/routes/groups/crud.py | 27 +-- mealie/routes/mealplans/crud.py | 36 ++-- mealie/routes/migration_routes.py | 18 +- mealie/routes/recipe/category_routes.py | 20 ++- mealie/routes/recipe/recipe_assets.py | 17 +- mealie/routes/recipe/recipe_crud_routes.py | 10 +- mealie/routes/recipe/tag_routes.py | 8 +- mealie/routes/site_settings/custom_pages.py | 6 +- mealie/routes/site_settings/site_settings.py | 3 - mealie/routes/theme_routes.py | 19 +- mealie/routes/users/auth.py | 7 +- mealie/routes/users/crud.py | 47 +++-- mealie/routes/users/sign_up.py | 33 ++-- mealie/routes/utility_routes.py | 9 +- mealie/schema/snackbar.py | 26 --- tests/integration_tests/test_group_routes.py | 16 +- tests/integration_tests/test_meal_routes.py | 2 +- .../integration_tests/test_settings_routes.py | 2 +- 72 files changed, 890 insertions(+), 606 deletions(-) delete mode 100644 mealie/schema/snackbar.py 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 @@