feat(lang): more localization(#2219)

* feat(lang): localize some views

* fix: typo

* fix: Localization broke bug report generation

* feat(lang): localize recipe page instructions
This commit is contained in:
sephrat 2023-03-21 20:45:27 +01:00 committed by GitHub
parent 6b63c751b1
commit 9fd1ba6e46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 362 additions and 226 deletions

View File

@ -172,7 +172,7 @@ import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue";
import RecipeDialogShare from "./RecipeDialogShare.vue";
import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
import { planTypeOptions } from "~/composables/use-group-mealplan";
import { usePlanTypeOptions } from "~/composables/use-group-mealplan";
import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe";
import { parseIngredientText } from "~/composables/recipes";
import { ShoppingListSummary } from "~/lib/api/types/group";
@ -548,6 +548,8 @@ export default defineComponent({
state.loading = false;
}
const planTypeOptions = usePlanTypeOptions();
return {
...toRefs(state),
recipeRef,

View File

@ -147,7 +147,7 @@
event: 'merge-above',
},
{
text: 'Upload image',
text: $tc('recipe.upload-image'),
event: 'upload-image'
},
{

View File

@ -2,7 +2,7 @@
<v-toolbar flat>
<BaseButton color="null" rounded secondary @click="$router.go(-1)">
<template #icon> {{ $globals.icons.arrowLeftBold }}</template>
Back
{{ $t('general.back') }}
</BaseButton>
<slot></slot>
</v-toolbar>
@ -22,4 +22,4 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
</style>
</style>

View File

@ -1,4 +1,4 @@
import { useAsync, ref, Ref, watch } from "@nuxtjs/composition-api";
import { useAsync, ref, Ref, watch, useContext } from "@nuxtjs/composition-api";
import { format } from "date-fns";
import { useAsyncKey } from "./use-utils";
import { useUserApi } from "~/composables/api";
@ -8,14 +8,21 @@ type PlanOption = {
text: string;
value: PlanEntryType;
};
export function usePlanTypeOptions() {
const { i18n } = useContext();
export const planTypeOptions: PlanOption[] = [
{ text: "Breakfast", value: "breakfast" },
{ text: "Lunch", value: "lunch" },
{ text: "Dinner", value: "dinner" },
{ text: "Side", value: "side" },
];
return [
{ text: i18n.tc("meal-plan.breakfast"), value: "breakfast" },
{ text: i18n.tc("meal-plan.lunch"), value: "lunch" },
{ text: i18n.tc("meal-plan.dinner"), value: "dinner" },
{ text: i18n.tc("meal-plan.side"), value: "side" },
] as PlanOption[];
}
export function getEntryTypeText(value: PlanEntryType) {
const { i18n } = useContext();
return i18n.tc("meal-plan." + value);
}
export interface DateRange {
start: Date;
end: Date;

View File

@ -99,11 +99,16 @@ function pad(num: number, size: number) {
return numStr;
}
export function timeUTCToLocal(time: string): string {
export function timeUTC(time: string): Date {
const [hours, minutes] = time.split(":");
const dt = new Date();
dt.setUTCMinutes(Number(minutes));
dt.setUTCHours(Number(hours));
return dt;
}
export function timeUTCToLocal(time: string): string {
const dt = timeUTC(time);
return `${pad(dt.getHours(), 2)}:${pad(dt.getMinutes(), 2)}`;
}

View File

@ -1,69 +1,72 @@
import { useContext } from "@nuxtjs/composition-api";
import { fieldTypes } from "../forms";
import { AutoFormItems } from "~/types/auto-forms";
export const useUserForm = () => {
const { i18n } = useContext();
const userForm: AutoFormItems = [
{
section: "User Details",
label: "User Name",
section: i18n.tc("user.user-details"),
label: i18n.tc("user.user-name"),
varName: "username",
type: fieldTypes.TEXT,
rules: ["required"],
},
{
label: "Full Name",
label: i18n.tc("user.full-name"),
varName: "fullName",
type: fieldTypes.TEXT,
rules: ["required"],
},
{
label: "Email",
label: i18n.tc("user.email"),
varName: "email",
type: fieldTypes.TEXT,
rules: ["required"],
},
{
label: "Password",
label: i18n.tc("user.password"),
varName: "password",
disableUpdate: true,
type: fieldTypes.PASSWORD,
rules: ["required", "minLength:8"],
},
{
label: "Authentication Method",
label: i18n.tc("user.authentication-method"),
varName: "authMethod",
type: fieldTypes.SELECT,
hint: "This specifies how a user will authenticate with Mealie. If you're not sure, choose 'Mealie'",
hint: i18n.tc("user.authentication-method-hint"),
disableCreate: true,
options: [{ text: "Mealie" }, { text: "LDAP" }],
},
{
section: "Permissions",
label: "Administrator",
section: i18n.tc("user.permissions"),
label: i18n.tc("user.administrator"),
varName: "admin",
type: fieldTypes.BOOLEAN,
rules: ["required"],
},
{
label: "User can invite other to group",
label: i18n.tc("user.user-can-invite-other-to-group"),
varName: "canInvite",
type: fieldTypes.BOOLEAN,
rules: ["required"],
},
{
label: "User can manage group",
label: i18n.tc("user.user-can-manage-group"),
varName: "canManage",
type: fieldTypes.BOOLEAN,
rules: ["required"],
},
{
label: "User can organize group data",
label: i18n.tc("user.user-can-organize-group-data"),
varName: "canOrganize",
type: fieldTypes.BOOLEAN,
rules: ["required"],
},
{
label: "Enable advanced features",
label: i18n.tc("user.enable-advanced-features"),
varName: "advanced",
type: fieldTypes.BOOLEAN,
rules: ["required"],

View File

@ -17,5 +17,10 @@
"weekday": "long",
"hour": "numeric",
"minute": "numeric"
},
"time": {
"ampm": "short",
"hour": "numeric",
"minute": "numeric"
}
}
}

View File

@ -63,7 +63,20 @@
"scheduled": "Scheduled",
"something-went-wrong": "Something Went Wrong!",
"subscribed-events": "Subscribed Events",
"test-message-sent": "Test Message Sent"
"test-message-sent": "Test Message Sent",
"new-notification": "New Notification",
"event-notifiers": "Event Notifiers",
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
"enable-notifier": "Enable Notifier",
"what-events": "What events should this notifier subscribe to?",
"user-events": "User Events",
"mealplan-events": "Mealplan Events",
"when-a-user-in-your-group-creates-a-new-mealplan": "When a user in your group creates a new mealplan",
"shopping-list-events": "Shopping List Events",
"cookbook-events": "Cookbook Events",
"tag-events": "Tag Events",
"category-events": "Category Events",
"when-a-new-user-joins-your-group": "When a new user joins your group"
},
"general": {
"cancel": "Cancel",
@ -171,11 +184,14 @@
"this-feature-is-currently-inactive": "This feature is currently inactive",
"clipboard-not-supported": "Clipboard not supported",
"copied-to-clipboard": "Copied to clipboard",
"your-browser-does-not-support-clipboard": "Your browser does not support clipboard\")",
"your-browser-does-not-support-clipboard": "Your browser does not support clipboard",
"copied-items-to-clipboard": "No item copied to clipboard|One item copied to clipboard|Copied {count} items to clipboard",
"actions": "Actions",
"selected-count": "Selected: {count}",
"export-all": "Export All"
"export-all": "Export All",
"refresh": "Refresh",
"upload-file": "Upload File",
"created-on-date": "Created on: {0}"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
@ -224,7 +240,11 @@
"disable-organizing-recipe-ingredients-by-units-and-food-description": "Hides the Food, Unit, and Amount fields for ingredients and treats ingredients as plain text fields.",
"general-preferences": "General Preferences",
"group-recipe-preferences": "Group Recipe Preferences",
"report": "Report"
"report": "Report",
"group-management": "Group Management",
"admin-group-management": "Admin Group Management",
"admin-group-management-text": "Changes to this group will be reflected immediately.",
"group-id-value": "Group Id: {0}"
},
"meal-plan": {
"create-a-new-meal-plan": "Create a New Meal Plan",
@ -310,7 +330,18 @@
"mealie-pre-v1": {
"description-long": "Mealie can import recipes from the Mealie application from a pre v1.0 release. Export your recipes from your old instance, and upload the zip file below. Note that only recipes can be imported from the export.",
"title": "Mealie Pre v1.0"
}
},
"recipe-data-migrations": "Recipe Data Migrations",
"recipe-data-migrations-explanation": "Recipes can be migrated from another supported application to Mealie. This is a great way to get started with Mealie.",
"choose-migration-type": "Choose Migration Type",
"tag-all-recipes": "Tag all recipes with {tag-name} tag",
"nextcloud-text": "Nextcloud recipes can be imported from a zip file that contains the data stored in Nextcloud. See the example folder structure below to ensure your recipes are able to be imported.",
"chowdown-text": "Mealie natively supports the chowdown repository format. Download the code repository as a .zip file and upload it below",
"recipe-1": "Recipe 1",
"recipe-2": "Recipe 2",
"paprika-text": "Mealie can import recipes from the Paprika application. Export your recipes from paprika, rename the export extension to .zip and upload it below.",
"mealie-text": "Mealie can import recipes from the Mealie application from a pre v1.0 release. Export your recipes from your old instance, and upload the zip file below. Note that only recipes can be imported from the export.",
"previous-migrations": "Previous Migrations"
},
"new-recipe": {
"bulk-add": "Bulk Add",
@ -490,7 +521,9 @@
"recipe-debugger-description": "Grab the URL of the recipe you want to debug and paste it here. The URL will be scraped by the recipe scraper and the results will be displayed. If you don't see any data returned, the site you are trying to scrape is not supported by Mealie or its scraper library.",
"debug": "Debug",
"tree-view": "Tree View",
"recipe-yield": "Recipe Yield"
"recipe-yield": "Recipe Yield",
"unit": "Unit",
"upload-image": "Upload image"
},
"search": {
"advanced-search": "Advanced Search",
@ -507,7 +540,8 @@
"search-placeholder": "Search...",
"tag-filter": "Tag Filter",
"search-hint": "Press '/'",
"advanced": "Advanced"
"advanced": "Advanced",
"auto-search": "Auto Search"
},
"settings": {
"add-a-new-theme": "Add a New Theme",
@ -522,7 +556,16 @@
"full-backup": "Full Backup",
"import-summary": "Import Summary",
"partial-backup": "Partial Backup",
"unable-to-delete-backup": "Unable to Delete Backup."
"unable-to-delete-backup": "Unable to Delete Backup.",
"experimental-description": "Backups a total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think off this as a snapshot of Mealie at a specific time. Currently, {not-crossed-version} (data migrations are not done automatically). These serve as a database agnostic way to export and import data or backup the site to an external location.",
"not-crossed-version": "this backup mechanism is not cross-version and therefore cannot be used to migrate data between versions",
"backup-restore": "Backup Restore",
"back-restore-description": "Restoring this backup will overwrite all the current data in your database and in the data directory and replace them with the contents of this backup. {cannot-be-undone} If the restoration is successful, you will be logged out.",
"cannot-be-undone": "This action cannot be undone - use with caution.",
"postgresql-note": "If you are using PostGreSQL, please review the {backup-restore-process} prior to restoring.",
"backup-restore-process-in-the-documentation": "backup/restore process in the documentation",
"irreversible-acknowledgment": "I understand that this action is irreversible, destructive and may cause data loss",
"restore-backup": "Restore Backup"
},
"backup-and-exports": "Backups",
"change-password": "Change Password",
@ -585,7 +628,9 @@
"api-tokens": "API Tokens",
"copy-this-token-for-use-with-an-external-application-this-token-will-not-be-viewable-again": "Copy this token for use with an external application. This token will not be viewable again.",
"create-an-api-token": "Create an API Token",
"token-name": "Token Name"
"token-name": "Token Name",
"generate": "Generate",
"you-have-token-count": "You have no active tokens.|You have one active token.|You have {count} active tokens."
},
"toolbox": {
"assign-all": "Assign All",
@ -604,8 +649,39 @@
"webhook-url": "Webhook URL",
"webhooks-caps": "WEBHOOKS",
"webhooks": "Webhooks",
"webhook-name": "Webhook Name"
}
"webhook-name": "Webhook Name",
"description": "The webhooks defined below will be executed when a meal is defined for the day. At the scheduled time the webhooks will be sent with the data from the recipe that is scheduled for the day. Note that webhook execution is not exact. The webhooks are executed on a 5 minutes interval so the webhooks will be executed within 5 +/- minutes of the scheduled."
},
"bug-report": "Bug Report",
"bug-report-information": "Use this information to report a bug. Providing details of your instance to developers is the best way to get your issues resolved quickly.",
"tracker": "Tracker",
"configuration": "Configuration",
"docker-volume": "Docker Volume",
"docker-volume-help": "Mealie requires that the frontend container and the backend share the same docker volume or storage. This ensures that the frontend container can properly access the images and assets stored on disk.",
"volumes-are-misconfigured": "Volumes are misconfigured",
"volumes-are-configured-correctly": "Volumes are configured correctly.",
"status-unknown-try-running-a-validation": "Status Unknown. Try running a validation.",
"validate": "Validate",
"email-configuration-status": "Email Configuration Status",
"ready": "Ready",
"not-ready": "Not Ready - Check Environmental Variables",
"succeeded": "Succeeded",
"failed": "Failed",
"general-about": "General About",
"application-version": "Application Version",
"application-version-error-text": "Your current version ({0}) does not match the latest release. Considering updating to the latest version ({1}).",
"mealie-is-up-to-date": "Mealie is up to date",
"secure-site": "Secure Site",
"secure-site-error-text": "Serve via localhost or secure with https. Clipboard and additional browser APIs may not work.",
"secure-site-success-text": "Site is accessed by localhost or https",
"server-side-base-url": "Server Side Base URL",
"server-side-base-url-error-text": "`BASE_URL` is still the default value on API Server. This will cause issues with notifications links generated on the server for emails, etc.",
"server-side-base-url-success-text": "Server Side URL does not match the default",
"ldap-ready": "LDAP Ready",
"ldap-ready-error-text": "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
"ldap-ready-success-text": "Required LDAP variables are all set.",
"build": "Build",
"recipe-scraper-version": "Recipe Scraper Version"
},
"shopping-list": {
"all-lists": "All Lists",
@ -759,7 +835,20 @@
"good": "Good",
"strong": "Strong",
"very-strong": "Very Strong"
}
},
"user-management": "User Management",
"reset-locked-users": "Reset Locked Users",
"admin-user-creation": "Admin User Creation",
"user-details": "User Details",
"user-name": "User Name",
"authentication-method": "Authentication Method",
"authentication-method-hint": "This specifies how a user will authenticate with Mealie. If you're not sure, choose 'Mealie",
"permissions": "Permissions",
"administrator": "Administrator",
"user-can-invite-other-to-group": "User can invite other to group",
"user-can-manage-group": "User can manage group",
"user-can-organize-group-data": "User can organize group data",
"enable-advanced-features": "Enable advanced features"
},
"language-dialog": {
"translated": "translated",
@ -811,7 +900,7 @@
"confirm-delete-recipes": "Are you sure you want to delete the following recipes? This action cannot be undone.",
"the-following-recipes-selected-length-will-be-exported": "The following recipes ({0}) will be exported.",
"settings-chosen-explanation": "Settings chosen here, excluding the locked option, will be applied to all selected recipes.",
"selected-length-recipe-s-settings-will-be-updated": "{0} recipe(s) settings will be updated.",
"selected-length-recipe-s-settings-will-be-updated": "{count} recipe(s) settings will be updated.",
"recipe-data": "Recipe Data",
"recipe-data-description": "Use this section to manage the data associated with your recipes. You can perform several bulk actions on your recipes including exporting, deleting, tagging, and assigning categories.",
"recipe-columns": "Recipe Columns",
@ -832,7 +921,8 @@
"data-management-description": "Select which data set you want to make changes to.",
"select-data": "Select Data",
"select-language": "Select Language",
"columns": "Columns"
"columns": "Columns",
"combine": "Combine"
},
"user-registration": {
"user-registration": "User Registration",
@ -848,7 +938,8 @@
"validation": {
"group-name-is-taken": "Group name is taken",
"username-is-taken": "Username is taken",
"email-is-taken": "Email is taken"
"email-is-taken": "Email is taken",
"this-field-is-required": "This Field is Required"
},
"export": {
"export": "Export",
@ -943,7 +1034,21 @@
},
"mainentance": {
"actions-title": "Actions"
}
},
"ingredients-natural-language-processor": "Ingredients Natural Language Processor",
"ingredients-natural-language-processor-explanation": "Mealie uses Conditional Random Fields (CRFs) for parsing and processing ingredients. The model used for ingredients is based off a data set of over 100,000 ingredients from a dataset compiled by the New York Times. Note that as the model is trained in English only, you may have varied results when using the model in other languages. This page is a playground for testing the model.",
"ingredients-natural-language-processor-explanation-2": "It's not perfect, but it yields great results in general and is a good starting point for manually parsing ingredients into individual fields. Alternatively, you can also use the \"Brute\" processor that uses a pattern matching technique to identify ingredients.",
"nlp": "NLP",
"brute": "Brute",
"show-individual-confidence": "Show individual confidence",
"ingredient-text": "Ingredient Text",
"average-confident": "{0} Confident",
"try-an-example": "Try an example",
"parser": "Parser",
"background-tasks": "Background Tasks",
"background-tasks-description": "Here you can view all the running background tasks and their status",
"no-logs-found": "No Logs Found",
"tasks": "Tasks"
},
"profile": {
"welcome-user": "👋 Welcome, {0}",
@ -979,7 +1084,14 @@
"preferences": "Preferences",
"show-advanced-description": "Show advanced features (API Keys, Webhooks, and Data Management)",
"back-to-profile": "Back to Profile",
"looking-for-privacy-settings": "Looking for Privacy Settings?"
"looking-for-privacy-settings": "Looking for Privacy Settings?",
"manage-your-api-tokens": "Manage Your API Tokens",
"manage-user-profile": "Manage User Profile",
"manage-cookbooks": "Manage Cookbooks",
"manage-members": "Manage Members",
"manage-webhooks": "Manage Webhooks",
"manage-notifiers": "Manage Notifiers",
"manage-data-migrations": "Manage Data Migrations"
},
"cookbook": {
"cookbooks": "Cookbooks",

View File

@ -4,17 +4,17 @@
<template #header>
<v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-tasks.svg')"></v-img>
</template>
<template #title> Background Tasks </template>
Here you can view all the running background tasks and their status
<template #title> {{ $t('admin.background-tasks') }} </template>
{{ $t('admin.background-tasks-description') }}
</BasePageTitle>
<v-card-actions>
<BaseButton color="info" :loading="loading" @click="refreshTasks">
<template #icon> {{ $globals.icons.refresh }} </template>
Refresh
{{ $t('general.refresh') }}
</BaseButton>
<BaseButton color="info" @click="testTask">
<template #icon> {{ $globals.icons.testTube }} </template>
Test
{{ $t('general.test') }}
</BaseButton>
</v-card-actions>
<v-expansion-panels class="mt-2">
@ -32,7 +32,7 @@
{{ $d(Date.parse(task.createdAt), "short") }}
</v-expansion-panel-header>
<v-expansion-panel-content style="white-space: pre-line">
{{ task.log === "" ? "No Logs Found" : task.log }}
{{ task.log === "" ? $t('admin.no-logs-found') : task.log }}
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
@ -80,7 +80,7 @@ export default defineComponent({
},
head() {
return {
title: "Tasks",
title: this.$t("admin.tasks"),
};
},
});

