Translations (#72)

* Translations + danish

* changed back proxy target to use ENV

* Resolved more merge conflicts

* Removed test in translation

* Documentation of translations

* Updated translations

* removed old packages

Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
kentora
2021-01-16 22:00:35 +01:00
committed by GitHub
parent b689c4715b
commit 0167f2f1ca
38 changed files with 546 additions and 182 deletions

View File

@@ -10,5 +10,10 @@
"python.testing.nosetestsEnabled": false, "python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": true, "python.testing.pytestEnabled": true,
"cSpell.enableFiletypes": ["!javascript", "!python"], "cSpell.enableFiletypes": ["!javascript", "!python"],
"python.testing.pytestArgs": ["mealie"] "python.testing.pytestArgs": ["mealie"],
"i18n-ally.localesPaths": "frontend/src/locales",
"i18n-ally.enabledFrameworks": [
"vue"
],
"i18n-ally.keystyle": "nested"
} }

View File

@@ -7,6 +7,7 @@ We love your input! We want to make contributing to this project as easy and tra
- Submitting a fix - Submitting a fix
- Proposing new features - Proposing new features
- Becoming a maintainer - Becoming a maintainer
- [Help translate to a new language or improve current translations](../translating)
[Remember to join the Discord and stay in touch with other developers working on the project](https://discord.gg/R6QDyJgbD2)! [Remember to join the Discord and stay in touch with other developers working on the project](https://discord.gg/R6QDyJgbD2)!

View File

@@ -0,0 +1,15 @@
# Contributing with translations
Having Mealie in different language could help the adaption of Mealie. Translations can be a great way for non-coders to contribute to Mealie.
## Is Mealie missing in your language?
If your language is missing, you can add it, by beginning to translate. We use a Vue-i18n in json files. Copy frontend/src/locales/en.json to get started.
## Improving translations
If your language is missing the translation for some strings, you can help out by adding a translation for that string. If you find a string you think could be improved, please feel free to do so.
## Tooling
Currently we use Vue-i18n for translations. Translations are stored in json format located in [frontend/src/locales](https://github.com/hay-kot/mealie/tree/master/frontend/src/locales).
If you have experience with a good Translation Management System, please feel free to chime in on the [Discord](https://discord.gg/R6QDyJgbD2), as such a system could be helpful as the projects grow.
Until then, [i18n Ally for VScode](https://marketplace.visualstudio.com/items?itemName=antfu.i18n-ally) is recommended to aid in translating. It also has a nice feature, which shows translations in-place when editing code.
i18n Ally will also show which languages is missing translations.

View File

@@ -38,6 +38,7 @@ nav:
- API Documentation: "api/docs/index.html" - API Documentation: "api/docs/index.html"
- Contributors Guide: - Contributors Guide:
- Non-Code: "contributors/non-coders.md" - Non-Code: "contributors/non-coders.md"
- Translating: "contributors/translating"
- Developers Guide: - Developers Guide:
- Code Contributions: "contributors/developers-guide/code-contributions.md" - Code Contributions: "contributors/developers-guide/code-contributions.md"
- Dev Getting Started: "contributors/developers-guide/starting-dev-server.md" - Dev Getting Started: "contributors/developers-guide/starting-dev-server.md"

View File

@@ -1089,6 +1089,16 @@
"postcss": "^7.0.0" "postcss": "^7.0.0"
} }
}, },
"@intlify/vue-i18n-loader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@intlify/vue-i18n-loader/-/vue-i18n-loader-1.0.0.tgz",
"integrity": "sha512-y7LlpKEQ01u7Yq14l4VNlbFYEHMmSEH1QXXASOMWspj9ZcIdCebhhvHCHqk5Oy5Epw3PtoxyRJNpb6Wle5udgA==",
"dev": true,
"requires": {
"js-yaml": "^3.13.1",
"json5": "^2.1.1"
}
},
"@mrmlnc/readdir-enhanced": { "@mrmlnc/readdir-enhanced": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npm.taobao.org/@mrmlnc/readdir-enhanced/download/@mrmlnc/readdir-enhanced-2.2.1.tgz", "resolved": "https://registry.npm.taobao.org/@mrmlnc/readdir-enhanced/download/@mrmlnc/readdir-enhanced-2.2.1.tgz",
@@ -11038,6 +11048,39 @@
"resolved": "https://registry.npm.taobao.org/vue/download/vue-2.6.12.tgz?cache=0&sync_timestamp=1603223959931&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue%2Fdownload%2Fvue-2.6.12.tgz", "resolved": "https://registry.npm.taobao.org/vue/download/vue-2.6.12.tgz?cache=0&sync_timestamp=1603223959931&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue%2Fdownload%2Fvue-2.6.12.tgz",
"integrity": "sha1-9evU+mvShpQD4pqJau1JBEVskSM=" "integrity": "sha1-9evU+mvShpQD4pqJau1JBEVskSM="
}, },
"vue-cli-plugin-i18n": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vue-cli-plugin-i18n/-/vue-cli-plugin-i18n-1.0.1.tgz",
"integrity": "sha512-sLo6YzudaWgn5dOMvrKixE5bb/onYGxcxm+0YexqoOx0QtR+7hZ/P5WPFBMM9v/2i1ec2YYe2PvKTBel7KE+tA==",
"dev": true,
"requires": {
"debug": "^4.1.0",
"deepmerge": "^4.2.0",
"dotenv": "^8.2.0",
"flat": "^5.0.0",
"rimraf": "^3.0.0",
"vue": "^2.6.11",
"vue-i18n": "^8.17.0",
"vue-i18n-extract": "1.0.2"
},
"dependencies": {
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
}
}
},
"vue-cli-plugin-vuetify": { "vue-cli-plugin-vuetify": {
"version": "2.0.9", "version": "2.0.9",
"resolved": "https://registry.npmjs.org/vue-cli-plugin-vuetify/-/vue-cli-plugin-vuetify-2.0.9.tgz", "resolved": "https://registry.npmjs.org/vue-cli-plugin-vuetify/-/vue-cli-plugin-vuetify-2.0.9.tgz",
@@ -11107,6 +11150,16 @@
"integrity": "sha1-UylVzB6yCKPZkLOp+acFdGV+CPI=", "integrity": "sha1-UylVzB6yCKPZkLOp+acFdGV+CPI=",
"dev": true "dev": true
}, },
"vue-html-to-paper": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/vue-html-to-paper/-/vue-html-to-paper-1.3.1.tgz",
"integrity": "sha512-5IdAPUgStfpVHfcG6nXD0FbUB1onWpvwVD+OZ00jJpy3qaRPkaGD7fFIvYgBB9YPkr0VK065LayEvmGmkkfhaQ=="
},
"vue-i18n": {
"version": "8.22.3",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.22.3.tgz",
"integrity": "sha512-Vhyx7sZEmmW/aZLkzSlXei08Rv3hTondx4J9wbOjnThocTIK1QiXV6QRdT4BTnhT24JixDSf6kGkxqCXSaJ3Jw=="
},
"vue-loader": { "vue-loader": {
"version": "15.9.5", "version": "15.9.5",
"resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-15.9.5.tgz?cache=0&sync_timestamp=1605670886675&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-15.9.5.tgz", "resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-15.9.5.tgz?cache=0&sync_timestamp=1605670886675&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-loader%2Fdownload%2Fvue-loader-15.9.5.tgz",

View File

@@ -5,7 +5,8 @@
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint",
"i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'"
}, },
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
@@ -14,12 +15,14 @@
"qs": "^6.9.4", "qs": "^6.9.4",
"v-jsoneditor": "^1.4.2", "v-jsoneditor": "^1.4.2",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-i18n": "^8.22.3",
"vue-router": "^3.4.9", "vue-router": "^3.4.9",
"vuetify": "^2.4.2", "vuetify": "^2.4.2",
"vuex": "^3.6.0", "vuex": "^3.6.0",
"vuex-persistedstate": "^4.0.0-beta.2" "vuex-persistedstate": "^4.0.0-beta.2"
}, },
"devDependencies": { "devDependencies": {
"@intlify/vue-i18n-loader": "^1.0.0",
"@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0", "@vue/cli-service": "~4.5.0",
@@ -28,7 +31,8 @@
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
"sass": "^1.32.0", "sass": "^1.32.0",
"sass-loader": "^8.0.0", "sass-loader": "^8.0.0",
"vue-cli-plugin-vuetify": "^2.0.9", "vue-cli-plugin-i18n": "~1.0.1",
"vue-cli-plugin-vuetify": "^2.0.8",
"vue-template-compiler": "^2.6.11", "vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.3.0" "vuetify-loader": "^1.3.0"
}, },

