1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-05 13:35:23 +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`;