View File

@ -16,33 +16,37 @@
</BaseDialog>
<!-- Import Dialog -->
<BaseDialog v-model="importDialog" color="error" title="Backup Restore" :icon="$globals.icons.database">
<BaseDialog v-model="importDialog" color="error" :title="$t('settings.backup.backup-restore')" :icon="$globals.icons.database">
<v-divider></v-divider>
<v-card-text>
Restoring this backup will overwrite all the current data in your database and in the data directory and
replace them with the contents of this backup. <b> This action cannot be undone - use with caution. </b> If
the restoration is successful, you will be logged out.
<i18n path="settings.backup.back-restore-description">
<template #cannot-be-undone>
<b> {{ $t('settings.backup.cannot-be-undone') }} </b>
</template>
</i18n>
<p class="mt-3">
If you are using PostGreSQL, please review the
<a href="https://nightly.mealie.io/documentation/getting-started/usage/backups-and-restoring/"
>backup/restore process in the documentation</a
>
prior to restoring.
<i18n path="settings.backup.postgresql-note">
<template #backup-restore-process>
<a href="https://nightly.mealie.io/documentation/getting-started/usage/backups-and-restoring/" >{{ $t('settings.backup.backup-restore-process-in-the-documentation') }}</a >
</template>
</i18n>
{{ $t('') }}
</p>
<v-checkbox
v-model="confirmImport"
class="checkbox-top"
color="error"
hide-details
label="I understand that this action is irreversible, destructive and may cause data loss"
:label="$t('settings.backup.irreversible-acknowledgment')"
></v-checkbox>
</v-card-text>
<v-card-actions class="justify-center pt-0">
<BaseButton delete :disabled="!confirmImport" @click="restoreBackup(selected)">
<template #icon> {{ $globals.icons.database }} </template>
Restore Backup
{{ $t('settings.backup.restore-backup') }}
</BaseButton>
</v-card-actions>
<p class="caption pb-0 mb-1 text-center">
@ -51,16 +55,13 @@
</BaseDialog>
<section>
<BaseCardSectionTitle title="Backups">
<BaseCardSectionTitle :title="$tc('settings.backup-and-exports')">
<v-card-text class="py-0 px-1">
Backups a total snapshots of the database and data directory of the site. This includes all data and cannot
be set to exclude subsets of data. You can think off this as a snapshot of Mealie at a specific time.
Currently,
<b>
this backup mechanism is not cross-version and therefore cannot be used to migrate data between versions
</b>
(data migrations are not done automatically). These serve as a database agnostic way to export and import
data or backup the site to an external location.
<i18n path="settings.backup.experimental-description">
<template #not-crossed-version>
<b> {{ $t('settings.backup.not-crossed-version') }} </b>
</template>
</i18n>
</v-card-text>
</BaseCardSectionTitle>
<BaseButton @click="createBackup"> {{ $t("settings.backup.create-heading") }} </BaseButton>
@ -108,7 +109,7 @@
</section>
</section>
<v-container class="mt-4 d-flex justify-end">
<v-btn outlined rounded to="/group/migrations"> Looking For Migrations? </v-btn>
<v-btn outlined rounded to="/group/migrations"> {{ $t('recipe.looking-for-migrations') }} </v-btn>
</v-container>
</v-container>
</template>
@ -177,7 +178,7 @@ export default defineComponent({
headers: [
{ text: i18n.t("general.name"), value: "name" },
{ text: i18n.t("general.created"), value: "date" },
{ text: "Size", value: "size" },
{ text: i18n.t("export.size"), value: "size" },
{ text: "", value: "actions", align: "right" },
],
});