View File

@@ -1,12 +1,13 @@
<template> <template>
<v-card> <v-card>
<v-card-title class="headline"> Edit Meal Plan </v-card-title> <v-card-title class="headline"> {{$t('meal-plan.edit-meal-plan')}} </v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-text> <v-card-text>
<MealPlanCard v-model="mealPlan.meals" /> <MealPlanCard v-model="mealPlan.meals" />
<v-row align="center" justify="end"> <v-row align="center" justify="end">
<v-card-actions> <v-card-actions>
<v-btn color="success" text @click="update"> Update </v-btn> <v-btn color="success" text @click="update"> {{$t('general.update')}} </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
</v-card-actions> </v-card-actions>
</v-row> </v-row>

View File

@@ -1,6 +1,8 @@
<template> <template>
<v-card> <v-card>
<v-card-title class="headline"> Create a New Meal Plan </v-card-title> <v-card-title class="headline">
{{$t('meal-plan.create-a-new-meal-plan')}}
</v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-text> <v-card-text>
<v-row dense> <v-row dense>
@@ -17,7 +19,7 @@
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-text-field <v-text-field
v-model="startComputedDateFormatted" v-model="startComputedDateFormatted"
label="Start Date" :label="$t('meal-plan.start-date')"
persistent-hint persistent-hint
prepend-icon="mdi-calendar" prepend-icon="mdi-calendar"
readonly readonly
@@ -45,7 +47,7 @@
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-text-field <v-text-field
v-model="endComputedDateFormatted" v-model="endComputedDateFormatted"
label="End Date" :label="$t('meal-plan.end-date')"
persistent-hint persistent-hint
prepend-icon="mdi-calendar" prepend-icon="mdi-calendar"
readonly readonly
@@ -69,9 +71,9 @@
<v-row align="center" justify="end"> <v-row align="center" justify="end">
<v-card-actions> <v-card-actions>
<v-btn color="success" @click="random" v-if="meals[1]" text> <v-btn color="success" @click="random" v-if="meals[1]" text>
Random {{$t('general.random')}}
</v-btn> </v-btn>
<v-btn color="success" @click="save" text> Save </v-btn> <v-btn color="success" @click="save" text> {{$t('general.save')}} </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn icon @click="show = !show"> </v-btn> <v-btn icon @click="show = !show"> </v-btn>

View File

@@ -2,7 +2,7 @@
<v-row justify="center"> <v-row justify="center">
<v-dialog v-model="dialog" persistent max-width="800"> <v-dialog v-model="dialog" persistent max-width="800">
<v-card> <v-card>
<v-card-title class="headline"> Choose a Recipe </v-card-title> <v-card-title class="headline"> {{$t('meal-plan.choose-a-recipe')}} </v-card-title>
<v-card-text> <v-card-text>
<v-autocomplete <v-autocomplete
:items="availableRecipes" :items="availableRecipes"
@@ -13,14 +13,12 @@
hide-details hide-details
hide-selected hide-selected
item-text="slug" item-text="slug"
label="Search for a Recipe" :label="$t('search.search-for-a-recipe')"
single-line single-line
> >
<template v-slot:no-data> <template v-slot:no-data>
<v-list-item> <v-list-item>
<v-list-item-title> <v-list-item-title :v-html="$t('search.search-for-your-favorite-recipe')">
Search for your Favorite
<strong>Recipe</strong>
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>
</template> </template>
@@ -44,8 +42,8 @@
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="secondary" text @click="dialog = false"> Close </v-btn> <v-btn color="secondary" text @click="dialog = false"> {{$t('general.close')}} </v-btn>
<v-btn color="secondary" text @click="dialog = false"> Select </v-btn> <v-btn color="secondary" text @click="dialog = false"> {{$t('general.select')}} </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>

View File

@@ -10,17 +10,16 @@
v-on="on" v-on="on"
@click="inputText = ''" @click="inputText = ''"
> >
Bulk Add {{$t('new-recipe.bulk-add')}}
</v-btn> </v-btn>
</template> </template>
<v-card> <v-card>
<v-card-title class="headline"> Bulk Add </v-card-title> <v-card-title class="headline"> {{$t('new-recipe.bulk-add')}} </v-card-title>
<v-card-text> <v-card-text>
<p> <p>
Paste in your recipe data. Each line will be treated as an item in a {{$t('new-recipe.paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list')}}
list
</p> </p>
<v-textarea v-model="inputText"> </v-textarea> <v-textarea v-model="inputText"> </v-textarea>
</v-card-text> </v-card-text>
@@ -29,7 +28,7 @@
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="success" text @click="save"> Save </v-btn> <v-btn color="success" text @click="save"> {{$t('general.save')}} </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>

View File

