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

Localize hard-coded texts (#2044)

* feat(lang): localize some views

* feat(lang): an attempt at localizing vuetify (WIP)

* feat(lang): localized some more screens

* feat(lang): localized some more screens again

* feat(lang): hack to localize vuetify

* feat(lang): localize data management pages

* fix linting errors

---------

Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
sephrat 2023-01-29 02:39:51 +01:00 committed by GitHub
parent 754d4c3937
commit f8b8680b45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 695 additions and 393 deletions

View file

@ -4,10 +4,8 @@
<template #header>
<v-img max-height="100" max-width="100" :src="require('~/static/svgs/manage-cookbooks.svg')"></v-img>
</template>
<template #title> Cookbooks </template>
Cookbooks are another way to organize recipes by creating cross sections of recipes and tags. Creating a cookbook
will add an entry to the side-bar and all the recipes with the tags and categories chosen will be displayed in the
cookbook.
<template #title> {{ $t('cookbook.cookbooks') }} </template>
{{ $t('cookbook.description') }}
</BasePageTitle>
<BaseButton create @click="actions.createOne()" />
@ -34,35 +32,34 @@
</v-expansion-panel-header>
<v-expansion-panel-content>
<v-card-text v-if="cookbooks">
<v-text-field v-model="cookbooks[index].name" label="Cookbook Name"></v-text-field>
<v-textarea v-model="cookbooks[index].description" auto-grow :rows="2" label="Description"></v-textarea>
<v-text-field v-model="cookbooks[index].name" :label="$t('cookbook.cookbook-name')"></v-text-field>
<v-textarea v-model="cookbooks[index].description" auto-grow :rows="2" :label="$t('recipe.description')"></v-textarea>
<RecipeOrganizerSelector v-model="cookbooks[index].categories" selector-type="categories" />
<RecipeOrganizerSelector v-model="cookbooks[index].tags" selector-type="tags" />
<RecipeOrganizerSelector v-model="cookbooks[index].tools" selector-type="tools" />
<v-switch v-model="cookbooks[index].public" hide-details single-line>
<template #label>
Public Cookbook
{{ $t('cookbook.public-cookbook') }}
<HelpIcon small right class="ml-2">
Public Cookbooks can be shared with non-mealie users and will be displayed on your groups page.
{{ $t('cookbook.public-cookbook-description') }}
</HelpIcon>
</template>
</v-switch>
<div class="mt-4">
<h3 class="text-subtitle-1 d-flex align-center mb-0 pb-0">
Filter Options
{{ $t('cookbook.filter-options') }}
<HelpIcon right small class="ml-2">
When require all is selected the cookbook will only include recipes that have all of the items
selected. This applies to each subset of selectors and not a cross section of the selected items.
{{ $t('cookbook.filter-options-description') }}
</HelpIcon>
</h3>
<v-switch v-model="cookbooks[index].requireAllCategories" class="mt-0" hide-details single-line>
<template #label> Require All Categories </template>
<template #label> {{ $t('cookbook.require-all-categories') }} </template>
</v-switch>
<v-switch v-model="cookbooks[index].requireAllTags" hide-details single-line>
<template #label> Require All Tags </template>
<template #label> {{ $t('cookbook.require-all-tags') }} </template>
</v-switch>
<v-switch v-model="cookbooks[index].requireAllTools" hide-details single-line>
<template #label> Require All Tools </template>
<template #label> {{ $t('cookbook.require-all-tools') }} </template>
</v-switch>
</div>
</v-card-text>

View file

@ -4,8 +4,8 @@
<template #header>
<v-img max-height="175" max-width="175" :src="require('~/static/svgs/manage-recipes.svg')"></v-img>
</template>
<template #title> Data Management </template>
Select which data set you want to make changes to.
<template #title> {{ $t('data-pages.data-management') }} </template>
{{ $t('data-pages.data-management-description') }}
<BannerExperimental class="mt-5"></BannerExperimental>
<template #content>
<div>
@ -13,28 +13,7 @@
:btn-text="buttonText"
mode="link"
rounded
:items="[
{
text: 'Recipes',
value: 'new',
to: '/group/data/recipes',
},
{
text: 'Foods',
value: 'url',
to: '/group/data/foods',
},
{
text: 'Units',
value: 'new',
to: '/group/data/units',
},
{
text: 'Labels',
value: 'new',
to: '/group/data/labels',
},
]"
:items="DATA_TYPE_OPTIONS"
>
</BaseOverflowButton>
</div>
@ -49,7 +28,7 @@
</template>
<script lang="ts">
import { computed, defineComponent, useRoute } from "@nuxtjs/composition-api";
import { computed, defineComponent, useContext, useRoute } from "@nuxtjs/composition-api";
export default defineComponent({
props: {
@ -59,13 +38,37 @@ export default defineComponent({
},
},
setup() {
const { i18n } = useContext();
const buttonLookup: { [key: string]: string } = {
recipes: "Recipes",
foods: "Foods",
units: "Units",
labels: "Labels",
recipes: i18n.tc("general.recipes"),
foods: i18n.tc("general.foods"),
units: i18n.tc("general.units"),
labels: i18n.tc("data-pages.labels.labels"),
};
const DATA_TYPE_OPTIONS = [
{
text: i18n.t("general.recipes"),
value: "new",
to: "/group/data/recipes",
},
{
text: i18n.t("general.foods"),
value: "url",
to: "/group/data/foods",
},
{
text: i18n.t("general.units"),
value: "new",
to: "/group/data/units",
},
{
text: i18n.t("data-pages.labels.labels"),
value: "new",
to: "/group/data/labels",
},
];
const route = useRoute();
const buttonText = computed(() => {
@ -75,15 +78,18 @@ export default defineComponent({
return buttonLookup[last];
}
return "Select Data";
return i18n.tc("data-pages.select-data");
});
return {
buttonText,
DATA_TYPE_OPTIONS
};
},
head: {
title: "Data Management",
head() {
return {
title: this.$tc("data-pages.data-management"),
};
},
});
</script>

View file

@ -1,13 +1,13 @@
<template>
<div>
<!-- Merge Dialog -->
<BaseDialog v-model="mergeDialog" :icon="$globals.icons.foods" title="Combine Food" @confirm="mergeFoods">
<BaseDialog v-model="mergeDialog" :icon="$globals.icons.foods" :title="$t('data-pages.foods.combine-food')" @confirm="mergeFoods">
<v-card-text>
<div>
{{ $t("data-pages.foods.merge-dialog-text") }}
</div>
<v-autocomplete v-model="fromFood" return-object :items="foods" item-text="name" label="Source Food" />
<v-autocomplete v-model="toFood" return-object :items="foods" item-text="name" label="Target Food" />
<v-autocomplete v-model="fromFood" return-object :items="foods" item-text="name" :label="$t('data-pages.foods.source-food')" />
<v-autocomplete v-model="toFood" return-object :items="foods" item-text="name" :label="$t('data-pages.foods.target-food')" />
<template v-if="canMerge && fromFood && toFood">
<div class="text-center">
@ -32,7 +32,7 @@
v-model="locale"
:items="locales"
item-text="name"
label="Select Language"
:label="$t('data-pages.select-language')"
class="my-3"
hide-details
outlined
@ -58,7 +58,7 @@
<BaseDialog
v-model="createDialog"
:icon="$globals.icons.foods"
title="Create Food"
:title="$t('data-pages.foods.create-food')"
:submit-text="$tc('general.save')"
@submit="createFood"
>
@ -67,17 +67,17 @@
<v-text-field
v-model="createTarget.name"
autofocus
label="Name"
:label="$t('general.name')"
:rules="[validators.required]"
></v-text-field>
<v-text-field v-model="createTarget.description" label="Description"></v-text-field>
<v-text-field v-model="createTarget.description" :label="$t('recipe.description')"></v-text-field>
<v-autocomplete
v-model="createTarget.labelId"
clearable
:items="allLabels"
item-value="id"
item-text="name"
label="Food Label"
:label="$t('data-pages.foods.food-label')"
>
</v-autocomplete>
</v-form> </v-card-text
@ -87,21 +87,21 @@
<BaseDialog
v-model="editDialog"
:icon="$globals.icons.foods"
title="Edit Food"
:title="$t('data-pages.foods.edit-food')"
:submit-text="$tc('general.save')"
@submit="editSaveFood"
>
<v-card-text v-if="editTarget">
<v-form ref="domEditFoodForm">
<v-text-field v-model="editTarget.name" label="Name" :rules="[validators.required]"></v-text-field>
<v-text-field v-model="editTarget.description" label="Description"></v-text-field>
<v-text-field v-model="editTarget.name" :label="$t('general.name')" :rules="[validators.required]"></v-text-field>
<v-text-field v-model="editTarget.description" :label="$t('recipe.description')"></v-text-field>
<v-autocomplete
v-model="editTarget.labelId"
clearable
:items="allLabels"
item-value="id"
item-text="name"
label="Food Label"
:label="$t('data-pages.foods.food-label')"
>
</v-autocomplete>
</v-form> </v-card-text
@ -121,7 +121,7 @@
</BaseDialog>
<!-- Recipe Data Table -->
<BaseCardSectionTitle :icon="$globals.icons.foods" section title="Food Data"> </BaseCardSectionTitle>
<BaseCardSectionTitle :icon="$globals.icons.foods" section :title="$tc('data-pages.foods.food-data')"> </BaseCardSectionTitle>
<CrudTable
:table-config="tableConfig"
:headers.sync="tableHeaders"
@ -154,7 +154,7 @@
</template>
<script lang="ts">
import { defineComponent, onMounted, ref, computed } from "@nuxtjs/composition-api";
import { defineComponent, onMounted, ref, computed, useContext } from "@nuxtjs/composition-api";
import type { LocaleObject } from "@nuxtjs/i18n";
import { validators } from "~/composables/use-validators";
import { useUserApi } from "~/composables/api";
@ -168,28 +168,29 @@ export default defineComponent({
components: { MultiPurposeLabel },
setup() {
const userApi = useUserApi();
const { i18n } = useContext();
const tableConfig = {
hideColumns: true,
canExport: true,
};
const tableHeaders = [
{
text: "Id",
text: i18n.tc("general.id"),
value: "id",
show: false,
},
{
text: "Name",
text: i18n.tc("general.name"),
value: "name",
show: true,
},
{
text: "Description",
text: i18n.tc("recipe.description"),
value: "description",
show: true,
},
{
text: "Label",
text: i18n.tc("shopping-list.label"),
value: "label",
show: true,
},
@ -297,7 +298,7 @@ export default defineComponent({
const seedDialog = ref(false);
const locale = ref("");
const { locales: LOCALES, locale: currentLocale, i18n } = useLocales();
const { locales: LOCALES, locale: currentLocale } = useLocales();
onMounted(() => {
locale.value = currentLocale.value;

View file

@ -1,7 +1,7 @@
<template>
<div>
<!-- Create New Dialog -->
<BaseDialog v-model="state.createDialog" title="New Label" :icon="$globals.icons.tags" @submit="createLabel">
<BaseDialog v-model="state.createDialog" :title="$t('data-pages.labels.new-label')" :icon="$globals.icons.tags" @submit="createLabel">
<v-card-text>
<MultiPurposeLabel :label="createLabelData" />
@ -16,7 +16,7 @@
<BaseDialog
v-model="state.editDialog"
:icon="$globals.icons.tags"
title="Edit Label"
:title="$t('data-pages.labels.edit-label')"
:submit-text="$tc('general.save')"
@submit="editSaveLabel"
>
@ -57,7 +57,7 @@
v-model="locale"
:items="locales"
item-text="name"
label="Select Language"
:label="$t('data-pages.select-language')"
class="my-3"
hide-details
outlined
@ -80,7 +80,7 @@
</BaseDialog>
<!-- Recipe Data Table -->
<BaseCardSectionTitle :icon="$globals.icons.tags" section title="Labels"> </BaseCardSectionTitle>
<BaseCardSectionTitle :icon="$globals.icons.tags" section :title="$tc('data-pages.labels.labels')"> </BaseCardSectionTitle>
<CrudTable
:table-config="tableConfig"
:headers.sync="tableHeaders"
@ -103,7 +103,7 @@
<template #button-bottom>
<BaseButton @click="seedDialog = true">
<template #icon> {{ $globals.icons.database }} </template>
Seed
{{ $t('data-pages.seed') }}
</BaseButton>
</template>
</CrudTable>
@ -111,7 +111,7 @@
</template>
<script lang="ts">
import { defineComponent, onMounted, reactive, ref } from "@nuxtjs/composition-api";
import { defineComponent, onMounted, reactive, ref, useContext } from "@nuxtjs/composition-api";
import type { LocaleObject } from "@nuxtjs/i18n";
import { validators } from "~/composables/use-validators";
import { useUserApi } from "~/composables/api";
@ -124,18 +124,19 @@ export default defineComponent({
components: { MultiPurposeLabel },
setup() {
const userApi = useUserApi();
const { i18n } = useContext();
const tableConfig = {
hideColumns: true,
canExport: true,
};
const tableHeaders = [
{
text: "Id",
text: i18n.t("general.id"),
value: "id",
show: false,
},
{
text: "Name",
text: i18n.t("general.name"),
value: "name",
show: true,
},
@ -205,7 +206,7 @@ export default defineComponent({
const seedDialog = ref(false);
const locale = ref("");
const { locales: LOCALES, locale: currentLocale, i18n } = useLocales();
const { locales: LOCALES, locale: currentLocale } = useLocales();
onMounted(() => {
locale.value = currentLocale.value;

View file

@ -3,12 +3,12 @@
<!-- Export Purge Confirmation Dialog -->
<BaseDialog
v-model="purgeExportsDialog"
title="Purge Exports"
:title="$t('data-pages.recipes.purge-exports')"
color="error"
:icon="$globals.icons.alertCircle"
@confirm="purgeExports()"
>
<v-card-text> Are you sure you want to delete all export data? </v-card-text>
<v-card-text> {{ $t('data-pages.recipes.are-you-sure-you-want-to-delete-all-export-data') }} </v-card-text>
</BaseDialog>
<!-- Base Dialog Object -->
@ -18,7 +18,7 @@
width="650px"
:icon="dialog.icon"
:title="dialog.title"
submit-text="Submit"
:submit-text="$t('general.submit')"
@submit="dialog.callback"
>
<v-card-text v-if="dialog.mode == MODES.tag">
@ -28,7 +28,7 @@
<RecipeOrganizerSelector v-model="toSetCategories" selector-type="categories" />
</v-card-text>
<v-card-text v-else-if="dialog.mode == MODES.delete">
<p class="h4">Are you sure you want to delete the following recipes? This action cannot be undone.</p>
<p class="h4">{{ $t('data-pages.recipes.confirm-delete-recipes') }}</p>
<v-card outlined>
<v-virtual-scroll height="400" item-height="25" :items="selected">
<template #default="{ item }">
@ -42,7 +42,7 @@
</v-card>
</v-card-text>
<v-card-text v-else-if="dialog.mode == MODES.export">
<p class="h4">The following recipes ({{ selected.length }}) will be exported.</p>
<p class="h4">{{ $t('data-pages.recipes.the-following-recipes-selected-length-will-be-exported', [selected.length]) }}</p>
<v-card outlined>
<v-virtual-scroll height="400" item-height="25" :items="selected">
<template #default="{ item }">
@ -56,20 +56,19 @@
</v-card>
</v-card-text>
<v-card-text v-else-if="dialog.mode == MODES.updateSettings" class="px-12">
<p>Settings chosen here, excluding the locked option, will be applied to all selected recipes.</p>
<p>{{ $t('data-pages.recipes.settings-chosen-explanation') }}</p>
<div class="mx-auto">
<RecipeSettingsSwitches v-model="recipeSettings" />
</div>
<p class="text-center mb-0">
<i>{{ selected.length }} recipe(s) settings will be updated.</i>
<i>{{ $t('data-pages.recipes.selected-length-recipe-s-settings-will-be-updated', [selected.length]) }}</i>
</p>
</v-card-text>
</BaseDialog>
<section>
<!-- Recipe Data Table -->
<BaseCardSectionTitle :icon="$globals.icons.primary" title="Recipe Data">
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.
<BaseCardSectionTitle :icon="$globals.icons.primary" :title="$tc('data-pages.recipes.recipe-data')">
{{ $t('data-pages.recipes.recipe-data-description') }}
</BaseCardSectionTitle>
<v-card-actions class="mt-n5 mb-1">
<v-menu offset-y bottom nudge-bottom="6" :close-on-content-click="false">
@ -78,12 +77,12 @@
<v-icon left>
{{ $globals.icons.cog }}
</v-icon>
Columns
{{ $t('data-pages.columns') }}
</v-btn>
</template>
<v-card>
<v-card-title class="py-2">
<div>Recipe Columns</div>
<div>{{ $t('data-pages.recipes.recipe-columns') }}</div>
</v-card-title>
<v-divider class="mx-2"></v-divider>
<v-card-text class="mt-n5">
@ -113,7 +112,7 @@
>
</BaseOverflowButton>
<p v-if="selected.length > 0" class="text-caption my-auto ml-5">Selected: {{ selected.length }}</p>
<p v-if="selected.length > 0" class="text-caption my-auto ml-5">{{ $tc('general.selected-count', selected.length) }}</p>
</v-card-actions>
<v-card>
<RecipeDataTable v-model="selected" :loading="loading" :recipes="allRecipes" :show-headers="headers" />
@ -134,7 +133,7 @@
<template #icon>
{{ $globals.icons.database }}
</template>
Export All
{{ $t('general.export-all') }}
</BaseButton>
</v-card-actions>
</v-card>
@ -142,9 +141,8 @@
<section class="mt-10">
<!-- Downloads Data Table -->
<BaseCardSectionTitle :icon="$globals.icons.database" section title="Data Exports">
This section provides links to available exports that are ready to download. These exports do expire, so be sure
to grab them while they're still available.
<BaseCardSectionTitle :icon="$globals.icons.database" section :title="$tc('data-pages.recipes.data-exports')">
{{ $t('data-pages.recipes.data-exports-description') }}
</BaseCardSectionTitle>
<v-card-actions class="mt-n5 mb-1">
<BaseButton delete @click="purgeExportsDialog = true"> </BaseButton>
@ -182,7 +180,7 @@ export default defineComponent({
setup() {
const { getAllRecipes, refreshRecipes } = useRecipes(true, true);
const { $globals } = useContext();
const { $globals, i18n } = useContext();
const selected = ref<Recipe[]>([]);
@ -204,39 +202,39 @@ export default defineComponent({
});
const headerLabels = {
id: "Id",
owner: "Owner",
tags: "Tags",
categories: "Categories",
tools: "Tools",
recipeYield: "Recipe Yield",
dateAdded: "Date Added",
id: i18n.t("general.id"),
owner: i18n.t("general.owner"),
tags: i18n.t("tag.tags"),
categories: i18n.t("recipe.categories"),
tools: i18n.t("tool.tools"),
recipeYield: i18n.t("recipe.recipe-yield"),
dateAdded: i18n.t("general.date-added"),
};
const actions: MenuItem[] = [
{
icon: $globals.icons.database,
text: "Export",
text: i18n.tc("export.export"),
event: "export-selected",
},
{
icon: $globals.icons.tags,
text: "Tag",
text: i18n.tc("data-pages.recipes.tag"),
event: "tag-selected",
},
{
icon: $globals.icons.tags,
text: "Categorize",
text: i18n.tc("data-pages.recipes.categorize"),
event: "categorize-selected",
},
{
icon: $globals.icons.cog,
text: "Update Settings",
text: i18n.tc("data-pages.recipes.update-settings"),
event: "update-settings",
},
{
icon: $globals.icons.delete,
text: "Delete",
text: i18n.tc("general.delete"),
event: "delete-selected",
},
];
@ -352,7 +350,7 @@ export default defineComponent({
const dialog = reactive({
state: false,
title: "Tag Recipes",
title: i18n.t("data-pages.recipes.tag-recipes"),
mode: MODES.tag,
tag: "",
callback: () => {
@ -364,11 +362,11 @@ export default defineComponent({
function openDialog(mode: MODES) {
const titles: Record<MODES, string> = {
[MODES.tag]: "Tag Recipes",
[MODES.category]: "Categorize Recipes",
[MODES.export]: "Export Recipes",
[MODES.delete]: "Delete Recipes",
[MODES.updateSettings]: "Update Settings",
[MODES.tag]: i18n.tc("data-pages.recipes.tag-recipes"),
[MODES.category]: i18n.tc("data-pages.recipes.categorize-recipes"),
[MODES.export]: i18n.tc("data-pages.recipes.export-recipes"),
[MODES.delete]: i18n.tc("data-pages.recipes.delete-recipes"),
[MODES.updateSettings]: i18n.tc("data-pages.recipes.update-settings"),
};
const callbacks: Record<MODES, () => Promise<void>> = {
@ -420,7 +418,7 @@ export default defineComponent({
},
head() {
return {
title: "Recipe Data",
title: this.$tc("data-pages.recipes.recipe-data"),
};
},
});

View file

@ -1,23 +1,25 @@
<template>
<div>
<!-- Merge Dialog -->
<BaseDialog v-model="mergeDialog" :icon="$globals.icons.units" title="Combine Unit" @confirm="mergeUnits">
<BaseDialog v-model="mergeDialog" :icon="$globals.icons.units" :title="$t('data-pages.units.combine-unit')" @confirm="mergeUnits">
<v-card-text>
Combining the selected units will merge the Source Unit and Target Unit into a single unit. The
<strong> Source Unit will be deleted </strong> and all of the references to the Source Unit will be updated to
point to the Target Unit.
<i18n path="data-pages.units.combine-unit-description">
<template #source-unit-will-be-deleted>
<strong> {{ $t('data-pages.recipes.source-unit-will-be-deleted') }} </strong>
</template>
</i18n>
<v-autocomplete v-model="fromUnit" return-object :items="units" item-text="id" label="Source Unit">
<v-autocomplete v-model="fromUnit" return-object :items="units" item-text="id" :label="$t('data-pages.units.source-unit')">
<template #selection="{ item }"> {{ item.name }}</template>
<template #item="{ item }"> {{ item.name }} </template>
</v-autocomplete>
<v-autocomplete v-model="toUnit" return-object :items="units" item-text="id" label="Target Unit">
<v-autocomplete v-model="toUnit" return-object :items="units" item-text="id" :label="$t('data-pages.units.target-unit')">
<template #selection="{ item }"> {{ item.name }}</template>
<template #item="{ item }"> {{ item.name }} </template>
</v-autocomplete>
<template v-if="canMerge && fromUnit && toUnit">
<div class="text-center">Merging {{ fromUnit.name }} into {{ toUnit.name }}</div>
<div class="text-center">{{ $t('data-pages.units.merging-unit-into-unit', [fromUnit.name, toUnit.name]) }}</div>
</template>
</v-card-text>
</BaseDialog>
@ -26,7 +28,7 @@
<BaseDialog
v-model="createDialog"
:icon="$globals.icons.units"
title="Create Unit"
:title="$t('data-pages.units.create-unit')"
:submit-text="$tc('general.save')"
@submit="createUnit"
>
@ -35,13 +37,13 @@
<v-text-field
v-model="createTarget.name"
autofocus
label="Name"
:label="$t('general.name')"
:rules="[validators.required]"
></v-text-field>
<v-text-field v-model="createTarget.abbreviation" label="Abbreviation"></v-text-field>
<v-text-field v-model="createTarget.description" label="Description"></v-text-field>
<v-checkbox v-model="createTarget.fraction" hide-details label="Display as Fraction"></v-checkbox>
<v-checkbox v-model="createTarget.useAbbreviation" hide-details label="Use Abbreviation"></v-checkbox>
<v-text-field v-model="createTarget.abbreviation" :label="$t('data-pages.units.abbreviation')"></v-text-field>
<v-text-field v-model="createTarget.description" :label="$t('data-pages.units.description')"></v-text-field>
<v-checkbox v-model="createTarget.fraction" hide-details :label="$t('data-pages.units.display-as-fraction')"></v-checkbox>
<v-checkbox v-model="createTarget.useAbbreviation" hide-details :label="$t('data-pages.units.use-abbreviation')"></v-checkbox>
</v-form>
</v-card-text>
</BaseDialog>
@ -50,17 +52,17 @@
<BaseDialog
v-model="editDialog"
:icon="$globals.icons.units"
title="Edit Unit"
:title="$t('data-pages.units.edit-unit')"
:submit-text="$tc('general.save')"
@submit="editSaveUnit"
>
<v-card-text v-if="editTarget">
<v-form ref="domEditUnitForm">
<v-text-field v-model="editTarget.name" label="Name" :rules="[validators.required]"></v-text-field>
<v-text-field v-model="editTarget.abbreviation" label="Abbreviation"></v-text-field>
<v-text-field v-model="editTarget.description" label="Description"></v-text-field>
<v-checkbox v-model="editTarget.fraction" hide-details label="Display as Fraction"></v-checkbox>
<v-checkbox v-model="editTarget.useAbbreviation" hide-details label="Use Abbreviation"></v-checkbox>
<v-text-field v-model="editTarget.name" :label="$t('general.name')" :rules="[validators.required]"></v-text-field>
<v-text-field v-model="editTarget.abbreviation" :label="$t('data-pages.units.abbreviation')"></v-text-field>
<v-text-field v-model="editTarget.description" :label="$t('data-pages.units.description')"></v-text-field>
<v-checkbox v-model="editTarget.fraction" hide-details :label="$t('data-pages.units.display-as-fraction')"></v-checkbox>
<v-checkbox v-model="editTarget.useAbbreviation" hide-details :label="$t('data-pages.units.use-abbreviation')"></v-checkbox>
</v-form>
</v-card-text>
</BaseDialog>
@ -93,7 +95,7 @@
v-model="locale"
:items="locales"
item-text="name"
label="Select Language"
:label="$t('data-pages.select-language')"
class="my-3"
hide-details
outlined
@ -116,7 +118,7 @@
</BaseDialog>
<!-- Recipe Data Table -->
<BaseCardSectionTitle :icon="$globals.icons.units" section title="Unit Data"> </BaseCardSectionTitle>
<BaseCardSectionTitle :icon="$globals.icons.units" section :title="$tc('data-pages.units.unit-data')"> </BaseCardSectionTitle>
<CrudTable
:table-config="tableConfig"
:headers.sync="tableHeaders"
@ -155,7 +157,7 @@
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
import { computed, defineComponent, onMounted, ref, useContext } from "@nuxtjs/composition-api";
import type { LocaleObject } from "@nuxtjs/i18n";
import { validators } from "~/composables/use-validators";
import { useUserApi } from "~/composables/api";
@ -167,38 +169,39 @@ import { VForm } from "~/types/vuetify";
export default defineComponent({
setup() {
const userApi = useUserApi();
const { i18n } = useContext();
const tableConfig = {
hideColumns: true,
canExport: true,
};
const tableHeaders = [
{
text: "Id",
text: i18n.t("general.id"),
value: "id",
show: false,
},
{
text: "Name",
text: i18n.t("general.name"),
value: "name",
show: true,
},
{
text: "Abbreviation",
text: i18n.t("data-pages.units.abbreviation"),
value: "abbreviation",
show: true,
},
{
text: "Use Abbv.",
text: i18n.t("data-pages.units.use-abbv"),
value: "useAbbreviation",
show: true,
},
{
text: "Description",
text: i18n.t("data-pages.units.description"),
value: "description",
show: false,
},
{
text: "Fraction",
text: i18n.t("data-pages.units.fraction"),
value: "fraction",
show: true,
},
@ -304,7 +307,7 @@ export default defineComponent({
const seedDialog = ref(false);
const locale = ref("");
const { locales: LOCALES, locale: currentLocale, i18n } = useLocales();
const { locales: LOCALES, locale: currentLocale } = useLocales();
onMounted(() => {
locale.value = currentLocale.value;

View file

@ -4,16 +4,16 @@
<template #header>
<v-img max-height="100" max-width="100" :src="require('~/static/svgs/manage-group-settings.svg')"></v-img>
</template>
<template #title> Group Settings </template>
These items are shared within your group. Editing one of them will change it for the whole group!
<template #title> {{ $t('profile.group-settings') }} </template>
{{ $t('profile.group-description') }}
</BasePageTitle>
<section v-if="group">
<BaseCardSectionTitle class="mt-10" title="Group Preferences"></BaseCardSectionTitle>
<BaseCardSectionTitle class="mt-10" :title="$tc('group.group-preferences')"></BaseCardSectionTitle>
<v-checkbox
v-model="group.preferences.privateGroup"
class="mt-n4"
label="Private Group"
:label="$t('group.private-group')"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-select
@ -28,45 +28,44 @@
</section>
<section v-if="group">
<BaseCardSectionTitle class="mt-10" title="Default Recipe Preferences">
These are the default settings when a new recipe is created in your group. These can be changed for individual
recipes in the recipe settings menu.
<BaseCardSectionTitle class="mt-10" :title="$tc('group.default-recipe-preferences')">
{{ $t('group.default-recipe-preferences-description') }}
</BaseCardSectionTitle>
<v-checkbox
v-model="group.preferences.recipePublic"
class="mt-n4"
label="Allow users outside of your group to see your recipes"
:label="$t('group.allow-users-outside-of-your-group-to-see-your-recipes')"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-checkbox
v-model="group.preferences.recipeShowNutrition"
class="mt-n4"
label="Show nutrition information"
:label="$t('group.show-nutrition-information')"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-checkbox
v-model="group.preferences.recipeShowAssets"
class="mt-n4"
label="Show recipe assets"
:label="$t('group.show-recipe-assets')"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-checkbox
v-model="group.preferences.recipeLandscapeView"
class="mt-n4"
label="Default to landscape view"
:label="$t('group.default-to-landscape-view')"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-checkbox
v-model="group.preferences.recipeDisableComments"
class="mt-n4"
label="Disable users from commenting on recipes"
:label="$t('group.disable-users-from-commenting-on-recipes')"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-checkbox
v-model="group.preferences.recipeDisableAmount"
class="mt-n4"
label="Disable organizing recipe ingredients by units and food"
:label="$t('group.disable-organizing-recipe-ingredients-by-units-and-food')"
@change="groupActions.updatePreferences()"
></v-checkbox>
</section>

View file

@ -35,25 +35,25 @@
<v-date-picker v-model="newMeal.date" :first-day-of-week="firstDayOfWeek" no-title @input="pickerMenu = false"></v-date-picker>
</v-menu>
<v-card-text>
<v-select v-model="newMeal.entryType" :return-object="false" :items="planTypeOptions" label="Entry Type">
<v-select v-model="newMeal.entryType" :return-object="false" :items="planTypeOptions" :label="$t('recipe.entry-type')">
</v-select>
<v-autocomplete
v-if="!dialog.note"
v-model="newMeal.recipeId"
label="Meal Recipe"
:label="$t('meal-plan.meal-recipe')"
:items="allRecipes"
item-text="name"
item-value="id"
:return-object="false"
></v-autocomplete>
<template v-else>
<v-text-field v-model="newMeal.title" label="Meal Title"> </v-text-field>
<v-textarea v-model="newMeal.text" rows="2" label="Meal Note"> </v-textarea>
<v-text-field v-model="newMeal.title" :label="$t('meal-plan.meal-title')"> </v-text-field>
<v-textarea v-model="newMeal.text" rows="2" :label="$t('meal-plan.meal-note')"> </v-textarea>
</template>
</v-card-text>
<v-card-actions class="my-0 py-0">
<v-switch v-model="dialog.note" class="mt-n3" label="Note Only"></v-switch>
<v-switch v-model="dialog.note" class="mt-n3" :label="$t('meal-plan.note-only')"></v-switch>
</v-card-actions>
</v-card-text>
</BaseDialog>
@ -71,8 +71,8 @@
</div>
</div>
<div class="d-flex align-center justify-space-between">
<v-switch v-model="edit" label="Editor"></v-switch>
<ButtonLink :icon="$globals.icons.calendar" to="/group/mealplan/settings" text="Settings" />
<v-switch v-model="edit" :label="$t('meal-plan.editor')"></v-switch>
<ButtonLink :icon="$globals.icons.calendar" to="/group/mealplan/settings" :text="$tc('general.settings')" />
</div>
<v-row class="">
<v-col
@ -174,7 +174,7 @@
:buttons="[
{
icon: $globals.icons.diceMultiple,
text: 'Random Meal',
text: $tc('meal-plan.random-meal'),
event: 'random',
children: [
{
@ -185,19 +185,19 @@
{
icon: $globals.icons.diceMultiple,
text: 'Lunch',
text: $tc('meal-plan.lunch'),
event: 'randomLunch',
},
],
},
{
icon: $globals.icons.potSteam,
text: 'Random Dinner',
text: $tc('meal-plan.random-dinner'),
event: 'randomDinner',
},
{
icon: $globals.icons.bowlMixOutline,
text: 'Random Side',
text: $tc('meal-plan.random-side'),
event: 'randomSide',
},
{

View file

@ -4,20 +4,15 @@
<template #header>
<v-img max-height="100" max-width="100" :src="require('~/static/svgs/manage-cookbooks.svg')"></v-img>
</template>
<template #title> Meal Plan Rules </template>
You can create rules for auto selecting recipes for you meal plans. These rules are used by the server to
determine the random pool of recipes to select from when creating meal plans. Note that if rules have the same
day/type constraints then the categories of the rules will be merged. In practice, it's unnecessary to create
duplicate rules, but it's possible to do so.
<template #title> {{ $t('meal-plan.meal-plan-rules') }} </template>
{{ $t('meal-plan.meal-plan-rules-description') }}
</BasePageTitle>
<v-card>
<v-card-title class="headline"> New Rule </v-card-title>
<v-card-title class="headline"> {{ $t('meal-plan.new-rule') }} </v-card-title>
<v-divider class="mx-2"></v-divider>
<v-card-text>
When creating a new rule for a meal plan you can restrict the rule to be applicable for a specific day of the
week and/or a specific type of meal. To apply a rule to all days or all meal types you can set the rule to "Any"
which will apply it to all the possible values for the day and/or meal type.
{{ $t('meal-plan.new-rule-description') }}
<GroupMealPlanRuleForm
class="mt-2"
@ -33,13 +28,13 @@
</v-card>
<section>
<BaseCardSectionTitle class="mt-10" title="Recipe Rules" />
<BaseCardSectionTitle class="mt-10" :title="$tc('meal-plan.recipe-rules')" />
<div>
<div v-for="(rule, idx) in allRules" :key="rule.id">
<v-card class="my-2 left-border">
<v-card-title class="headline pb-1">
{{ rule.day === "unset" ? "Applies to all days" : `Applies on ${rule.day}s` }}
{{ rule.entryType === "unset" ? "for all meal types" : ` for ${rule.entryType} meal types` }}
{{ rule.day === "unset" ? $t('meal-plan.applies-to-all-days') : $t('meal-plan.applies-on-days', [rule.day]) }}
{{ rule.entryType === "unset" ? $t('meal-plan.for-all-meal-types') : $t('meal-plan.for-type-meal-types', [rule.entryType]) }}
<span class="ml-auto">
<BaseButtonGroup
:buttons="[
@ -91,7 +86,7 @@
</template>
<script lang="ts">
import { defineComponent, ref, useAsync } from "@nuxtjs/composition-api";
import { defineComponent, ref, useAsync, useContext } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
import { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan";
import GroupMealPlanRuleForm from "~/components/Domain/Group/GroupMealPlanRuleForm.vue";
@ -182,8 +177,10 @@ export default defineComponent({
toggleEditState,
};
},
head: {
title: "Meal Plan Settings",
head() {
return {
title: this.$tc("meal-plan.meal-plan-settings"),
};
},
});
</script>

View file

@ -4,13 +4,18 @@
<template #header>
<v-img max-height="125" max-width="125" :src="require('~/static/svgs/manage-members.svg')"></v-img>
</template>
<template #title> Manage Members </template>
Manage the permissions of the members in your groups. <b> Manage </b> allows the user to access the
data-management page <b> Invite </b> allows the user to generate invitation links for other users. Group owners
cannot change their own permissions.
<template #title> {{ $t('group.manage-members') }} </template>
<i18n path="group.manage-members-description">
<template #manage>
<b>{{ $t('group.manage') }}</b>
</template>
<template #invite>
<b>{{ $t('group.invite') }}</b>
</template>
</i18n>
</BasePageTitle>
<v-container class="mt-4 d-flex justify-start">
<v-btn outlined rounded to="/user/profile/edit"> Looking to Update Your Profile? </v-btn>
<v-btn outlined rounded to="/user/profile/edit"> {{ $t('group.looking-to-update-your-profile') }} </v-btn>
</v-container>
<v-data-table
:headers="headers"
@ -24,7 +29,7 @@
<UserAvatar :user-id="item.id" />
</template>
<template #item.admin="{ item }">
{{ item.admin ? "Admin" : "User" }}
{{ item.admin ? $t('user.admin') : $t('user.user') }}
</template>
<template #item.manage="{ item }">
<div class="d-flex justify-center">
@ -85,9 +90,9 @@ export default defineComponent({
{ text: i18n.t("user.username"), value: "username" },
{ text: i18n.t("user.full-name"), value: "fullName" },
{ text: i18n.t("user.admin"), value: "admin" },
{ text: "Manage", value: "manage", sortable: false, align: "center" },
{ text: "Organize", value: "organize", sortable: false, align: "center" },
{ text: "Invite", value: "invite", sortable: false, align: "center" },
{ text: i18n.t("group.manage"), value: "manage", sortable: false, align: "center" },
{ text: i18n.t("settings.organize"), value: "organize", sortable: false, align: "center" },
{ text: i18n.t("group.invite"), value: "invite", sortable: false, align: "center" },
];
async function refreshMembers() {
@ -116,7 +121,7 @@ export default defineComponent({
},
head() {
return {
title: "Members",
title: this.$t("profile.members"),
};
},
});

View file

@ -4,7 +4,7 @@
<template #header>
<v-img max-height="200" max-width="200" class="mb-2" :src="require('~/static/svgs/data-reports.svg')"></v-img>
</template>
<template #title> Report </template>
<template #title> {{ $t('group.report') }} </template>
</BasePageTitle>
<v-container v-if="report">
<BaseCardSectionTitle :title="report.name"> </BaseCardSectionTitle>

View file

@ -23,7 +23,7 @@
</v-avatar>
</div>
<v-card-title class="headline justify-center pb-1"> Sign In </v-card-title>
<v-card-title class="headline justify-center pb-1"> {{ $t('user.sign-in') }} </v-card-title>
<v-card-text>
<v-form @submit.prevent="authenticate">
<v-text-field
@ -34,7 +34,7 @@
autofocus
class="rounded-lg"
name="login"
label="Email or Username"
:label="$t('user.email-or-username')"
type="text"
/>
<v-text-field
@ -46,11 +46,11 @@
rounded
class="rounded-lg"
name="password"
label="Password"
:label="$t('user.password')"
:type="inputType"
@click:append="togglePasswordShow"
/>
<v-checkbox v-model="form.remember" class="ml-2 mt-n2" label="Remember Me"></v-checkbox>
<v-checkbox v-model="form.remember" class="ml-2 mt-n2" :label="$t('user.remember-me')"></v-checkbox>
<v-card-actions class="justify-center pt-0">
<div class="max-button">
<v-btn :loading="loggingIn" color="primary" type="submit" large rounded class="rounded-xl" block>
@ -72,17 +72,17 @@
<div
v-for="link in [
{
text: 'Sponsor',
text: $t('about.sponsor'),
icon: $globals.icons.heart,
href: 'https://github.com/sponsors/hay-kot',
},
{
text: 'GitHub',
text: $t('about.github'),
icon: $globals.icons.github,
href: 'https://github.com/hay-kot/mealie',
},
{
text: 'Docs',
text: $t('about.docs'),
icon: $globals.icons.folderOutline,
href: 'https://docs.mealie.io/',
},
@ -103,7 +103,7 @@
<v-icon left>
{{ $vuetify.theme.dark ? $globals.icons.weatherSunny : $globals.icons.weatherNight }}
</v-icon>
{{ $vuetify.theme.dark ? "Light Mode" : "Dark Mode" }}
{{ $vuetify.theme.dark ? $t('settings.theme.light-mode') : $t('settings.theme.dark-mode') }}
</v-btn>
</v-container>
</template>
@ -123,7 +123,7 @@ export default defineComponent({
const isDark = useDark();
const router = useRouter();
const { $auth } = useContext();
const { $auth, i18n } = useContext();
whenever(
() => $auth.loggedIn,
@ -149,7 +149,7 @@ export default defineComponent({
async function authenticate() {
if (form.email.length === 0 || form.password.length === 0) {
alert.error("Please enter your email and password");
alert.error(i18n.t("user.please-enter-your-email-and-password") as string);
return;
}
@ -168,12 +168,12 @@ export default defineComponent({
// if ($axios.isAxiosError(error) && error.response?.status === 401) {
// @ts-ignore- see above
if (error.response?.status === 401) {
alert.error("Invalid Credentials");
alert.error(i18n.t("user.invalid-credentials") as string);
// @ts-ignore - see above
} else if (error.response?.status === 423) {
alert.error("Account Locked. Please try again later");
alert.error(i18n.t("user.account-locked-please-try-again-later") as string);
} else {
alert.error("Something Went Wrong!");
alert.error(i18n.t("events.something-went-wrong") as string);
}
}
loggingIn.value = false;

View file

@ -5,8 +5,8 @@
<template #header>
<v-img max-height="175" max-width="175" :src="require('~/static/svgs/recipes-create.svg')"></v-img>
</template>
<template #title> Recipe Creation </template>
Select one of the various ways to create a recipe
<template #title> {{ $t('recipe.recipe-creation') }} </template>
{{ $t('recipe.select-one-of-the-various-ways-to-create-a-recipe') }}
<template #content>
<div class="ml-auto">
<BaseOverflowButton v-model="subpage" rounded :items="subpages"> </BaseOverflowButton>
@ -20,7 +20,7 @@
<AdvancedOnly>
<v-container class="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>
</AdvancedOnly>
</div>
@ -34,37 +34,37 @@ import AdvancedOnly from "~/components/global/AdvancedOnly.vue";
export default defineComponent({
components: { AdvancedOnly },
setup() {
const { $globals } = useContext();
const { $globals, i18n } = useContext();
const subpages: MenuItem[] = [
{
icon: $globals.icons.link,
text: "Import with URL",
text: i18n.tc("recipe.import-with-url"),
value: "url",
},
{
icon: $globals.icons.edit,
text: "Create Recipe",
text: i18n.tc("recipe.create-recipe"),
value: "new",
},
{
icon: $globals.icons.zip,
text: "Import with .zip",
text: i18n.tc("recipe.import-with-zip"),
value: "zip",
},
{
icon: $globals.icons.fileImage,
text: "Create recipe from an image",
text: i18n.tc("recipe.create-recipe-from-an-image"),
value: "ocr",
},
{
icon: $globals.icons.link,
text: "Bulk URL Import",
text: i18n.tc("recipe.bulk-url-import"),
value: "bulk",
},
{
icon: $globals.icons.robot,
text: "Debug Scraper",
text: i18n.tc("recipe.debug-scraper"),
value: "debug",
},
];

View file

@ -1,11 +1,9 @@
<template>
<div>
<div>
<v-card-title class="headline"> Recipe Bulk Importer </v-card-title>
<v-card-title class="headline"> {{ $t('recipe.recipe-bulk-importer') }} </v-card-title>
<v-card-text>
The Bulk recipe importer allows you to import multiple recipes at once by queueing the sites on the backend and
running the task in the background. This can be useful when initially migrating to Mealie, or when you want to
import a large number of recipes.
{{ $t('recipe.recipe-bulk-importer-description') }}
</v-card-text>
</div>
<section class="mt-2">
@ -85,24 +83,24 @@
<RecipeDialogBulkAdd v-model="bulkDialog" @bulk-data="assignUrls" />
</v-card-actions>
<div class="px-1">
<v-checkbox v-model="showCatTags" hide-details label="Set Categories and Tags " />
<v-checkbox v-model="showCatTags" hide-details :label="$t('recipe.set-categories-and-tags')" />
</div>
<v-card-actions class="justify-end">
<BaseButton :disabled="bulkUrls.length === 0 || lockBulkImport" @click="bulkCreate">
<template #icon> {{ $globals.icons.check }} </template>
Submit
{{ $t('general.submit') }}
</BaseButton>
</v-card-actions>
</section>
<section class="mt-12">
<BaseCardSectionTitle title="Bulk Imports"> </BaseCardSectionTitle>
<BaseCardSectionTitle :title="$tc('recipe.bulk-imports')"> </BaseCardSectionTitle>
<ReportTable :items="reports" @delete="deleteReport" />
</section>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, ref } from "@nuxtjs/composition-api";
import { defineComponent, reactive, toRefs, ref, useContext } from "@nuxtjs/composition-api";
import { whenever } from "@vueuse/shared";
import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
@ -128,6 +126,7 @@ export default defineComponent({
);
const api = useUserApi();
const { i18n } = useContext();
const bulkUrls = ref([{ url: "", categories: [], tags: [] }]);
const lockBulkImport = ref(false);
@ -140,10 +139,10 @@ export default defineComponent({
const { response } = await api.recipes.createManyByUrl({ imports: bulkUrls.value });
if (response?.status === 202) {
alert.success("Bulk Import process has started");
alert.success(i18n.tc("recipe.bulk-import-process-has-started"));
lockBulkImport.value = true;
} else {
alert.error("Bulk import process has failed");
alert.error(i18n.tc("recipe.bulk-import-process-has-failed"));
}
fetchReports();
@ -166,7 +165,7 @@ export default defineComponent({
if (response?.status === 200) {
fetchReports();
} else {
alert.error("Report deletion failed");
alert.error(i18n.tc("recipe.report-deletion-failed"));
}
}

View file

@ -2,11 +2,9 @@
<div>
<v-form ref="domUrlForm" @submit.prevent="debugUrl(recipeUrl)">
<div>
<v-card-title class="headline"> Recipe Debugger </v-card-title>
<v-card-title class="headline"> {{ $t('recipe.recipe-debugger') }} </v-card-title>
<v-card-text>
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.
{{ $t('recipe.recipe-debugger-description') }}
<v-text-field
v-model="recipeUrl"
:label="$t('new-recipe.recipe-url')"
@ -28,14 +26,14 @@
<template #icon>
{{ $globals.icons.robot }}
</template>
Debug
{{ $t('recipe.debug') }}
</BaseButton>
</div>
</v-card-actions>
</div>
</v-form>
<section v-if="debugData">
<v-checkbox v-model="debugTreeView" label="Tree View"></v-checkbox>
<v-checkbox v-model="debugTreeView" :label="$t('recipe.tree-view')"></v-checkbox>
<LazyRecipeJsonEditor
v-model="debugData"
class="primary"

View file

@ -1,8 +1,8 @@
<template>
<div>
<v-card-title class="headline"> Create Recipe </v-card-title>
<v-card-title class="headline"> {{ $t('recipe.create-recipe') }} </v-card-title>
<v-card-text>
Create a recipe by providing the name. All recipes must have unique names.
{{ $t('recipe.create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names') }}
<v-form ref="domCreateByName">
<v-text-field
v-model="newRecipeName"
@ -15,7 +15,7 @@
class="rounded-lg mt-2"
rounded
:rules="[validators.required]"
hint="New recipe names must be unique"
:hint="$t('recipe.new-recipe-names-must-be-unique')"
persistent-hint
></v-text-field>
</v-form>

View file

@ -1,8 +1,8 @@
<template>
<div>
<v-card-title class="headline"> Create Recipe from an Image </v-card-title>
<v-card-title class="headline"> {{ $t('recipe.create-recipe-from-an-image') }} </v-card-title>
<v-card-text>
Create a recipe by uploading a scan.
{{ $t('recipe.create-a-recipe-by-uploading-a-scan') }}
<v-form ref="domCreateByOcr"> </v-form>
</v-card-text>
<v-card-actions class="justify-center">
@ -15,7 +15,7 @@
class="rounded-lg mt-2"
rounded
truncate-length="100"
hint="Upload a png image from a recipe book"
:hint="$t('recipe.upload-a-png-image-from-a-recipe-book')"
persistent-hint
prepend-icon=""
:prepend-inner-icon="$globals.icons.fileImage"

View file

@ -2,10 +2,9 @@
<div>
<v-form ref="domUrlForm" @submit.prevent="createByUrl(recipeUrl, importKeywordsAsTags, stayInEditMode)">
<div>
<v-card-title class="headline"> Scrape Recipe </v-card-title>
<v-card-title class="headline"> {{ $t('recipe.scrape-recipe') }} </v-card-title>
<v-card-text>
Scrape a recipe by url. Provide the url for the site you want to scrape, and Mealie will attempt to scrape the
recipe from that site and add it to your collection.
{{ $t('recipe.scrape-recipe-description') }}
<v-text-field
v-model="recipeUrl"
:label="$t('new-recipe.recipe-url')"
@ -20,8 +19,8 @@
:hint="$t('new-recipe.url-form-hint')"
persistent-hint
></v-text-field>
<v-checkbox v-model="importKeywordsAsTags" hide-details label="Import original keywords as tags" />
<v-checkbox v-model="stayInEditMode" hide-details label="Stay in Edit mode" />
<v-checkbox v-model="importKeywordsAsTags" hide-details :label="$t('recipe.import-original-keywords-as-tags')" />
<v-checkbox v-model="stayInEditMode" hide-details :label="$t('recipe.stay-in-edit-mode')" />
</v-card-text>
<v-card-actions class="justify-center">
<div style="width: 250px">

View file

@ -1,9 +1,9 @@
<template>
<v-form>
<div>
<v-card-title class="headline"> Import from Zip </v-card-title>
<v-card-title class="headline"> {{ $t('recipe.import-from-zip') }} </v-card-title>
<v-card-text>
Import a single recipe that was exported from another Mealie instance.
{{ $t('recipe.import-from-zip-description') }}
<v-file-input
v-model="newRecipeZip"
accept=".zip"
@ -13,7 +13,7 @@
class="rounded-lg mt-2"
rounded
truncate-length="100"
hint=".zip files must have been exported from Mealie"
:hint="$t('recipe.zip-files-must-have-been-exported-from-mealie')"
persistent-hint
prepend-icon=""
:prepend-inner-icon="$globals.icons.zip"

View file

@ -7,7 +7,7 @@
item-type="tags"
@delete="actions.deleteOne"
>
<template #title> Tags </template>
<template #title> {{ $t('tag.tags') }} </template>
</RecipeOrganizerPage>
</v-container>
</template>
@ -29,8 +29,10 @@ export default defineComponent({
actions,
};
},
head: {
title: "Tags",
head() {
return {
title: this.$tc("tag.tags"),
}
},
});
</script>

View file

@ -7,7 +7,7 @@
item-type="tools"
@delete="actions.deleteOne"
>
<template #title> Tools </template>
<template #title> {{ $t('tool.tools') }} </template>
</RecipeOrganizerPage>
</v-container>
</template>
@ -31,8 +31,10 @@ export default defineComponent({
actions: toolStore.actions,
};
},
head: {
title: "Tools",
head() {
return {
title: this.$tc("tool.tools"),
};
},
});
</script>

View file

@ -30,7 +30,7 @@
v-model="advanced"
color="info"
class="ma-0 pa-0"
label="Advanced"
:label="$t('search.advanced')"
@input="advanced = !advanced"
@click="advanced = !advanced"
/>
@ -75,7 +75,7 @@
:items="foods || []"
item-text="name"
:prepend-inner-icon="$globals.icons.foods"
label="Foods"
:label="$t('general.foods')"
>
<template #selection="data">
<v-chip
@ -102,7 +102,7 @@
<RecipeCardSection
class="mt-n5"
:icon="$globals.icons.search"
title="Results"
:title="$tc('search.results')"
:recipes="showRecipes.slice(0, maxResults)"
@sort="assignFuzzy"
/>

View file

@ -79,29 +79,29 @@
children: [
{
icon: $globals.icons.contentCopy,
text: 'Copy as Text',
text: $tc('shopping-list.copy-as-text'),
event: 'copy-plain',
},
{
icon: $globals.icons.contentCopy,
text: 'Copy as Markdown',
text: $tc('shopping-list.copy-as-markdown'),
event: 'copy-markdown',
},
],
},
{
icon: $globals.icons.delete,
text: 'Delete Checked',
text: $tc('shopping-list.delete-checked'),
event: 'delete',
},
{
icon: $globals.icons.tags,
text: 'Toggle Label Sort',
text: $tc('shopping-list.toggle-label-sort'),
event: 'sort-by-labels',
},
{
icon: $globals.icons.checkboxBlankOutline,
text: 'Uncheck All Items',
text: $tc('shopping-list.uncheck-all-items'),
event: 'uncheck',
},
]"
@ -122,7 +122,7 @@
{{ showChecked ? $globals.icons.chevronDown : $globals.icons.chevronRight }}
</v-icon>
</span>
{{ listItems.checked ? listItems.checked.length : 0 }} items checked
{{ $tc('shopping-list.items-checked-count', listItems.checked ? listItems.checked.length : 0) }}
</button>
<v-divider class="my-4"></v-divider>
<v-expand-transition>
@ -153,7 +153,7 @@
{{ $globals.icons.primary }}
</v-icon>
</span>
{{ shoppingList.recipeReferences ? shoppingList.recipeReferences.length : 0 }} Linked Recipes
{{ $tc('shopping-list.linked-recipes-count', shoppingList.recipeReferences ? shoppingList.recipeReferences.length : 0) }}
</div>
<v-divider class="my-4"></v-divider>
<RecipeList :recipes="listRecipes">
@ -178,7 +178,7 @@
<v-lazy>
<div class="d-flex justify-end mt-10">
<ButtonLink to="/group/data/labels" text="Manage Labels" :icon="$globals.icons.tags" />
<ButtonLink to="/group/data/labels" :text="$tc('shopping-list.manage-labels')" :icon="$globals.icons.tags" />
</div>
</v-lazy>
</v-container>
@ -187,7 +187,7 @@
<script lang="ts">
import draggable from "vuedraggable";
import { defineComponent, useAsync, useRoute, computed, ref, watch, onUnmounted } from "@nuxtjs/composition-api";
import { defineComponent, useAsync, useRoute, computed, ref, watch, onUnmounted, useContext } from "@nuxtjs/composition-api";
import { useIdle, useToggle } from "@vueuse/core";
import { useCopyList } from "~/composables/use-copy";
import { useUserApi } from "~/composables/api";
@ -224,6 +224,8 @@ export default defineComponent({
const route = useRoute();
const id = route.value.params.id;
const { i18n } = useContext();
// ===============================================================
// Shopping List Actions
@ -416,9 +418,9 @@ export default defineComponent({
function updateItemsByLabel() {
const items: { [prop: string]: ShoppingListItemOut[] } = {};
const noLabel = {
"No Label": [] as ShoppingListItemOut[],
};
const noLabelText = i18n.tc("shopping-list.no-label");
const noLabel = [] as ShoppingListItemOut[];
shoppingList.value?.listItems?.forEach((item) => {
if (item.checked) {
@ -432,12 +434,12 @@ export default defineComponent({
items[item.label.name] = [item];
}
} else {
noLabel["No Label"].push(item);
noLabel.push(item);
}
});
if (noLabel["No Label"].length > 0) {
items["No Label"] = noLabel["No Label"];
if (noLabel.length > 0) {
items[noLabelText] = noLabel;
}
itemsByLabel.value = items;

View file

@ -7,13 +7,13 @@
</BaseDialog>
<BaseDialog v-model="deleteDialog" :title="$tc('general.confirm')" color="error" @confirm="deleteOne">
<v-card-text> Are you sure you want to delete this item?</v-card-text>
<v-card-text>{{ $t('shopping-list.are-you-sure-you-want-to-delete-this-item') }}</v-card-text>
</BaseDialog>
<BasePageTitle divider>
<template #header>
<v-img max-height="100" max-width="100" :src="require('~/static/svgs/shopping-cart.svg')"></v-img>
</template>
<template #title> Shopping Lists </template>
<template #title>{{ $t('shopping-list.shopping-lists') }}</template>
</BasePageTitle>
<BaseButton create @click="createDialog = true" />
@ -33,7 +33,7 @@
</v-card>
</section>
<div class="d-flex justify-end mt-10">
<ButtonLink to="/group/data/labels" text="Manage Labels" :icon="$globals.icons.tags" />
<ButtonLink to="/group/data/labels" :text="$tc('shopping-list.manage-labels')" :icon="$globals.icons.tags" />
</div>
</v-container>
</template>

View file

@ -31,7 +31,7 @@
<template #default="{ state }">
<v-slide-x-transition leave-absolute hide-on-leave>
<div v-if="!state" key="personal-info">
<BaseCardSectionTitle class="mt-10" title="Personal Information"> </BaseCardSectionTitle>
<BaseCardSectionTitle class="mt-10" :title="$tc('profile.personal-information')"> </BaseCardSectionTitle>
<v-card tag="article" outlined>
<v-card-text class="pb-0">
<v-form ref="userUpdate">
@ -101,11 +101,11 @@
</ToggleState>
</section>
<section>
<BaseCardSectionTitle class="mt-10" title="Preferences"> </BaseCardSectionTitle>
<BaseCardSectionTitle class="mt-10" :title="$tc('profile.preferences')"> </BaseCardSectionTitle>
<v-checkbox
v-model="userCopy.advanced"
class="mt-n4"
label="Show advanced features (API Keys, Webhooks, and Data Management)"
:label="$t('profile.show-advanced-description')"
@change="updateUser"
></v-checkbox>
<div class="d-flex flex-wrap justify-center mt-5">
@ -113,9 +113,9 @@
<v-icon left>
{{ $globals.icons.backArrow }}
</v-icon>
Back to Profile
{{ $t('profile.back-to-profile') }}
</v-btn>
<v-btn outlined class="rounded-xl my-1 mx-1" to="/group"> Looking for Privacy Settings? </v-btn>
<v-btn outlined class="rounded-xl my-1 mx-1" to="/group"> {{ $t('profile.looking-for-privacy-settings') }} </v-btn>
</div>
</section>
</v-container>

View file

@ -3,10 +3,10 @@
<section class="d-flex flex-column align-center">
<UserAvatar size="84" :user-id="$auth.user.id" />
<h2 class="headline">👋 Welcome, {{ user.fullName }}</h2>
<h2 class="headline">{{ $t('profile.welcome-user', [user.fullName]) }}</h2>
<p class="subtitle-1 mb-0">
Manage your profile, recipes, and group settings.
<a href="https://hay-kot.github.io/mealie/" target="_blank"> Learn More </a>
{{ $t('profile.description') }}
<a href="https://hay-kot.github.io/mealie/" target="_blank"> {{ $t('general.learn-more') }} </a>
</p>
<v-card v-if="$auth.user.canInvite" flat color="background" width="100%" max-width="600px">
<v-card-actions class="d-flex justify-center">
@ -14,7 +14,7 @@
<v-icon left>
{{ $globals.icons.createAlt }}
</v-icon>
Get Invite Link
{{ $t('profile.get-invite-link') }}
</v-btn>
</v-card-actions>
<div v-show="generatedLink !== ''">
@ -40,15 +40,15 @@
</section>
<section class="my-3">
<div>
<h3 class="headline">Account Summary</h3>
<p>Here's a summary of your group's information</p>
<h3 class="headline">{{ $t('profile.account-summary') }}</h3>
<p>{{ $t('profile.account-summary-description') }}</p>
</div>
<v-row tag="section">
<v-col cols="12" sm="12" md="6">
<v-card outlined>
<v-card-title class="headline pb-0"> Group Statistics </v-card-title>
<v-card-title class="headline pb-0"> {{ $t('profile.group-statistics') }} </v-card-title>
<v-card-text class="py-0">
Your Group Statistics provide some insight how you're using Mealie.
{{ $t('profile.group-statistics-description') }}
</v-card-text>
<v-card-text class="d-flex flex-wrap justify-center align-center" style="gap: 0.8rem">
<StatsCards
@ -66,10 +66,10 @@
</v-col>
<v-col cols="12" sm="12" md="6" class="d-flex align-strart">
<v-card outlined>
<v-card-title class="headline pb-0"> Storage Capacity </v-card-title>
<v-card-title class="headline pb-0"> {{ $t('profile.storage-capacity') }} </v-card-title>
<v-card-text class="py-0">
Your storage capacity is a calculation of the images and assets you have uploaded.
<strong> This feature is currently inactive</strong>
{{ $t('profile.storage-capacity-description') }}
<strong> {{ $t('general.this-feature-is-currently-inactive') }}</strong>
</v-card-text>
<v-card-text>
<v-progress-linear :value="storageUsedPercentage" color="accent" class="rounded" height="30">
@ -85,8 +85,8 @@
<v-divider class="my-7"></v-divider>
<section>
<div>
<h3 class="headline">Personal</h3>
<p>These are settings that are personal to you. Changes here won't affect other users</p>
<h3 class="headline">{{ $t('profile.personal') }}</h3>
<p>{{ $t('profile.personal-description') }}</p>
</div>
<v-row tag="section">
<v-col cols="12" sm="12" md="6">
@ -94,8 +94,8 @@
:link="{ text: 'Manage User Profile', to: '/user/profile/edit' }"
:image="require('~/static/svgs/manage-profile.svg')"
>
<template #title> User Settings </template>
Manage your preferences, change your password, and update your email
<template #title> {{ $t('profile.user-settings') }} </template>
{{ $t('profile.user-settings-description') }}
</UserProfileLinkCard>
</v-col>
<AdvancedOnly>
@ -104,8 +104,8 @@
:link="{ text: 'Manage Your API Tokens', to: '/user/profile/api-tokens' }"
:image="require('~/static/svgs/manage-api-tokens.svg')"
>
<template #title> API Tokens </template>
Manage your API Tokens for access from external applications
<template #title> {{ $t('settings.token.api-tokens') }} </template>
{{ $t('profile.api-tokens-description') }}
</UserProfileLinkCard>
</v-col>
</AdvancedOnly>
@ -114,8 +114,8 @@
<v-divider class="my-7"></v-divider>
<section>
<div>
<h3 class="headline">Group</h3>
<p>These items are shared within your group. Editing one of them will change it for the whole group!</p>
<h3 class="headline">{{ $t('group.group') }}</h3>
<p>{{ $t('profile.group-description') }}</p>
</div>
<v-row tag="section">
<v-col cols="12" sm="12" md="6">
@ -123,8 +123,8 @@
:link="{ text: 'Group Settings', to: '/group' }"
:image="require('~/static/svgs/manage-group-settings.svg')"
>
<template #title> Group Settings </template>
Manage your common group settings like mealplan and privacy settings.
<template #title> {{ $t('profile.group-settings') }} </template>
{{ $t('profile.group-settings-description') }}
</UserProfileLinkCard>
</v-col>
<v-col cols="12" sm="12" md="6">
@ -132,8 +132,8 @@
:link="{ text: 'Manage Cookbooks', to: '/group/cookbooks' }"
:image="require('~/static/svgs/manage-cookbooks.svg')"
>
<template #title> Cookbooks </template>
Manage a collection of recipe categories and generate pages for them.
<template #title> {{ $t('sidebar.cookbooks') }} </template>
{{ $t('profile.cookbooks-description') }}
</UserProfileLinkCard>
</v-col>
<v-col v-if="user.canManage" cols="12" sm="12" md="6">
@ -141,8 +141,8 @@
:link="{ text: 'Manage Members', to: '/group/members' }"
:image="require('~/static/svgs/manage-members.svg')"
>
<template #title> Members </template>
See who's in your group and manage their permissions.
<template #title> {{ $t('profile.members') }} </template>
{{ $t('profile.members-description') }}
</UserProfileLinkCard>
</v-col>
<AdvancedOnly>
@ -151,8 +151,8 @@
:link="{ text: 'Manage Webhooks', to: '/group/webhooks' }"
:image="require('~/static/svgs/manage-webhooks.svg')"
>
<template #title> Webhooks </template>
Setup webhooks that trigger on days that you have have mealplan scheduled.
<template #title> {{ $t('settings.webhooks.webhooks') }} </template>
{{ $t('profile.webhooks-description') }}
</UserProfileLinkCard>
</v-col>
</AdvancedOnly>
@ -162,8 +162,8 @@
:link="{ text: 'Manage Notifiers', to: '/group/notifiers' }"
:image="require('~/static/svgs/manage-notifiers.svg')"
>
<template #title> Notifiers </template>
Setup email and push notifications that trigger on specific events.
<template #title> {{ $t('profile.notifiers') }} </template>
{{ $t('profile.notifiers-description') }}
</UserProfileLinkCard>
</v-col>
</AdvancedOnly>
@ -173,8 +173,8 @@
:link="{ text: 'Manage Data', to: '/group/data/foods' }"
:image="require('~/static/svgs/manage-recipes.svg')"
>
<template #title> Manage Data </template>
Manage your Food and Units (more options coming soon)
<template #title> {{ $t('profile.manage-data') }} </template>
{{ $t('profile.manage-data-description') }}
</UserProfileLinkCard>
</v-col>
</AdvancedOnly>
@ -184,8 +184,8 @@
:link="{ text: 'Manage Data Migrations', to: '/group/migrations' }"
:image="require('~/static/svgs/manage-data-migrations.svg')"
>
<template #title> Data Migrations </template>
Migrate your existing data from other applications like Nextcloud Recipes and Chowdown
<template #title>{{ $t('profile.data-migrations') }} </template>
{{ $t('profile.data-migrations-description') }}
</UserProfileLinkCard>
</v-col>
</AdvancedOnly>
@ -213,7 +213,7 @@ export default defineComponent({
},
scrollToTop: true,
setup() {
const { $auth } = useContext();
const { $auth, i18n } = useContext();
const user = computed(() => $auth.user);
@ -247,9 +247,9 @@ export default defineComponent({
});
if (data && data.success) {
alert.success("Email Sent");
alert.success(i18n.tc("profile.email-sent"));
} else {
alert.error("Error Sending Email");
alert.error(i18n.tc("profile.error-sending-email"));
}
state.loading = false;
}
@ -276,11 +276,11 @@ export default defineComponent({
}, useAsyncKey());
const statsText: { [key: string]: string } = {
totalRecipes: "Recipes",
totalUsers: "Users",
totalCategories: "Categories",
totalTags: "Tags",
totalTools: "Tools",
totalRecipes: i18n.tc("general.recipes"),
totalUsers: i18n.tc("user.users"),
totalCategories: i18n.tc("sidebar.categories"),
totalTags: i18n.tc("sidebar.tags"),
totalTools: i18n.tc("tool.tools"),
};
function getStatsTitle(key: string) {