View File

@ -4,15 +4,15 @@
<template #header>
<v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-group-settings.svg')"></v-img>
</template>
<template #title> Admin Group Management </template>
Changes to this group will be reflected immediately.
<template #title> {{ $t('group.admin-group-management') }} </template>
{{ $t('group.admin-group-management-text') }}
</BasePageTitle>
<AppToolbar back> </AppToolbar>
<v-card-text> Group Id: {{ group.id }} </v-card-text>
<v-card-text> {{ $t('group.group-id-value', [group.id]) }} </v-card-text>
<v-form v-if="!userError" ref="refGroupEditForm" @submit.prevent="handleSubmit">
<v-card outlined>
<v-card-text>
<v-text-field v-model="group.name" label="Group Name"> </v-text-field>
<v-text-field v-model="group.name" :label="$t('group.group-name')"> </v-text-field>
<GroupPreferencesEditor v-if="group.preferences" v-model="group.preferences" />
</v-card-text>
</v-card>

View File

@ -25,7 +25,7 @@
</v-card-text>
</BaseDialog>
<BaseCardSectionTitle title="Group Management"> </BaseCardSectionTitle>
<BaseCardSectionTitle :title="$tc('group.group-management')"> </BaseCardSectionTitle>
<section>
<v-toolbar flat color="background" class="justify-between">
<BaseButton @click="openDialog"> {{ $t("general.create") }} </BaseButton>
@ -102,7 +102,7 @@ export default defineComponent({
createUserForm: {
items: [
{
label: "Group Name",
label: i18n.t("group.group-name"),
varName: "name",
type: fieldTypes.TEXT,
rules: ["required"],

View File

@ -4,7 +4,7 @@
<template #header>
<v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-profile.svg')"></v-img>
</template>
<template #title> Admin User Creation </template>
<template #title> {{ $t('user.admin-user-creation') }} </template>
</BasePageTitle>
<AppToolbar back> </AppToolbar>
<v-form ref="refNewUserForm" @submit.prevent="handleSubmit">
@ -20,7 +20,7 @@
item-value="name"
:return-object="false"
filled
label="User Group"
:label="$t('group.user-group')"
:rules="[validators.required]"
></v-select>
<AutoForm v-model="newUserData" :items="userForm" />

View File

@ -16,24 +16,14 @@
</v-card-text>
</BaseDialog>
<BaseCardSectionTitle title="User Management"> </BaseCardSectionTitle>
<BaseCardSectionTitle :title="$tc('user.user-management')"> </BaseCardSectionTitle>
<section>
<v-toolbar color="background" flat class="justify-between">
<BaseButton to="/admin/manage/users/create" class="mr-2">
{{ $t("general.create") }}
</BaseButton>
<BaseOverflowButton
mode="event"
:items="[
{
text: 'Reset Locked Users',
icon: $globals.icons.lock,
event: 'unlock-all-users',
},
]"
@unlock-all-users="unlockAllUsers"
>
<BaseOverflowButton mode="event" :items="ACTIONS_OPTIONS" @unlock-all-users="unlockAllUsers">
</BaseOverflowButton>
</v-toolbar>
<v-data-table
@ -89,7 +79,7 @@ export default defineComponent({
const user = computed(() => $auth.user as UserOut | null);
const { i18n } = useContext();
const { $globals, i18n } = useContext();
const router = useRouter();
@ -97,6 +87,14 @@ export default defineComponent({
return state.deleteTargetId === user.value?.id;
});
const ACTIONS_OPTIONS = [
{
text: i18n.t("user.reset-locked-users"),
icon: $globals.icons.lock,
event: "unlock-all-users",
},
];
const state = reactive({
deleteDialog: false,
deleteTargetId: "",
@ -158,6 +156,7 @@ export default defineComponent({
users,
user,
handleRowClick,
ACTIONS_OPTIONS,
};
},
head() {

View File

@ -1,31 +1,26 @@
<template>
<v-container class="pa-0">
<v-container>
<BaseCardSectionTitle title="Ingredients Natural Language Processor">
Mealie uses Conditional Random Fields (CRFs) for parsing and processing ingredients. The model used for
ingredients is based off a data set of over 100,000 ingredients from a dataset compiled by the New York Times.
Note that as the model is trained in English only, you may have varied results when using the model in other
languages. This page is a playground for testing the model.
<BaseCardSectionTitle :title="$t('admin.ingredients-natural-language-processor')">
{{ $t('admin.ingredients-natural-language-processor-explanation') }}
<p class="pt-3">
It's not perfect, but it yields great results in general and is a good starting point for manually parsing
ingredients into individual fields. Alternatively, you can also use the "Brute" processor that uses a pattern
matching technique to identify ingredients.
{{ $t('admin.ingredients-natural-language-processor-explanation-2') }}
</p>
</BaseCardSectionTitle>
<div class="d-flex align-center justify-center justify-md-start flex-wrap">
<v-btn-toggle v-model="parser" dense mandatory @change="processIngredient">
<v-btn value="nlp"> NLP </v-btn>
<v-btn value="brute"> Brute </v-btn>
<v-btn value="nlp"> {{ $t('admin.nlp') }} </v-btn>
<v-btn value="brute"> {{ $t('admin.brute') }} </v-btn>
</v-btn-toggle>
<v-checkbox v-model="showConfidence" class="ml-5" label="Show individual confidence"></v-checkbox>
<v-checkbox v-model="showConfidence" class="ml-5" :label="$t('admin.show-individual-confidence')"></v-checkbox>
</div>
<v-card flat>
<v-card-text>
<v-text-field v-model="ingredient" label="Ingredient Text"> </v-text-field>
<v-text-field v-model="ingredient" :label="$t('admin.ingredient-text')"> </v-text-field>
</v-card-text>
<v-card-actions>
<BaseButton class="ml-auto" @click="processIngredient">
@ -38,7 +33,7 @@
<v-container v-if="results">
<div v-if="parser !== 'brute' && getConfidence('average')" class="d-flex">
<v-chip dark :color="getColor('average')" class="mx-auto mb-2">
{{ getConfidence("average") }} Confident
{{ $t('admin.average-confident', [getConfidence("average")]) }}
</v-chip>
</div>
<div class="d-flex justify-center flex-wrap" style="gap: 1.5rem">
@ -51,14 +46,14 @@
</v-card-text>
</v-card>
<v-chip v-if="prop.confidence && showConfidence" dark :color="prop.color" class="mt-2">
{{ prop.confidence }} Confident
{{ $t('admin.average-confident', [prop.confidence]) }}
</v-chip>
</div>
</template>
</div>
</v-container>
<v-container class="narrow-container">
<v-card-title> Try an example </v-card-title>
<v-card-title> {{ $t('admin.try-an-example') }} </v-card-title>
<v-card v-for="(text, idx) in tryText" :key="idx" class="my-2" hover @click="processTryText(text)">
<v-card-text> {{ text }} </v-card-text>
</v-card>
@ -67,7 +62,7 @@
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from "@nuxtjs/composition-api";
import { defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
import { IngredientConfidence } from "~/lib/api/types/recipe";
import { useUserApi } from "~/composables/api";
import { Parser } from "~/lib/api/user/recipes/recipe";
@ -86,6 +81,8 @@ export default defineComponent({
parser: "nlp" as Parser,
});
const { i18n } = useContext();
const confidence = ref<IngredientConfidence>({});
function getColor(attribute: ConfidenceAttribute) {
@ -169,25 +166,25 @@ export default defineComponent({
const properties = reactive({
quantity: {
subtitle: "Quantity",
subtitle: i18n.t("recipe.quantity"),
value: "" as string | number,
color: null,
confidence: null,
},
unit: {
subtitle: "Unit",
subtitle: i18n.t("recipe.unit"),
value: "",
color: null,
confidence: null,
},
food: {
subtitle: "Food",
subtitle: i18n.t("shopping-list.food"),
value: "",
color: null,
confidence: null,
},
comment: {
subtitle: "Comment",
subtitle: i18n.t("recipe.comment"),
value: "",
color: null,
confidence: null,
@ -210,7 +207,7 @@ export default defineComponent({
},
head() {
return {
title: "Parser",
title: this.$t("admin.parser"),
};
},
});

View File

@ -7,17 +7,16 @@
<template #title> {{ $t("settings.site-settings") }} </template>
</BasePageTitle>
<BaseDialog v-model="bugReportDialog" title="Bug Report" :width="800" :icon="$globals.icons.github">
<BaseDialog v-model="bugReportDialog" :title="$t('settings.bug-report')" :width="800" :icon="$globals.icons.github">
<v-card-text>
<div class="pb-4">
Use this information to report a bug. Providing details of your instance to developers is the best way to get
your issues resolved quickly.
{{ $t('settings.bug-report-information') }}
</div>
<v-textarea v-model="bugReportText" outlined rows="18" readonly> </v-textarea>
<div class="d-flex justify-end" style="gap: 5px">
<BaseButton color="gray" secondary target="_blank" href="https://github.com/hay-kot/mealie/issues/new/choose">
<template #icon> {{ $globals.icons.github }}</template>
Tracker
{{ $t('settings.tracker') }}
</BaseButton>
<AppButtonCopy :copy-text="bugReportText" color="info" :icon="false" />
</div>
@ -33,12 +32,12 @@
"
>
<template #icon> {{ $globals.icons.github }}</template>
Bug Report
{{ $t('settings.bug-report') }}
</BaseButton>
</div>
<section>
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" title="Configuration"> </BaseCardSectionTitle>
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" :title="$t('settings.configuration')"> </BaseCardSectionTitle>
<v-card class="mb-4">
<template v-for="(check, idx) in simpleChecks">
<v-list-item :key="`list-item-${idx}`">
@ -62,7 +61,7 @@
</section>
<section>
<BaseCardSectionTitle class="pt-2" :icon="$globals.icons.docker" title="Docker Volume" />
<BaseCardSectionTitle class="pt-2" :icon="$globals.icons.docker" :title="$t('settings.docker-volume')" />
<v-alert
border="left"
colored-border
@ -72,36 +71,35 @@
:loading="docker.loading"
>
<div class="d-flex align-center font-weight-medium">
Docker Volume
{{ $t('settings.docker-volume') }}
<HelpIcon small class="my-n3">
Mealie requires that the frontend container and the backend share the same docker volume or storage. This
ensures that the frontend container can properly access the images and assets stored on disk.
{{ $t('settings.docker-volume-help') }}
</HelpIcon>
</div>
<div>
<template v-if="docker.state === DockerVolumeState.Error"> Volumes are misconfigured. </template>
<template v-if="docker.state === DockerVolumeState.Error"> {{ $t('settings.volumes-are-misconfigured') }}. </template>
<template v-else-if="docker.state === DockerVolumeState.Success">
Volumes are configured correctly.
{{ $t('settings.volumes-are-configured-correctly') }}
</template>
<template v-else-if="docker.state === DockerVolumeState.Unknown">
Status Unknown. Try running a validation.
{{ $t('settings.status-unknown-try-running-a-validation') }}
</template>
</div>
<div class="mt-4">
<BaseButton color="info" :loading="docker.loading" @click="dockerValidate">
<template #icon> {{ $globals.icons.checkboxMarkedCircle }} </template>
Validate
{{ $t('settings.validate') }}
</BaseButton>
</div>
</v-alert>
</section>
<section>
<BaseCardSectionTitle class="pt-2" :icon="$globals.icons.email" title="Email" />
<BaseCardSectionTitle class="pt-2" :icon="$globals.icons.email" :title="$tc('user.email')" />
<v-alert border="left" colored-border :type="appConfig.emailReady ? 'success' : 'error'" elevation="2">
<div class="font-weight-medium">Email Configuration Status</div>
<div class="font-weight-medium">{{ $t('settings.email-configuration-status') }}</div>
<div>
{{ appConfig.emailReady ? "Ready" : "Not Ready - Check Environmental Variables" }}
{{ appConfig.emailReady ? $t('settings.ready') : $t('settings.not-ready') }}
</div>
<div>
<v-text-field v-model="address" class="mr-4" :label="$t('user.email')" :rules="[validators.email]">
@ -120,7 +118,7 @@
<v-card-text class="px-0">
<h4>Email Test Results</h4>
<span class="pl-4">
{{ success ? "Succeeded" : "Failed" }}
{{ success ? $t('settings.succeeded') : $t('settings.failed') }}
</span>
</v-card-text>
</template>
@ -130,7 +128,7 @@
<!-- General App Info -->
<section class="mt-4">
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" title="General About"> </BaseCardSectionTitle>
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" :title="$t('settings.general-about')"> </BaseCardSectionTitle>
<v-card class="mb-4">
<template v-for="(property, idx) in appInfo">
<v-list-item :key="property.name">
@ -183,6 +181,7 @@ import {
useAsync,
useContext,
} from "@nuxtjs/composition-api";
import { TranslateResult } from "vue-i18n";
import { useAdminApi, useUserApi } from "~/composables/api";
import { validators } from "~/composables/use-validators";
import { useAsyncKey } from "~/composables/use-utils";
@ -195,10 +194,11 @@ enum DockerVolumeState {
}
interface SimpleCheck {
text: string;
id: string;
text: TranslateResult;
status: boolean | undefined;
successText: string;
errorText: string;
successText: TranslateResult;
errorText: TranslateResult;
color: string;
icon: string;
}
@ -281,38 +281,43 @@ export default defineComponent({
const badColor = "error";
const warningColor = "warning";
const data: SimpleCheck[] = [
{
text: "Application Version",
id: "application-version",
text: i18n.t("settings.application-version"),
status: appConfig.value.isUpToDate,
errorText: `Your current version (${rawAppInfo.value.version}) does not match the latest release. Considering updating to the latest version (${rawAppInfo.value.versionLatest}).`,
successText: "Mealie is up to date",
errorText: i18n.t("settings.application-version-error-text", [rawAppInfo.value.version, rawAppInfo.value.versionLatest]),
successText: i18n.t("settings.mealie-is-up-to-date"),
color: appConfig.value.isUpToDate ? goodColor : warningColor,
icon: appConfig.value.isUpToDate ? goodIcon : warningIcon,
},
{
text: "Secure Site",
id: "secure-site",
text: i18n.t("settings.secure-site"),
status: appConfig.value.isSiteSecure,
errorText: "Serve via localhost or secure with https. Clipboard and additional browser APIs may not work.",
successText: "Site is accessed by localhost or https",
errorText: i18n.t("settings.secure-site-error-text"),
successText: i18n.t("settings.secure-site-success-text"),
color: appConfig.value.isSiteSecure ? goodColor : badColor,
icon: appConfig.value.isSiteSecure ? goodIcon : badIcon,
},
{
text: "Server Side Base URL",
id: "server-side-base-url",
text: i18n.t("settings.server-side-base-url"),
status: appConfig.value.baseUrlSet,
errorText:
"`BASE_URL` is still the default value on API Server. This will cause issues with notifications links generated on the server for emails, etc.",
successText: "Server Side URL does not match the default",
i18n.t("settings.server-side-base-url-error-text"),
successText: i18n.t("settings.server-side-base-url-success-text"),
color: appConfig.value.baseUrlSet ? goodColor : badColor,
icon: appConfig.value.baseUrlSet ? goodIcon : badIcon,
},
{
text: "LDAP Ready",
id: "ldap-ready",
text: i18n.t("settings.ldap-ready"),
status: appConfig.value.ldapReady,
errorText:
"Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
successText: "Required LDAP variables are all set.",
i18n.t("settings.ldap-ready-error-text"),
successText: i18n.t("settings.ldap-ready-success-text"),
color: appConfig.value.ldapReady ? goodColor : warningColor,
icon: appConfig.value.ldapReady ? goodIcon : warningIcon,
},
@ -377,7 +382,7 @@ export default defineComponent({
},
{
slot: "build",
name: "Build",
name: i18n.t("settings.build"),
icon: $globals.icons.information,
value: data.buildId,
},
@ -418,7 +423,7 @@ export default defineComponent({
},
{
slot: "recipe-scraper",
name: "Recipe Scraper Version",
name: i18n.t("settings.recipe-scraper-version"),
icon: $globals.icons.primary,
value: data.recipeScraperVersion,
},
@ -452,17 +457,17 @@ export default defineComponent({
});
const ignoreChecks: { [key: string]: boolean } = {
"Application Version": true,
"application-version": true,
};
text += "\n**Checks**\n";
simpleChecks.value.forEach((item) => {
if (ignoreChecks[item.text]) {
if (ignoreChecks[item.id]) {
return;
}
const status = item.status ? "Yes" : "No";
text += `${item.text}: ${status}\n`;
text += `${item.text.toString()}: ${status}\n`;
});
text += `Email Configured: ${appConfig.value.emailReady ? "Yes" : "No"}\n`;

View File

@ -135,7 +135,7 @@
<BaseButton create @click="createDialog = true" />
<BaseButton @click="mergeDialog = true">
<template #icon> {{ $globals.icons.foods }} </template>
Combine
{{ $t('data-pages.combine') }}
</BaseButton>
</template>
<template #item.label="{ item }">
@ -146,7 +146,7 @@
<template #button-bottom>
<BaseButton @click="seedDialog = true">
<template #icon> {{ $globals.icons.database }} </template>
Seed
{{ $t('data-pages.seed') }}
</BaseButton>
</template>
</CrudTable>

View File

@ -61,7 +61,7 @@
<RecipeSettingsSwitches v-model="recipeSettings" />
</div>
<p class="text-center mb-0">
<i>{{ $t('data-pages.recipes.selected-length-recipe-s-settings-will-be-updated', [selected.length]) }}</i>
<i>{{ $tc('data-pages.recipes.selected-length-recipe-s-settings-will-be-updated', selected.length) }}</i>
</p>
</v-card-text>
</BaseDialog>

View File

@ -133,7 +133,7 @@
<BaseButton @click="mergeDialog = true">
<template #icon> {{ $globals.icons.units }} </template>
Combine
{{ $t('data-pages.combine') }}
</BaseButton>
</template>
<template #item.useAbbreviation="{ item }">
@ -149,7 +149,7 @@
<template #button-bottom>
<BaseButton @click="seedDialog = true">
<template #icon> {{ $globals.icons.database }} </template>
Seed
{{ $t('data-pages.seed') }}
</BaseButton>
</template>
</CrudTable>

View File

@ -26,8 +26,8 @@
<div class="d-flex align-center justify-space-between mb-2">
<v-tabs>
<v-tab to="/group/mealplan/planner/view">Meal Planner</v-tab>
<v-tab to="/group/mealplan/planner/edit">Edit</v-tab>
<v-tab to="/group/mealplan/planner/view">{{ $t('meal-plan.meal-planner') }}</v-tab>
<v-tab to="/group/mealplan/planner/edit">{{ $t('general.edit') }}</v-tab>
</v-tabs>
<ButtonLink :icon="$globals.icons.calendar" to="/group/mealplan/settings" :text="$tc('general.settings')" />
</div>

View File

@ -137,7 +137,7 @@
<v-icon left>
{{ $globals.icons.tags }}
</v-icon>
{{ mealplan.entryType }}
{{ getEntryTypeText(mealplan.entryType) }}
</v-chip>
</template>
<v-list>
@ -167,7 +167,7 @@
children: [
{
icon: $globals.icons.diceMultiple,
text: 'Breakfast',
text: $tc('meal-plan.breakfast'),
event: 'randomBreakfast',
},
{
@ -212,7 +212,7 @@ import { SortableEvent } from "sortablejs";
import draggable from "vuedraggable";
import { watchDebounced } from "@vueuse/core";
import { MealsByDate } from "./types";
import { useMealplans, planTypeOptions } from "~/composables/use-group-mealplan";
import { useMealplans, usePlanTypeOptions, getEntryTypeText } from "~/composables/use-group-mealplan";
import RecipeCardImage from "~/components/Domain/Recipe/RecipeCardImage.vue";
import { PlanEntryType } from "~/lib/api/types/meal-plan";
import { useUserApi } from "~/composables/api";
@ -333,10 +333,13 @@ export default defineComponent({
const search = useRecipeSearch(api);
const planTypeOptions = usePlanTypeOptions();
return {
state,
onMoveCallback,
planTypeOptions,
getEntryTypeText,
// Dialog
dialog,

View File

@ -39,7 +39,7 @@
</template>
<script lang="ts">
import { computed, defineComponent } from "@nuxtjs/composition-api";
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
import { MealsByDate } from "./types";
import { ReadPlanEntry } from "~/lib/api/types/meal-plan";
import RecipeCardMobile from "~/components/Domain/Recipe/RecipeCardMobile.vue";
@ -65,15 +65,17 @@ export default defineComponent({
sections: DaySection[];
};
const { i18n } = useContext();
const plan = computed<Days[]>(() => {
return props.mealplans.reduce((acc, day) => {
const out: Days = {
date: day.date,
sections: [
{ title: "Breakfast", meals: [] },
{ title: "Lunch", meals: [] },
{ title: "Dinner", meals: [] },
{ title: "Side", meals: [] },
{ title: i18n.tc("meal-plan.breakfast"), meals: [] },
{ title: i18n.tc("meal-plan.lunch"), meals: [] },
{ title: i18n.tc("meal-plan.dinner"), meals: [] },
{ title: i18n.tc("meal-plan.side"), meals: [] },
],
};

View File

@ -9,14 +9,13 @@
:src="require('~/static/svgs/manage-data-migrations.svg')"
></v-img>
</template>
<template #title> Recipe Data Migrations</template>
Recipes can be migrated from another supported application to Mealie. This is a great way to get started with
Mealie.
<template #title> {{ $t('migration.recipe-data-migrations') }}</template>
{{ $t('migration.recipe-data-migrations-explanation') }}
</BasePageTitle>
<v-container>
<BaseCardSectionTitle :title="$i18n.tc('migration.new-migration')"> </BaseCardSectionTitle>
<v-card outlined :loading="loading">
<v-card-title> Choose Migration Type </v-card-title>
<v-card-title> {{ $t('migration.choose-migration-type') }} </v-card-title>
<v-card-text v-if="content" class="pb-0">
<div class="mb-2">
<BaseOverflowButton v-model="migrationType" mode="model" :items="items" />
@ -29,7 +28,7 @@
</v-treeview>
</v-card-text>
<v-card-title class="mt-0"> Upload File </v-card-title>
<v-card-title class="mt-0"> {{ $t('general.upload-file') }} </v-card-title>
<v-card-text>
<AppButtonUpload
accept=".zip"
@ -45,7 +44,11 @@
<v-card-text>
<v-checkbox v-model="addMigrationTag">
<template #label>
Tag all recipes with <b class="mx-1"> {{ migrationType }} </b> tag
<i18n path="migration.tag-all-recipes">
<template #tag-name>
<b class="mx-1"> {{ migrationType }} </b>
</template>
</i18n>
</template>
</v-checkbox>
</v-card-text>
@ -128,7 +131,7 @@ export default defineComponent({
children: [
{
id: 2,
name: "Recipe 1",
name: i18n.t("migration.recipe-1"),
icon: $globals.icons.folderOutline,
children: [
{ id: 3, name: "recipe.json", icon: $globals.icons.codeJson },
@ -138,7 +141,7 @@ export default defineComponent({
},
{
id: 6,
name: "Recipe 2",
name: i18n.t("migration.recipe-2"),
icon: $globals.icons.folderOutline,
children: [
{ id: 7, name: "recipe.json", icon: $globals.icons.codeJson },
@ -160,7 +163,7 @@ export default defineComponent({
children: [
{
id: 2,
name: "Recipe 1",
name: i18n.t("migration.recipe-1"),
icon: $globals.icons.folderOutline,
children: [
{ id: 3, name: "recipe.json", icon: $globals.icons.codeJson },
@ -170,7 +173,7 @@ export default defineComponent({
},
{
id: 6,
name: "Recipe 2",
name: i18n.t("migration.recipe-2"),
icon: $globals.icons.folderOutline,
children: [
{ id: 7, name: "recipe.json", icon: $globals.icons.codeJson },

View File

@ -11,7 +11,7 @@
{{ $t("general.confirm-delete-generic") }}
</v-card-text>
</BaseDialog>
<BaseDialog v-model="createDialog" title="New Notification" @submit="createNewNotifier">
<BaseDialog v-model="createDialog" :title="$t('events.new-notification')" @submit="createNewNotifier">
<v-card-text>
<v-text-field v-model="createNotifierData.name" :label="$t('general.name')"></v-text-field>
<v-text-field v-model="createNotifierData.appriseUrl" :label="$t('events.apprise-url')"></v-text-field>
@ -22,7 +22,7 @@
<template #header>
<v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-notifiers.svg')"></v-img>
</template>
<template #title> Event Notifiers </template>
<template #title> {{ $t("events.event-notifiers") }} </template>
{{ $t("events.new-notification-form-description") }}
<div class="mt-3 d-flex justify-space-around">
@ -51,12 +51,15 @@
</template>
</v-expansion-panel-header>
<v-expansion-panel-content>
<v-text-field v-model="notifiers[index].name" label="Name"></v-text-field>
<v-text-field v-model="notifiers[index].appriseUrl" label="Apprise URL (skipped if blank)"></v-text-field>
<v-checkbox v-model="notifiers[index].enabled" label="Enable Notifier" dense></v-checkbox>
<v-text-field v-model="notifiers[index].name" :label="$t('general.name')"></v-text-field>
<v-text-field
v-model="notifiers[index].appriseUrl"
:label="$t('events.apprise-url-skipped-if-blank')"
></v-text-field>
<v-checkbox v-model="notifiers[index].enabled" :label="$t('events.enable-notifier')" dense></v-checkbox>
<v-divider></v-divider>
<p class="pt-4">What events should this notifier subscribe to?</p>
<p class="pt-4">{{ $t("events.what-events") }}</p>
<div class="notifier-options">
<section v-for="sec in optionsSections" :key="sec.id">
<h4>
@ -196,27 +199,27 @@ export default defineComponent({
},
{
id: 2,
text: "User Events",
text: i18n.tc("events.user-events"),
options: [
{
text: "When a new user joins your group",
text: i18n.tc("events.when-a-new-user-joins-your-group"),
key: "userSignup",
},
],
},
{
id: 3,
text: "Mealplan Events",
text: i18n.tc("events.mealplan-events"),
options: [
{
text: "When a user in your group creates a new mealplan",
text: i18n.tc("events.when-a-user-in-your-group-creates-a-new-mealplan"),
key: "mealplanEntryCreated",
},
],
},
{
id: 4,
text: "Shopping List Events",
text: i18n.tc("events.shopping-list-events"),
options: [
{
text: i18n.t("general.create") as string,
@ -234,7 +237,7 @@ export default defineComponent({
},
{
id: 5,
text: "Cookbook Events",
text: i18n.tc("events.cookbook-events"),
options: [
{
text: i18n.t("general.create") as string,
@ -252,7 +255,7 @@ export default defineComponent({
},
{
id: 6,
text: "Tag Events",
text: i18n.tc("events.tag-events"),
options: [
{
text: i18n.t("general.create") as string,
@ -270,7 +273,7 @@ export default defineComponent({
},
{
id: 7,
text: "Category Events",
text: i18n.tc("events.category-events"),
options: [
{
text: i18n.t("general.create") as string,
@ -300,8 +303,10 @@ export default defineComponent({
createNewNotifier,
};
},
head: {
title: "Notifiers",
head() {
return {
title: this.$t("profile.notifiers"),
};
},
});
</script>

View File

@ -4,12 +4,9 @@
<template #header>
<v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-webhooks.svg')"></v-img>
</template>
<template #title> Webhooks </template>
<template #title> {{ $t('settings.webhooks.webhooks') }} </template>
<v-card-text class="pb-0">
The webhooks defined below will be executed when a meal is defined for the day. At the scheduled time the
webhooks will be sent with the data from the recipe that is scheduled for the day. Note that webhook execution
is not exact. The webhooks are executed on a 5 minutes interval so the webhooks will be executed within 5 +/-
minutes of the scheduled.
{{ $t('settings.webhooks.description') }}
</v-card-text>
</BasePageTitle>
@ -23,7 +20,7 @@
<v-icon large left :color="webhook.enabled ? 'info' : null">
{{ $globals.icons.webhook }}
</v-icon>
{{ webhook.name }} - {{ timeDisplay(timeUTCToLocal(webhook.scheduledTime)) }}
{{ webhook.name }} - {{ $d(timeUTC(webhook.scheduledTime), "time") }}
</div>
<template #actions>
<v-btn small icon class="ml-2">
@ -48,28 +45,18 @@
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import { useGroupWebhooks, timeLocalToUTC, timeUTCToLocal } from "~/composables/use-group-webhooks";
import { useGroupWebhooks, timeUTC } from "~/composables/use-group-webhooks";
import GroupWebhookEditor from "~/components/Domain/Group/GroupWebhookEditor.vue";
export default defineComponent({
components: { GroupWebhookEditor },
setup() {
const { actions, webhooks } = useGroupWebhooks();
function timeDisplay(time: string): string {
// returns the time in the format HH:MM AM/PM
const [hours, minutes] = time.split(":");
const ampm = Number(hours) < 12 ? "AM" : "PM";
const hour = Number(hours) % 12 || 12;
const minute = minutes.padStart(2, "0");
return `${hour}:${minute} ${ampm}`;
}
return {
webhooks,
actions,
timeLocalToUTC,
timeUTCToLocal,
timeDisplay,
timeUTC
};
},
head() {

View File

@ -98,7 +98,7 @@
</template>
<v-card>
<v-card-text>
<v-switch v-model="state.auto" label="Auto Search" single-line></v-switch>
<v-switch v-model="state.auto" :label="$t('search.auto-search')" single-line></v-switch>
<v-btn block color="primary" @click="reset">
{{ $tc("general.reset") }}
</v-btn>

View File

@ -1,6 +1,6 @@
<template>
<v-container>
<RecipeCardSection v-if="user" :icon="$globals.icons.heart" title="User Favorites" :recipes="user.favoriteRecipes">
<RecipeCardSection v-if="user" :icon="$globals.icons.heart" :title="$tc('user.user-favorites')" :recipes="user.favoriteRecipes">
</RecipeCardSection>
</v-container>
</template>

View File

@ -5,7 +5,7 @@
<v-img max-height="200px" max-width="200px" :src="require('~/static/svgs/manage-api-tokens.svg')"></v-img>
</template>
<template #title> API Tokens </template>
You have {{ user.tokens.length }} active tokens.
{{ $tc('settings.token.you-have-token-count', user.tokens.length) }}
</BasePageTitle>
<section class="d-flex justify-center">
<v-card class="mt-4" width="500px">
@ -33,11 +33,11 @@
</template>
</v-card-text>
<v-card-actions>
<BaseButton v-if="createdToken" cancel @click="resetCreate()"> Close </BaseButton>
<BaseButton v-if="createdToken" cancel @click="resetCreate()"> {{ $t('general.close') }} </BaseButton>
<v-spacer></v-spacer>
<AppButtonCopy v-if="createdToken" :icon="false" color="info" :copy-text="createdToken"> </AppButtonCopy>
<BaseButton v-else key="generate-button" :disabled="name == ''" @click="createToken(name)">
Generate
{{ $t('settings.token.generate') }}
</BaseButton>
</v-card-actions>
</v-card>
@ -51,7 +51,7 @@
<v-list-item-title>
{{ token.name }}
</v-list-item-title>
<v-list-item-subtitle> Created on: {{ $d(new Date(token.createdAt+"Z")) }} </v-list-item-subtitle>
<v-list-item-subtitle> {{ $t('general.created-on-date', [$d(new Date(token.createdAt+"Z"))]) }} </v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<BaseButton delete small @click="deleteToken(token.id)"></BaseButton>

View File

@ -91,7 +91,7 @@
<v-row tag="section">
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: 'Manage User Profile', to: '/user/profile/edit' }"
:link="{ text: $tc('profile.manage-user-profile'), to: '/user/profile/edit' }"
:image="require('~/static/svgs/manage-profile.svg')"
>
<template #title> {{ $t('profile.user-settings') }} </template>
@ -101,7 +101,7 @@
<AdvancedOnly>
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: 'Manage Your API Tokens', to: '/user/profile/api-tokens' }"
:link="{ text: $tc('profile.manage-your-api-tokens'), to: '/user/profile/api-tokens' }"
:image="require('~/static/svgs/manage-api-tokens.svg')"
>
<template #title> {{ $t('settings.token.api-tokens') }} </template>
@ -120,7 +120,7 @@
<v-row tag="section">
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: 'Group Settings', to: '/group' }"
:link="{ text: $tc('profile.group-settings'), to: '/group' }"
:image="require('~/static/svgs/manage-group-settings.svg')"
>
<template #title> {{ $t('profile.group-settings') }} </template>
@ -129,7 +129,7 @@
</v-col>
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: 'Manage Cookbooks', to: '/group/cookbooks' }"
:link="{ text: $tc('profile.manage-cookbooks'), to: '/group/cookbooks' }"
:image="require('~/static/svgs/manage-cookbooks.svg')"
>
<template #title> {{ $t('sidebar.cookbooks') }} </template>
@ -138,7 +138,7 @@
</v-col>
<v-col v-if="user.canManage" cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: 'Manage Members', to: '/group/members' }"
:link="{ text: $tc('profile.manage-members'), to: '/group/members' }"
:image="require('~/static/svgs/manage-members.svg')"
>
<template #title> {{ $t('profile.members') }} </template>
@ -148,7 +148,7 @@
<AdvancedOnly>
<v-col v-if="user.advanced" cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: 'Manage Webhooks', to: '/group/webhooks' }"
:link="{ text: $tc('profile.manage-webhooks'), to: '/group/webhooks' }"
:image="require('~/static/svgs/manage-webhooks.svg')"
>
<template #title> {{ $t('settings.webhooks.webhooks') }} </template>
@ -159,7 +159,7 @@
<AdvancedOnly>
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: 'Manage Notifiers', to: '/group/notifiers' }"
:link="{ text: $tc('profile.manage-notifiers'), to: '/group/notifiers' }"
:image="require('~/static/svgs/manage-notifiers.svg')"
>
<template #title> {{ $t('profile.notifiers') }} </template>
@ -170,7 +170,7 @@
<AdvancedOnly>
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: 'Manage Data', to: '/group/data/foods' }"
:link="{ text: $tc('profile.manage-data'), to: '/group/data/foods' }"
:image="require('~/static/svgs/manage-recipes.svg')"
>
<template #title> {{ $t('profile.manage-data') }} </template>
@ -181,7 +181,7 @@
<AdvancedOnly>
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
:link="{ text: 'Manage Data Migrations', to: '/group/migrations' }"
:link="{ text: $tc('profile.manage-data-migrations'), to: '/group/migrations' }"
:image="require('~/static/svgs/manage-data-migrations.svg')"
>
<template #title>{{ $t('profile.data-migrations') }} </template>