@@ -6,21 +6,21 @@
<v-col> <v-col>
<v-file-input <v-file-input
v-model="fileObject" v-model="fileObject"
label="Image File" :label="$t('general.image-file')"
truncate-length="30" truncate-length="30"
@change="uploadImage" @change="uploadImage"
></v-file-input> ></v-file-input>
</v-col> </v-col>
<v-col cols="3"></v-col> <v-col cols="3"></v-col>
</v-row> </v-row>
<v-text-field class="my-3" label="Recipe Name" v-model="value.name"> <v-text-field class="my-3" :label="$t('recipe.recipe-name')" v-model="value.name">
</v-text-field> </v-text-field>
<v-textarea height="100" label="Description" v-model="value.description"> <v-textarea height="100" :label="$t('recipe.description')" v-model="value.description">
</v-textarea> </v-textarea>
<div class="my-2"></div> <div class="my-2"></div>
<v-row dense disabled> <v-row dense disabled>
<v-col sm="5"> <v-col sm="5">
<v-text-field label="Servings" v-model="value.recipeYield"> <v-text-field :label="$t('recipe.servings')" v-model="value.recipeYield">
</v-text-field> </v-text-field>
</v-col> </v-col>
<v-col></v-col> <v-col></v-col>
@@ -34,7 +34,7 @@
</v-row> </v-row>
<v-row> <v-row>
<v-col cols="12" sm="12" md="4" lg="4"> <v-col cols="12" sm="12" md="4" lg="4">
<h2 class="mb-4">Ingredients</h2> <h2 class="mb-4">{{$t('recipe.ingredients')}}</h2>
<div <div
v-for="(ingredient, index) in value.recipeIngredient" v-for="(ingredient, index) in value.recipeIngredient"
:key="generateKey('ingredient', index)" :key="generateKey('ingredient', index)"
@@ -51,7 +51,7 @@
<v-icon color="error">mdi-delete</v-icon> <v-icon color="error">mdi-delete</v-icon>
</v-btn> </v-btn>
<v-text-field <v-text-field
label="Ingredient" :label="$t('recipe.ingredient')"
v-model="value.recipeIngredient[index]" v-model="value.recipeIngredient[index]"
></v-text-field> ></v-text-field>
</v-row> </v-row>
@@ -61,7 +61,7 @@
</v-btn> </v-btn>
<BulkAdd @bulk-data="appendIngredients" /> <BulkAdd @bulk-data="appendIngredients" />
<h2 class="mt-6">Categories</h2> <h2 class="mt-6">{{$t('recipe.categories')}}</h2>
<v-combobox <v-combobox
dense dense
multiple multiple
@@ -83,7 +83,7 @@
</template> </template>
</v-combobox> </v-combobox>
<h2 class="mt-4">Tags</h2> <h2 class="mt-4">{{$t('recipe.tags')}}</h2>
<v-combobox dense multiple chips deletable-chips v-model="value.tags"> <v-combobox dense multiple chips deletable-chips v-model="value.tags">
<template v-slot:selection="data"> <template v-slot:selection="data">
<v-chip <v-chip
@@ -98,7 +98,7 @@
</template> </template>
</v-combobox> </v-combobox>
<h2 class="my-4">Notes</h2> <h2 class="my-4">{{$t('recipe.notes')}}</h2>
<v-card <v-card
class="mt-1" class="mt-1"
v-for="(note, index) in value.notes" v-for="(note, index) in value.notes"
@@ -122,7 +122,7 @@
></v-text-field> ></v-text-field>
</v-row> </v-row>
<v-textarea label="Note" v-model="value.notes[index]['text']"> <v-textarea :label="$t('recipe.note')" v-model="value.notes[index]['text']">
</v-textarea> </v-textarea>
</v-card-text> </v-card-text>
</v-card> </v-card>
@@ -135,7 +135,7 @@
<v-divider class="my-divider" :vertical="true"></v-divider> <v-divider class="my-divider" :vertical="true"></v-divider>
<v-col cols="12" sm="12" md="8" lg="8"> <v-col cols="12" sm="12" md="8" lg="8">
<h2 class="mb-4">Instructions</h2> <h2 class="mb-4">{{$t('recipe.instructions')}}</h2>
<div v-for="(step, index) in value.recipeInstructions" :key="index"> <div v-for="(step, index) in value.recipeInstructions" :key="index">
<v-hover v-slot="{ hover }"> <v-hover v-slot="{ hover }">
<v-card <v-card
@@ -153,7 +153,7 @@
@click="removeStep(index)" @click="removeStep(index)"
> >
<v-icon color="error">mdi-delete</v-icon> </v-btn <v-icon color="error">mdi-delete</v-icon> </v-btn
>Step: {{ index + 1 }}</v-card-title >{{ $t('recipe.step-index', {step: index + 1}) }}</v-card-title
> >
<v-card-text> <v-card-text>
<v-textarea <v-textarea

View File

@@ -32,7 +32,7 @@
</v-row> </v-row>
<v-row> <v-row>
<v-col cols="12" sm="12" md="4" lg="4"> <v-col cols="12" sm="12" md="4" lg="4">
<h2 class="mb-4">Ingredients</h2> <h2 class="mb-4">{{$t('recipe.ingredients')}}</h2>
<div <div
v-for="(ingredient, index) in ingredients" v-for="(ingredient, index) in ingredients"
:key="generateKey('ingredient', index)" :key="generateKey('ingredient', index)"
@@ -47,7 +47,7 @@
</div> </div>
<div v-if="categories[0]"> <div v-if="categories[0]">
<h2 class="mt-4">Categories</h2> <h2 class="mt-4">{{$t('recipe.categories')}}</h2>
<v-chip <v-chip
class="ma-1" class="ma-1"
color="accent" color="accent"
@@ -60,7 +60,7 @@
</div> </div>
<div v-if="tags[0]"> <div v-if="tags[0]">
<h2 class="mt-4">Tags</h2> <h2 class="mt-4">{{$t('recipe.tags')}}</h2>
<v-chip <v-chip
class="ma-1" class="ma-1"
color="accent" color="accent"
@@ -72,7 +72,7 @@
</v-chip> </v-chip>
</div> </div>
<h2 v-if="notes[0]" class="my-4">Notes</h2> <h2 v-if="notes[0]" class="my-4">{{$t('recipe.notes')}}</h2>
<v-card <v-card
class="mt-1" class="mt-1"
v-for="(note, index) in notes" v-for="(note, index) in notes"
@@ -87,7 +87,7 @@
<v-divider class="my-divider" :vertical="true"></v-divider> <v-divider class="my-divider" :vertical="true"></v-divider>
<v-col cols="12" sm="12" md="8" lg="8"> <v-col cols="12" sm="12" md="8" lg="8">
<h2 class="mb-4">Instructions</h2> <h2 class="mb-4">{{$t('recipe.instructions')}}</h2>
<v-hover <v-hover
v-for="(step, index) in instructions" v-for="(step, index) in instructions"
:key="generateKey('step', index)" :key="generateKey('step', index)"
@@ -99,7 +99,7 @@
:elevation="hover ? 12 : 2" :elevation="hover ? 12 : 2"
@click="toggleDisabled(index)" @click="toggleDisabled(index)"
> >
<v-card-title>Step: {{ index + 1 }}</v-card-title> <v-card-title>{{ $t('recipe.step-index', {step: index + 1}) }}</v-card-title>
<v-card-text>{{ step.text }}</v-card-text> <v-card-text>{{ step.text }}</v-card-text>
</v-card> </v-card>
</v-hover> </v-hover>
@@ -121,7 +121,7 @@
target="_blank" target="_blank"
class="rounded-sm mr-4" class="rounded-sm mr-4"
> >
Original Recipe {{$t('recipe.original-recipe')}}
</v-btn> </v-btn>
</v-row> </v-row>
</v-card-text> </v-card-text>

View File

@@ -12,19 +12,19 @@
<v-checkbox <v-checkbox
class="mb-n4 mt-1" class="mb-n4 mt-1"
dense dense
label="Import Recipes" :label="$t('settings.backup.import-recipes')"
v-model="importRecipes" v-model="importRecipes"
></v-checkbox> ></v-checkbox>
<v-checkbox <v-checkbox
class="my-n4" class="my-n4"
dense dense
label="Import Themes" :label="$t('settings.backup.import-themes')"
v-model="importThemes" v-model="importThemes"
></v-checkbox> ></v-checkbox>
<v-checkbox <v-checkbox
class="my-n4" class="my-n4"
dense dense
label="Import Settings" :label="$t('settings.backup.import-settings')"
v-model="importSettings" v-model="importSettings"
></v-checkbox> ></v-checkbox>
</v-col> </v-col>
@@ -66,14 +66,14 @@
<v-card-actions> <v-card-actions>
<v-btn disabled color="success" text @click="raiseEvent('download')"> <v-btn disabled color="success" text @click="raiseEvent('download')">
Download {{$t('general.download')}}
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="error" text @click="raiseEvent('delete')"> <v-btn color="error" text @click="raiseEvent('delete')">
Delete {{$t('general.delete')}}
</v-btn> </v-btn>
<v-btn color="success" text @click="raiseEvent('import')"> <v-btn color="success" text @click="raiseEvent('import')">
Import {{$t('general.import')}}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>

View File

