1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-05 21:45:25 +02:00

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

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