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
This commit is contained in:
sephrat
2021-04-29 18:22:45 +02:00
committed by GitHub
parent 861020ffe0
commit 1e5edc7434
72 changed files with 890 additions and 606 deletions

View File

@@ -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) {

View File

@@ -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?

View File

@@ -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;
},
};

View File

@@ -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'); }
);
},
};

View File

@@ -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) {

View File

@@ -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));

View File

@@ -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) {

View File

@@ -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'); }
);
},
};

View File

@@ -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;
},
};

View File

@@ -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'); }
);
},
};

View File

@@ -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'); }
);
},
};

View File

@@ -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'); }
);
},
};