@@ -1,33 +1,30 @@
<template> <template>
<v-card :loading="backupLoading"> <v-card :loading="backupLoading" class="mt-3" min-height="410px">
<v-card-title class="headline"> Backup and Exports </v-card-title> <v-card-title class="headline">
{{$t('settings.backup-and-exports')}}
</v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-text> <v-card-text>
<p> <p>
Backups are exported in standard JSON format along with all the images {{$t('settings.backup-info')}}
stored on the file system. In your backup folder you'll find a .zip file
that contains all of the recipe JSON and images from the database.
Additionally, if you selected a markdown file, those will also be stored
in the .zip file. To import a backup, it must be located in your backups
folder. Automated backups are done each day at 3:00 AM.
</p> </p>
<v-row dense align="center"> <v-row dense align="center">
<v-col dense cols="12" sm="12" md="4"> <v-col dense cols="12" sm="12" md="4">
<v-text-field v-model="backupTag" label="Backup Tag"></v-text-field> <v-text-field v-model="backupTag" :label="$t('settings.backup-tag')"></v-text-field>
</v-col> </v-col>
<v-col cols="12" sm="12" md="3"> <v-col cols="12" sm="12" md="3">
<v-combobox <v-combobox
auto-select-first auto-select-first
label="Markdown Template" :label="$t('settings.markdown-template')"
:items="availableTemplates" :items="availableTemplates"
v-model="selectedTemplate" v-model="selectedTemplate"
></v-combobox> ></v-combobox>
</v-col> </v-col>
<v-col dense cols="12" sm="12" md="2"> <v-col dense cols="12" sm="12" md="2">
<v-btn block text color="accent" @click="createBackup" width="165"> <v-btn block text color="accent" @click="createBackup" width="165">
Backup Recipes {{$t('settings.backup-recipes')}}
</v-btn> </v-btn>
</v-col> </v-col>
</v-row> </v-row>

View File

@@ -1,26 +1,25 @@
<template> <template>
<v-card-text> <v-card-text>
<p> <p>
Currently Chowdown via public Repo URL is the only supported type of {{$t('migration.currently-chowdown-via-public-repo-url-is-the-only-supported-type-of-migration')}}
migration
</p> </p>
<v-form ref="form"> <v-form ref="form">
<v-row dense align="center"> <v-row dense align="center">
<v-col cols="12" md="5" sm="5"> <v-col cols="12" md="5" sm="5">
<v-text-field <v-text-field
v-model="repo" v-model="repo"
label="Chowdown Repo URL" :label="$t('migration.chowdown-repo-url')"
:rules="[rules.required]" :rules="[rules.required]"
> >
</v-text-field> </v-text-field>
</v-col> </v-col>
<v-col cols="12" md="4" sm="5"> <v-col cols="12" md="4" sm="5">
<v-btn text color="info" @click="importRepo"> Migrate </v-btn> <v-btn text color="info" @click="importRepo"> {{$t('migration.migrate')}} </v-btn>
</v-col> </v-col>
</v-row> </v-row>
</v-form> </v-form>
<v-alert v-if="failedRecipes[1]" outlined dense type="error"> <v-alert v-if="failedRecipes[1]" outlined dense type="error">
<h4>Failed Recipes</h4> <h4>{{$t('migration.failed-recipes')}}</h4>
<v-list dense> <v-list dense>
<v-list-item v-for="fail in this.failedRecipes" :key="fail"> <v-list-item v-for="fail in this.failedRecipes" :key="fail">
{{ fail }} {{ fail }}
@@ -28,7 +27,7 @@
</v-list> </v-list>
</v-alert> </v-alert>
<v-alert v-if="failedImages[1]" outlined dense type="error"> <v-alert v-if="failedImages[1]" outlined dense type="error">
<h4>Failed Images</h4> <h4>{{$t('migration.failed-images')}}</h4>
<v-list dense> <v-list dense>
<v-list-item v-for="fail in this.failedImages" :key="fail"> <v-list-item v-for="fail in this.failedImages" :key="fail">
{{ fail }} {{ fail }}

View File

@@ -1,9 +1,7 @@
<template> <template>
<v-card-text> <v-card-text>
<p> <p>
You can import recipes from either a zip file or a directory located in {{$t('migration.you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected')}}
the /app/data/migraiton/ folder. Please review the documentation to ensure
your directory structure matches what is expected
</p> </p>
<v-form ref="form"> <v-form ref="form">
<v-row align="center"> <v-row align="center">
@@ -11,20 +9,20 @@
<v-select <v-select
:items="availableImports" :items="availableImports"
v-model="selectedImport" v-model="selectedImport"
label="Nextcloud Data" :label="$t('migration.nextcloud-data')"
:rules="[rules.required]" :rules="[rules.required]"
></v-select> ></v-select>
</v-col> </v-col>
<v-col cols="12" md="2" sm="12"> <v-col cols="12" md="2" sm="12">
<v-btn text color="info" @click="importRecipes"> Migrate </v-btn> <v-btn text color="info" @click="importRecipes"> {{$t('migration.migrate')}} </v-btn>
</v-col> </v-col>
<v-col cols="12" md="1" sm="12"> <v-col cols="12" md="1" sm="12">
<v-btn text color="error" @click="deleteImportValidation"> <v-btn text color="error" @click="deleteImportValidation">
Delete {{$t('general.delete')}}
</v-btn> </v-btn>
<Confirmation <Confirmation
title="Delete Data" :title="$t('general.delete-data')"
message="Are you sure you want to delete this migration data?" :message="$t('migration.delete-confirmation')"
color="error" color="error"
icon="mdi-alert-circle" icon="mdi-alert-circle"
ref="deleteThemeConfirm" ref="deleteThemeConfirm"
@@ -39,9 +37,9 @@
</v-row> </v-row>
</v-form> </v-form>
<SuccessFailureAlert <SuccessFailureAlert
success-header="Successfully Imported from Nextcloud" success-header="{{$t('migration.successfully-imported-from-nextcloud')}}"
:success="successfulImports" :success="successfulImports"
failed-header="Failed Imports" failed-header="$t('migration.failed-imports')"
:failed="failedImports" :failed="failedImports"
/> />
</v-card-text> </v-card-text>

View File

@@ -2,7 +2,7 @@
<v-form ref="file"> <v-form ref="file">
<v-file-input <v-file-input
:loading="loading" :loading="loading"
label="Upload an Archive" :label="$t('migration.upload-an-archive')"
v-model="file" v-model="file"
accept=".zip" accept=".zip"
@change="upload" @change="upload"

View File

@@ -1,6 +1,6 @@
<template> <template>
<v-card :loading="loading"> <v-card :loading="loading">
<v-card-title class="headline"> Recipe Migration </v-card-title> <v-card-title class="headline"> {{$t('migration.recipe-migration')}} </v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<v-tabs v-model="tab"> <v-tabs v-model="tab">

View File

@@ -5,7 +5,7 @@
</v-btn> </v-btn>
<v-dialog v-model="dialog" width="400"> <v-dialog v-model="dialog" width="400">
<v-card> <v-card>
<v-card-title> {{ buttonText }} Color </v-card-title> <v-card-title> {{ buttonText }} {{$t('settings.color')}} </v-card-title>
<v-card-text> <v-card-text>
<v-text-field v-model="color"> </v-text-field> <v-text-field v-model="color"> </v-text-field>
<v-row> <v-row>
@@ -26,8 +26,8 @@
</v-row> </v-row>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-btn text @click="toggleSwatches"> Swatches </v-btn> <v-btn text @click="toggleSwatches"> {{$t('settings.swatches')}} </v-btn>
<v-btn text @click="dialog = false"> Select </v-btn> <v-btn text @click="dialog = false"> {{$t('general.select')}} </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>

View File

@@ -1,9 +1,9 @@
<template> <template>
<div> <div>
<v-btn text color="info" @click="dialog = true"> New </v-btn> <v-btn text color="info" @click="dialog = true"> {{$t('general.new')}} </v-btn>
<v-dialog v-model="dialog" width="400"> <v-dialog v-model="dialog" width="400">
<v-card> <v-card>
<v-card-title> Add a New Theme </v-card-title> <v-card-title> {{$t('settings.add-a-new-theme')}} </v-card-title>
<v-card-text> <v-card-text>
<v-text-field <v-text-field
label="Theme Name" label="Theme Name"
@@ -13,9 +13,9 @@
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="grey" text @click="dialog = false"> Cancel </v-btn> <v-btn color="grey" text @click="dialog = false"> {{$t('general.cancel')}} </v-btn>
<v-btn color="success" text @click="Select" :disabled="!themeName"> <v-btn color="success" text @click="Select" :disabled="!themeName">
Create {{$t('general.create')}}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>

View File

@@ -1,12 +1,11 @@
<template> <template>
<v-card> <v-card>
<v-card-title class="headline"> Theme Settings </v-card-title> <v-card-title class="headline"> {{$t('settings.theme.theme-settings')}} </v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-text> <v-card-text>
<h2 class="mt-4 mb-1">Dark Mode</h2> <h2 class="mt-4 mb-1">{{$t('settings.theme.dark-mode')}}</h2>
<p> <p>
Choose how Mealie looks to you. Set your theme preference to follow your {{$t('settings.theme.choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme')}}
system settings, or choose to use the light or dark theme.
</p> </p>
<v-row dense align="center"> <v-row dense align="center">
<v-col cols="12"> <v-col cols="12">
@@ -18,33 +17,31 @@
> >
<v-btn value="system"> Default to system </v-btn> <v-btn value="system"> Default to system </v-btn>
<v-btn value="light"> Light </v-btn> <v-btn value="light"> {{$t('settings.theme.light')}} </v-btn>
<v-btn value="dark"> Dark </v-btn> <v-btn value="dark"> {{$t('settings.theme.dark')}} </v-btn>
</v-btn-toggle> </v-btn-toggle>
</v-col> </v-col>
</v-row></v-card-text </v-row></v-card-text
> >
<v-divider></v-divider> <v-divider></v-divider>
<v-card-text> <v-card-text>
<h2 class="mt-1 mb-1">Theme</h2> <h2 class="mt-1 mb-1">{{$t('settings.theme.theme')}}</h2>
<p> <p>
Select a theme from the dropdown or create a new theme. Note that the {{$t('settings.theme.select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference')}}
default theme will be served to all users who have not set a theme
preference.
</p> </p>
<v-form ref="form" lazy-validation> <v-form ref="form" lazy-validation>
<v-row dense align="center"> <v-row dense align="center">
<v-col cols="12" md="4" sm="3"> <v-col cols="12" md="4" sm="3">
<v-select <v-select
label="Saved Color Theme" :label="$t('settings.theme.saved-color-theme')"
:items="availableThemes" :items="availableThemes"
item-text="name" item-text="name"
return-object return-object
v-model="selectedTheme" v-model="selectedTheme"
@change="themeSelected" @change="themeSelected"
:rules="[(v) => !!v || 'Theme is required']" :rules="[(v) => !!v || $t('settings.theme.theme-is-required')]"
required required
> >
</v-select> </v-select>
@@ -57,8 +54,8 @@
Delete Delete
</v-btn> </v-btn>
<Confirmation <Confirmation
title="Delete Theme" title="$t('settings.theme.delete-theme')"
message="Are you sure you want to delete this theme?" message="$t('settings.theme.are-you-sure-you-want-to-delete-this-theme')"
color="error" color="error"
icon="mdi-alert-circle" icon="mdi-alert-circle"
ref="deleteThemeConfirm" ref="deleteThemeConfirm"
@@ -70,43 +67,43 @@
<v-row dense align-content="center" v-if="selectedTheme.colors"> <v-row dense align-content="center" v-if="selectedTheme.colors">
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog
button-text="Primary" button-text="$t('settings.theme.primary')"
v-model="selectedTheme.colors.primary" v-model="selectedTheme.colors.primary"
/> />
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog
button-text="Secondary" button-text="$t('settings.theme.secondary')"
v-model="selectedTheme.colors.secondary" v-model="selectedTheme.colors.secondary"
/> />
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog
button-text="Accent" button-text="$t('settings.theme.accent')"
v-model="selectedTheme.colors.accent" v-model="selectedTheme.colors.accent"
/> />
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog
button-text="Success" button-text="$t('settings.theme.success')"
v-model="selectedTheme.colors.success" v-model="selectedTheme.colors.success"
/> />
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog
button-text="Info" button-text="$t('settings.theme.info')"
v-model="selectedTheme.colors.info" v-model="selectedTheme.colors.info"
/> />
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog
button-text="Warning" button-text="$t('settings.theme.warning')"
v-model="selectedTheme.colors.warning" v-model="selectedTheme.colors.warning"
/> />
</v-col> </v-col>
<v-col> <v-col>
<ColorPickerDialog <ColorPickerDialog
button-text="Error" button-text="$t('settings.theme.error')"
v-model="selectedTheme.colors.error" v-model="selectedTheme.colors.error"
/> />
</v-col> </v-col>
@@ -119,7 +116,7 @@
<v-col></v-col> <v-col></v-col>
<v-col align="end"> <v-col align="end">
<v-btn text color="success" @click="saveThemes"> <v-btn text color="success" @click="saveThemes">
Save Colors and Apply Theme {{$t('settings.theme.save-colors-and-apply-theme')}}
</v-btn> </v-btn>
</v-col> </v-col>
</v-row> </v-row>

View File

@@ -9,7 +9,7 @@
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-text-field <v-text-field
v-model="time" v-model="time"
label="Set New Time" :label="$t('settings.set-new-time')"
prepend-icon="mdi-clock-time-four-outline" prepend-icon="mdi-clock-time-four-outline"
readonly readonly
v-bind="attrs" v-bind="attrs"
@@ -18,8 +18,8 @@
</template> </template>
<v-time-picker v-if="modal2" v-model="time" full-width> <v-time-picker v-if="modal2" v-model="time" full-width>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn text color="primary" @click="modal2 = false"> Cancel </v-btn> <v-btn text color="primary" @click="modal2 = false"> {{$t('general.cancel')}} </v-btn>
<v-btn text color="primary" @click="saveTime"> OK </v-btn> <v-btn text color="primary" @click="saveTime"> {{$t('general.ok')}} </v-btn>
</v-time-picker> </v-time-picker>
</v-dialog> </v-dialog>
</template> </template>

View File

@@ -1,21 +1,17 @@
<template> <template>
<v-card> <v-card>
<v-card-title class="headline"> <v-card-title class="headline">
Meal Planner Webhooks {{$t('settings.webhooks.meal-planner-webhooks')}}
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<p> <p v-html="$t('settings.webhooks.the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at', {time: time})"></p>
The URLs listed below will recieve webhooks containing the recipe data
for the meal plan on it's scheduled day. Currently Webhooks will execute
at <strong>{{ time }}</strong>
</p>
<v-row dense align="center"> <v-row dense align="center">
<v-col cols="12" md="2" sm="5"> <v-col cols="12" md="2" sm="5">
<v-switch <v-switch
v-model="enabled" v-model="enabled"
inset inset
label="Enabled" :label="$t('general.enabled')"
class="my-n3" class="my-n3"
></v-switch> ></v-switch>
</v-col> </v-col>
@@ -23,7 +19,7 @@
<TimePickerDialog @save-time="saveTime" /> <TimePickerDialog @save-time="saveTime" />
</v-col> </v-col>
<v-col cols="12" md="4" sm="5"> <v-col cols="12" md="4" sm="5">
<v-btn text color="info" @click="testWebhooks"> Test Webhooks </v-btn> <v-btn text color="info" @click="testWebhooks"> {{$t('settings.webhooks.test-webhooks')}} </v-btn>
</v-col> </v-col>
</v-row> </v-row>
@@ -36,7 +32,7 @@
<v-col> <v-col>
<v-text-field <v-text-field
v-model="webhooks[index]" v-model="webhooks[index]"
label="Webhook URL" :label="$t('settings.webhooks.webhook-url')"
></v-text-field> ></v-text-field>
</v-col> </v-col>
</v-row> </v-row>
@@ -51,7 +47,7 @@
<v-col> </v-col> <v-col> </v-col>
<v-col align="end"> <v-col align="end">
<v-btn text color="success" @click="saveWebhooks"> <v-btn text color="success" @click="saveWebhooks">
Save Webhooks {{$t('settings.webhooks.save-webhooks')}}
</v-btn> </v-btn>
</v-col> </v-col>
</v-row> </v-row>

View File

@@ -2,16 +2,15 @@
<div class="text-center"> <div class="text-center">
<v-dialog v-model="addRecipe" width="650" @click:outside="reset"> <v-dialog v-model="addRecipe" width="650" @click:outside="reset">
<v-card :loading="processing"> <v-card :loading="processing">
<v-card-title class="headline"> From URL </v-card-title> <v-card-title class="headline">{{ $t('new-recipe.from-url') }} </v-card-title>
<v-card-text> <v-card-text>
<v-form> <v-form>
<v-text-field v-model="recipeURL" label="Recipe URL"></v-text-field> <v-text-field v-model="recipeURL" :label="$t('new-recipe.recipe-url')"></v-text-field>
</v-form> </v-form>
<v-alert v-if="error" color="red" outlined type="success"> <v-alert v-if="error" color="red" outlined type="success">
Looks like there was an error parsing the URL. Check the log and {{ $t('new-recipe.error-message') }}
debug/last_recipe.json to see what went wrong.
</v-alert> </v-alert>
</v-card-text> </v-card-text>
@@ -19,8 +18,8 @@
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="grey" text @click="reset"> Close </v-btn> <v-btn color="grey" text @click="reset"> {{$t('general.close')}} </v-btn>
<v-btn color="success" text @click="createRecipe"> Submit </v-btn> <v-btn color="success" text @click="createRecipe"> {{ $t('general.submit') }} </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>

View File

@@ -13,20 +13,20 @@
v-model="user.name" v-model="user.name"
light="light" light="light"
prepend-icon="person" prepend-icon="person"
label="Name" :label="$t('general.name')"
></v-text-field> ></v-text-field>
<v-text-field <v-text-field
v-model="user.email" v-model="user.email"
light="light" light="light"
prepend-icon="mdi-email" prepend-icon="mdi-email"
label="Email" :label="$t('login.email')"
type="email" type="email"
></v-text-field> ></v-text-field>
<v-text-field <v-text-field
v-model="user.password" v-model="user.password"
light="light" light="light"
prepend-icon="mdi-lock" prepend-icon="mdi-lock"
label="Password" :label="$t('login.password')"
type="password" type="password"
></v-text-field> ></v-text-field>
<v-checkbox <v-checkbox
@@ -34,7 +34,7 @@
v-if="options.isLoggingIn" v-if="options.isLoggingIn"
v-model="options.shouldStayLoggedIn" v-model="options.shouldStayLoggedIn"
light="light" light="light"
label="Stay logged in?" :label="$t('login.stay-logged-in')"
hide-details="hide-details" hide-details="hide-details"
></v-checkbox> ></v-checkbox>
<v-btn <v-btn
@@ -44,14 +44,14 @@
color="primary" color="primary"
block="block" block="block"
type="submit" type="submit"
>Sign in</v-btn >{{$t('login.sign-in')}}</v-btn
> >
<v-btn <v-btn
v-else v-else
block="block" block="block"
type="submit" type="submit"
@click.prevent="options.isLoggingIn = true" @click.prevent="options.isLoggingIn = true"
>Sign up</v-btn >{{$t('login.sign-up')}}</v-btn
> >
</v-form> </v-form>
</v-card-text> </v-card-text>

View File

@@ -32,26 +32,32 @@
<script> <script>
export default { export default {
data: () => ({ data: function () {
items: [ return {
{ items: [
icon: "mdi-calendar-week", {
title: "Dinner This Week", icon: "mdi-calendar-week",
nav: "/meal-plan/this-week", title: this.$i18n.t("meal-plan.dinner-this-week"),
}, nav: "/meal-plan/this-week",
{ },
icon: "mdi-calendar-today", {
title: "Dinner Today", icon: "mdi-calendar-today",
nav: "/meal-plan/today", title: this.$i18n.t("meal-plan.dinner-today"),
}, nav: "/meal-plan/today",
{ },
icon: "mdi-calendar-multiselect", {
title: "Planner", icon: "mdi-calendar-multiselect",
nav: "/meal-plan/planner", title: this.$i18n.t("meal-plan.planner"),
}, nav: "/meal-plan/planner",
{ icon: "mdi-cog", title: "Settings", nav: "/settings/site" }, },
], {
}), icon: "mdi-cog",
title: this.$i18n.t("general.settings"),
nav: "/settings/site",
},
],
};
},
methods: { methods: {
navRouter(route) { navRouter(route) {
this.$router.push(route); this.$router.push(route);

View File

@@ -26,7 +26,7 @@
<v-tooltip top color="secondary" max-width="400" open-delay="50"> <v-tooltip top color="secondary" max-width="400" open-delay="50">
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn color="secondary" v-on="on" v-bind="attrs" text <v-btn color="secondary" v-on="on" v-bind="attrs" text
>Description</v-btn >{{$t('recipe.description')}}</v-btn
> >
</template> </template>
<span>{{ description }}</span> <span>{{ description }}</span>

View File

@@ -9,15 +9,13 @@
hide-details hide-details
hide-selected hide-selected
item-text="slug" item-text="slug"
label="Search for a Recipe" :label="$t('search.search-for-a-recipe')"
single-line single-line
@keyup.enter.native="moreInfo(selected)" @keyup.enter.native="moreInfo(selected)"
> >
<template v-slot:no-data> <template v-slot:no-data>
<v-list-item> <v-list-item>
<v-list-item-title> <v-list-item-title :v-html="$t('search.search-for-your-favorite-recipe')">
Search for your Favorite
<strong>Recipe</strong>
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>
</template> </template>

View File

@@ -5,7 +5,7 @@
<template v-slot:action="{ attrs }"> <template v-slot:action="{ attrs }">
<v-btn color="white" text v-bind="attrs" @click="close(false)"> <v-btn color="white" text v-bind="attrs" @click="close(false)">
Close {{$t('general.close')}}
</v-btn> </v-btn>
</template> </template>
</v-snackbar> </v-snackbar>

23
frontend/src/i18n.js Normal file
View File

@@ -0,0 +1,23 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
function loadLocaleMessages () {
const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
const messages = {}
locales.keys().forEach(key => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
if (matched && matched.length > 1) {
const locale = matched[1]
messages[locale] = locales(key)
}
})
return messages
}
export default new VueI18n({
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages()
})

View File

@@ -0,0 +1,133 @@
{
"404": {
"page-not-found": "404 side blev ikke fundet",
"take-me-home": "Tag mig hjem"
},
"new-recipe": {
"from-url": "Fra URL",
"recipe-url": "URL på opskrift",
"error-message": "Der opstod en fejl under indlæsning af opskriften. Tjek loggen og debug/last_recipe.json for at fejlsøge problemet.",
"bulk-add": "Bulk Tilføj",
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Indsæt dine opskriftsdata. \nHver linje behandles som et element på en liste"
},
"general": {
"submit": "Indsend",
"name": "Navn",
"settings": "Indstillinger",
"cancel": "Annuller",
"close": "Luk",
"create": "Opret",
"delete": "Slet",
"edit": "Rediger",
"enabled": "Aktiveret",
"image-file": "Billedfil",
"new": "Ny",
"ok": "Ok",
"random": "Tilfældig",
"save": "Gem",
"select": "Vælg",
"update": "Opdater",
"delete-data": "Slet data",
"download": "Hent",
"import": "Importere"
},
"login": {
"email": "E-mail",
"password": "Adgangskode",
"sign-in": "Log ind",
"sign-up": "Opret bruger",
"stay-logged-in": "Forbliv logget ind"
},
"meal-plan": {
"dinner-this-week": "Madplan denne uge",
"dinner-today": "Madplan i dag",
"planner": "Planlægger",
"choose-a-recipe": "Vælg en opskrift",
"create-a-new-meal-plan": "Opret en ny måltidsplan",
"edit-meal-plan": "Rediger måltidsplan",
"end-date": "Slutdato",
"meal-plans": "Måltidsplaner",
"start-date": "Start dato"
},
"recipe": {
"description": "Beskrivelse",
"categories": "Kategorier",
"ingredient": "Ingrediens",
"ingredients": "Ingredienser",
"instructions": "Instruktioner",
"note": "Bemærk",
"notes": "Bemærkninger",
"original-recipe": "Oprindelig opskrift",
"recipe-name": "Opskriftens navn",
"servings": "Portioner",
"step-index": "Trin: {step}",
"tags": "Mærker",
"view-recipe": "Se opskrift"
},
"search": {
"search-for-a-recipe": "Søg efter en opskrift",
"search-for-your-favorite-recipe": "Søg efter din foretrukne <strong>opskrift</strong>"
},
"migration": {
"chowdown-repo-url": "Chowdown Repo URL",
"currently-chowdown-via-public-repo-url-is-the-only-supported-type-of-migration": "I øjeblikket er Chowdown via offentlig Repo URL den eneste understøttede migreringstype",
"failed-images": "Mislykkede billeder",
"failed-recipes": "Mislykkede opskrifter",
"migrate": "Migrere",
"recipe-migration": "Migrering af opskrifter",
"delete-confirmation": "Er du sikker på, at du vil slette disse migrationsdata?",
"failed-imports": "Mislykket import",
"nextcloud-data": "Nextcloud data",
"successfully-imported-from-nextcloud": "Importeret fra Nextcloud",
"upload-an-archive": "Upload et arkiv",
"you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected": "Du kan importere opskrifter fra enten en zip-fil eller et bibliotek i /app/data/migraiton/ folderen. \nGennemse dokumentationen for at sikre, at din bibliotekstruktur svarer til det, der forventes"
},
"settings": {
"add-a-new-theme": "Tilføj et nyt tema",
"backup-and-exports": "Backup og eksport",
"backup-info": "Sikkerhedskopier eksporteres i standard JSON-format sammen med alle de billeder, der er gemt på filsystemet. \nI din sikkerhedskopimappe finder du en .zip-fil, der indeholder alle opskrifterne JSON og billeder fra databasen. \nDerudover, hvis du valgte en markdown-fil, gemmes disse også i .zip-filen. \nFor at importere en sikkerhedskopi skal den være placeret i din sikkerhedskopimappe. \nAutomatiske sikkerhedskopier udføres hver dag kl. 3:00.",
"backup-recipes": "Sikkerhedskopier opksrifter",
"backup-tag": "Sikkerhedskopier tags",
"color": "Farve",
"contribute": "Bidrag",
"explore-the-docs": "Udforsk dokumentation",
"markdown-template": "Markdown skabelon",
"new-version-available": "En ny version af Mealie er tilgængelig. <a {aContents}> Besøg repoen </a>",
"set-new-time": "Indstil ny tid",
"swatches": "Prøver",
"version-latest": "Version: {current} | Seneste: {latest}",
"theme": {
"accent": "Accent",
"dark-mode": "Mørk tilstand",
"error": "Fejl",
"info": "Info",
"primary": "Primær",
"secondary": "Sekundær",
"select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference": "Vælg et tema i rullemenuen, eller opret et nyt tema. \nBemærk, at standardtemaet serveres til alle brugere, der ikke har angivet en temapræference.",
"success": "Succes",
"theme-is-required": "Tema er påkrævet",
"theme-settings": "Temaindstillinger",
"warning": "Advarsel",
"are-you-sure-you-want-to-delete-this-theme": "Er du sikker på, at du vil slette dette tema?",
"choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme": "Vælg, hvordan Mealie ser ud for dig. \nIndstil dit tema til at følge dine systemindstillinger, eller vælg at bruge det lyse eller mørke tema.",
"dark": "Mørkt",
"delete-theme": "Slet tema",
"light": "Lyst",
"save-colors-and-apply-theme": "Gem farver og anvend tema",
"saved-color-theme": "Gemt farvetema",
"theme": "Tema"
},
"webhooks": {
"meal-planner-webhooks": "Måltidsplanlægning Webhooks",
"save-webhooks": "Gem Webhooks",
"test-webhooks": "Test Webhooks",
"the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "Webadresserne, der er anført nedenfor, modtager webhooks, der indeholder opskriftsdataene for måltidsplanen på den planlagte dag. \nWebhooks udføres i øjeblikket på <strong> {time} </strong>",
"webhook-url": "Webhook adresse"
},
"backup": {
"import-recipes": "Importer opskrifter",
"import-settings": "Importindstillinger",
"import-themes": "Importer temaer"
}
}
}

View File

@@ -0,0 +1,133 @@
{
"404": {
"page-not-found": "404 Page Not Found",
"take-me-home": "Take me Home"
},
"new-recipe": {
"from-url": "From URL",
"recipe-url": "Recipe URL",
"error-message": "Looks like there was an error parsing the URL. Check the log and debug/last_recipe.json to see what went wrong.",
"bulk-add": "Bulk Add",
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Paste in your recipe data. Each line will be treated as an item in a list"
},
"general": {
"submit": "Submit",
"name": "Name",
"settings": "Settings",
"close": "Close",
"save": "Save",
"image-file": "Image File",
"update": "Update",
"edit": "Edit",
"delete": "Delete",
"select": "Select",
"random": "Random",
"new": "New",
"create": "Create",
"cancel": "Cancel",
"ok": "OK",
"enabled": "Enabled",
"download": "Download",
"import": "Import",
"delete-data": "Delete Data"
},
"login": {
"stay-logged-in": "Stay logged in?",
"email": "Email",
"password": "Password",
"sign-in": "Sign in",
"sign-up": "Sign up"
},
"meal-plan": {
"dinner-this-week": "Dinner This Week",
"dinner-today": "Dinner Today",
"planner": "Planner",
"edit-meal-plan": "Edit Meal Plan",
"meal-plans": "Meal Plans",
"choose-a-recipe": "Choose a Recipe",
"create-a-new-meal-plan": "Create a New Meal Plan",
"start-date": "Start Date",
"end-date": "End Date"
},
"recipe": {
"description": "Description",
"ingredients": "Ingredients",
"categories": "Categories",
"tags": "Tags",
"instructions": "Instructions",
"step-index": "Step: {step}",
"recipe-name": "Recipe Name",
"servings": "Servings",
"ingredient": "Ingredient",
"notes": "Notes",
"note": "Note",
"original-recipe": "Original Recipe",
"view-recipe": "View Recipe"
},
"search": {
"search-for-a-recipe": "Search for a Recipe",
"search-for-your-favorite-recipe": "Search for your Favorite <strong>Recipe</strong>"
},
"settings": {
"color": "Color",
"swatches": "Swatches",
"add-a-new-theme": "Add a New Theme",
"set-new-time": "Set New Time",
"version-latest": "Version: { current } | Latest: { latest }",
"explore-the-docs": "Explore the Docs",
"contribute": "Contribute",
"backup-and-exports": "Backup and Exports",
"backup-info": "Backups are exported in standard JSON format along with all the images stored on the file system. In your backup folder you'll find a .zip file that contains all of the recipe JSON and images from the database. Additionally, if you selected a markdown file, those will also be stored in the .zip file. To import a backup, it must be located in your backups folder. Automated backups are done each day at 3:00 AM.",
"backup-tag": "Backup Tag",
"markdown-template": "Markdown Template",
"backup-recipes": "Backup Recipes",
"theme": {
"theme-settings": "Theme Settings",
"select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference": "Select a theme from the dropdown or create a new theme. Note that the default theme will be served to all users who have not set a theme preference.",
"dark-mode": "Dark Mode",
"theme-is-required": "Theme is required",
"primary": "Primary",
"secondary": "Secondary",
"accent": "Accent",
"success": "Success",
"info": "Info",
"warning": "Warning",
"error": "Error",
"light": "Light",
"dark": "Dark",
"theme": "Theme",
"saved-color-theme": "Saved Color Theme",
"delete-theme": "Delete Theme",
"are-you-sure-you-want-to-delete-this-theme": "Are you sure you want to delete this theme?",
"save-colors-and-apply-theme": "Save Colors and Apply Theme",
"choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme": "Choose how Mealie looks to you. Set your theme preference to follow your system settings, or choose to use the light or dark theme."
},
"webhooks": {
"meal-planner-webhooks": "Meal Planner Webhooks",
"the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "The URLs listed below will recieve webhooks containing the recipe data for the meal plan on it's scheduled day. Currently Webhooks will execute at <strong>{ time }</strong>",
"test-webhooks": "Test Webhooks",
"webhook-url": "Webhook URL",
"save-webhooks": "Save Webhooks"
},
"new-version-available": "A New Version of Mealie is Avaiable, <a {aContents}> Visit the Repo </a>",
"backup": {
"import-recipes": "Import Recipes",
"import-themes": "Import Themes",
"import-settings": "Import Settings"
}
},
"migration": {
"recipe-migration": "Recipe Migration",
"currently-chowdown-via-public-repo-url-is-the-only-supported-type-of-migration": "Currently Chowdown via public Repo URL is the only supported type of migration",
"chowdown-repo-url": "Chowdown Repo URL",
"migrate": "Migrate",
"failed-recipes": "Failed Recipes",
"failed-images": "Failed Images",
"you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected": "You can import recipes from either a zip file or a directory located in the /app/data/migraiton/ folder. Please review the documentation to ensure your directory structure matches what is expected",
"nextcloud-data": "Nextcloud Data",
"delete-confirmation": "Are you sure you want to delete this migration data?",
"successfully-imported-from-nextcloud": "Successfully Imported from Nextcloud",
"failed-imports": "Failed Imports",
"upload-an-archive": "Upload an Archive"
}
}

View File

@@ -4,6 +4,8 @@ import vuetify from "./plugins/vuetify";
import store from "./store/store"; import store from "./store/store";
import VueRouter from "vue-router"; import VueRouter from "vue-router";
import { routes } from "./routes"; import { routes } from "./routes";
import VueCookies from "vue-cookies";
import i18n from './i18n'
Vue.config.productionTip = false; Vue.config.productionTip = false;
Vue.use(VueRouter); Vue.use(VueRouter);
@@ -17,7 +19,8 @@ new Vue({
vuetify, vuetify,
store, store,
router, router,
render: (h) => h(App), i18n,
render: (h) => h(App)
}).$mount("#app"); }).$mount("#app");
// Truncate // Truncate

View File

@@ -5,9 +5,9 @@
<v-col> <v-col>
<v-card height=""> <v-card height="">
<v-card-text> <v-card-text>
<h1>404 No Page Found</h1> <h1>{{$t('404.page-not-found')}}</h1>
</v-card-text> </v-card-text>
<v-btn text block @click="$router.push('/')"> Take me Home </v-btn> <v-btn text block @click="$router.push('/')"> {{$t('404.take-me-home')}} </v-btn>
</v-card> </v-card>
</v-col> </v-col>
<v-col cols="2"></v-col> <v-col cols="2"></v-col>

View File

@@ -8,7 +8,7 @@
<NewMeal v-else @created="requestMeals" /> <NewMeal v-else @created="requestMeals" />
<v-card class="my-1"> <v-card class="my-1">
<v-card-title class="headline"> Meal Plans </v-card-title> <v-card-title class="headline"> {{$t('meal-plan.meal-plans')}} </v-card-title>
<v-divider></v-divider> <v-divider></v-divider>
<v-timeline align-top :dense="$vuetify.breakpoint.smAndDown"> <v-timeline align-top :dense="$vuetify.breakpoint.smAndDown">
@@ -50,7 +50,7 @@
text text
@click="editPlan(mealplan.uid)" @click="editPlan(mealplan.uid)"
> >
Edit {{$t('general.edit')}}
</v-btn> </v-btn>
<v-btn <v-btn
color="error lighten-2" color="error lighten-2"
@@ -58,7 +58,7 @@
text text
@click="deletePlan(mealplan.uid)" @click="deletePlan(mealplan.uid)"
> >
Delete {{$t('general.delete')}}
</v-btn> </v-btn>
</v-row> </v-row>
</v-card-text> </v-card-text>

View File

@@ -22,14 +22,16 @@
<v-card-text> {{ meal.description }} </v-card-text> <v-card-text> {{ meal.description }} </v-card-text>
<v-btn <v-card-actions>
align="center" <v-btn
color="secondary" align="center"
text color="secondary"
@click="$router.push(`/recipe/${meal.slug}`)" text
> @click="$router.push(`/recipe/${meal.slug}`)"
View Recipe >
</v-btn> {{$t('recipe.view-recipe')}}
</v-btn>
</v-card-actions>
</v-card> </v-card>
</v-col> </v-col>
<v-col order-sm="0" :order-md="getOrder(index)" md="6" sm="12"> <v-col order-sm="0" :order-md="getOrder(index)" md="6" sm="12">

View File

@@ -1,30 +1,23 @@
<template> <template>
<v-container> <v-container>
<v-alert v-if="newVersion" color="green" type="success" outlined> <v-alert v-if="newVersion" color="green" type="success" outlined v-html="$t('settings.new-version-available', { aContents: 'target=\'_blank\' href=\'https://github.com/hay-kot/mealie\' class=\'green--text\''})">
A New Version of Mealie is Avaiable,
<a
href="https://github.com/hay-kot/mealie/releases/latest"
target="_blank"
class="green--text"
>
Visit the Repo
</a>
</v-alert> </v-alert>
<Theme /> <Theme />
<Backup class="mt-2" /> <Backup class="mt-2" />
<Webhooks class="mt-2" /> <Webhooks class="mt-2" />
<Migration class="mt-2" /> <Migration class="mt-2" />
<p class="text-center my-2"> <p class="text-center my-2">
Version: {{ version }} | Latest: {{ latestVersion }} · {{$t('settings.version-latest', {current: version, latest: latestVersion})}} ·
<a href="https://hay-kot.github.io/mealie/" target="_blank"> <a href="https://hay-kot.github.io/mealie/" target="_blank">
Explore the Docs {{$t('settings.explore-the-docs')}}
</a> </a>
· ·
<a <a
href="https://hay-kot.github.io/mealie/2.1%20-%20Contributions/" href="https://hay-kot.github.io/mealie/2.1%20-%20Contributions/"
target="_blank" target="_blank"
> >
Contribute {{$t('settings.contribute')}}
</a> </a>
</p> </p>
</v-container> </v-container>

View File

@@ -10,4 +10,12 @@ module.exports = {
}, },
}, },
}, },
pluginOptions: {
i18n: {
locale: 'en',
fallbackLocale: 'en',
localeDir: 'locales',
enableInSFC: true
}
}
}; };