Add i18n, fix #1
This commit is contained in:
parent
f042214c92
commit
1c4053de8e
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<v-dialog v-if="error" v-model="error" max-width="500px">
|
||||
<v-card>
|
||||
<v-card-title class="headline">Error</v-card-title>
|
||||
<v-card-title class="headline">{{ $t('error.title') }}</v-card-title>
|
||||
<v-card-text>
|
||||
{{ description }} {{ value.message }}.
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="secondary" flat @click.stop="error=null">Cancel</v-btn>
|
||||
<v-btn color="secondary" flat @click.stop="error=null">{{ $t('misc.cancel') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
@ -2,10 +2,10 @@
|
||||
<v-container fluid grid-list-md>
|
||||
<Loader v-if="isLoading"></Loader>
|
||||
<v-layout row wrap v-else>
|
||||
<ErrorDialog :v-model="error" description="Unable to load recipes: " />
|
||||
<ErrorDialog :v-model="error" :description="$t('error.unable_load_recipes')" />
|
||||
|
||||
<v-flex xs12 v-if="!recipes.length" class="text-xs-center">
|
||||
<p>Start by adding a recipe with the "+" button on the top right corner!</p>
|
||||
<p>{{ $t('home.onboarding') }}</p>
|
||||
</v-flex>
|
||||
<v-flex
|
||||
v-for="recipe in recipes"
|
||||
@ -21,10 +21,10 @@
|
||||
<p>{{ recipe.short_description }}</p>
|
||||
<v-layout row text-xs-center>
|
||||
<v-flex xs6>
|
||||
<p><v-icon>timelapse</v-icon> {{ recipe.preparation_time }} mins</p>
|
||||
<p><v-icon>timelapse</v-icon> {{ $tc('misc.Nmins', recipe.preparation_time, { count: recipe.preparation_time }) }}</p>
|
||||
</v-flex>
|
||||
<v-flex xs6>
|
||||
<p><v-icon>whatshot</v-icon> {{ recipe.cooking_time }} mins</p>
|
||||
<p><v-icon>whatshot</v-icon> {{ $tc('misc.Nmins', recipe.cooking_time, { count: recipe.cooking_time }) }}</p>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-card>
|
||||
|
@ -4,16 +4,16 @@
|
||||
<Loader></Loader>
|
||||
|
||||
<v-flex xs12>
|
||||
<p>Importing...</p>
|
||||
<p>{{ $t('new.importing') }}</p>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
<v-container text-xs-center v-else>
|
||||
<v-layout row wrap>
|
||||
<ErrorDialog v-model="error" description="Unable to import recipe: " />
|
||||
<ErrorDialog v-model="error" :description="$t('error.unable_import_recipe')" />
|
||||
|
||||
<v-flex xs12>
|
||||
<h2>Import from URL</h2>
|
||||
<h2>{{ $t('new.import_from_url') }}</h2>
|
||||
<v-form v-model="validImport">
|
||||
<v-text-field
|
||||
label="URL"
|
||||
@ -25,61 +25,61 @@
|
||||
@click="submitImport"
|
||||
:disabled="!validImport || isImporting"
|
||||
>
|
||||
Import
|
||||
{{ $t('new.import') }}
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<v-layout row wrap mt-5 v-if="featureAddManually">
|
||||
<v-flex xs12>
|
||||
<h2>Add manually</h2>
|
||||
<h2>{{ $t('new.add_manually') }}</h2>
|
||||
<v-form v-model="validAdd">
|
||||
<v-text-field
|
||||
label="Title"
|
||||
:label="$t('new.title')"
|
||||
v-model="title"
|
||||
required
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
label="Picture"
|
||||
:label="$t('new.picture')"
|
||||
v-model="picture"
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
label="Short description"
|
||||
:label="$t('new.short_description')"
|
||||
v-model="short_description"
|
||||
textarea
|
||||
></v-text-field>
|
||||
<v-layout row>
|
||||
<v-flex xs4 mr-3>
|
||||
<v-text-field
|
||||
label="Number of persons"
|
||||
:label="$t('new.nb_persons')"
|
||||
v-model="nb_person"
|
||||
type="number"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
<v-flex xs4 mx-3>
|
||||
<v-text-field
|
||||
label="Preparation time"
|
||||
:label="$t('new.preparation_time')"
|
||||
v-model="preparation_time"
|
||||
type="number"
|
||||
suffix="mins"
|
||||
:suffix="$t('new.mins')"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
<v-flex xs4 ml-3>
|
||||
<v-text-field
|
||||
label="Cooking time"
|
||||
:label="$t('new.cooking_time')"
|
||||
v-model="cooking_time"
|
||||
type="number"
|
||||
suffix="mins"
|
||||
:suffix="$t('new.mins')"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<v-text-field
|
||||
label="Ingredients"
|
||||
:label="$t('new.ingredients')"
|
||||
v-model="ingredients"
|
||||
textarea
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
label="Instructions"
|
||||
:label="$t('new.instructions')"
|
||||
v-model="instructions"
|
||||
textarea
|
||||
required
|
||||
@ -89,7 +89,7 @@
|
||||
@click="submitAdd"
|
||||
:disabled="!validAdd"
|
||||
>
|
||||
Add
|
||||
{{ $t('new.add') }}
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</v-flex>
|
||||
|
@ -8,26 +8,26 @@
|
||||
|
||||
<v-dialog v-model="refetchConfirm" max-width="500px">
|
||||
<v-card>
|
||||
<v-card-title class="headline">Refetch recipe</v-card-title>
|
||||
<v-card-title class="headline">{{ $t('recipe.refetch_recipe') }}</v-card-title>
|
||||
<v-card-text>
|
||||
This will refetch the recipe from the website and replace all current data with newly fetched ones. Are you sure?
|
||||
{{ $t('recipe.refetch_recipe_description') }}
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="secondary" flat @click.stop="refetchConfirm=false">Cancel</v-btn>
|
||||
<v-btn color="error" flat @click.stop="handleRefetch">Refetch</v-btn>
|
||||
<v-btn color="secondary" flat @click.stop="refetchConfirm=false">{{ $t('misc.cancel') }}</v-btn>
|
||||
<v-btn color="error" flat @click.stop="handleRefetch">{{ $t('recipe.refetch') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="deleteConfirm" max-width="500px">
|
||||
<v-card>
|
||||
<v-card-title class="headline">Delete recipe</v-card-title>
|
||||
<v-card-text>This will delete this recipe. Are you sure?</v-card-text>
|
||||
<v-card-title class="headline">{{ $t('recipe.delete_recipe') }}</v-card-title>
|
||||
<v-card-text>{{ $t('recipe.delete_recipe_description') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="secondary" flat @click.stop="deleteConfirm=false">Cancel</v-btn>
|
||||
<v-btn color="error" flat @click.stop="handleDelete">Delete</v-btn>
|
||||
<v-btn color="secondary" flat @click.stop="deleteConfirm=false">{{ $t('misc.cancel') }}</v-btn>
|
||||
<v-btn color="error" flat @click.stop="handleDelete">{{ $t('recipe.delete') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
@ -46,29 +46,29 @@
|
||||
<p>{{ recipe.nb_person }}</p>
|
||||
</v-flex>
|
||||
<v-flex xs6>
|
||||
<p><v-icon>timelapse</v-icon> Preparation: {{ recipe.preparation_time }} mins</p>
|
||||
<p><v-icon>whatshot</v-icon> Cooking: {{ recipe.cooking_time }} mins</p>
|
||||
<p><v-icon>timelapse</v-icon> {{ $t('recipe.preparation') }} {{ $tc('misc.Nmins', recipe.preparation_time, { count: recipe.preparation_time }) }}</p>
|
||||
<p><v-icon>whatshot</v-icon> {{ $t('recipe.cooking') }} {{ $tc('misc.Nmins', recipe.cooking_time, { count: recipe.cooking_time }) }}</p>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<p>{{ recipe.short_description }}</p>
|
||||
<h2>Ingredients</h2>
|
||||
<h2>{{ $t('recipe.ingredients') }}</h2>
|
||||
<ul class="ml-5">
|
||||
<li v-for="ingredient in recipe.ingredients">
|
||||
{{ ingredient }}
|
||||
</li>
|
||||
</ul>
|
||||
<h2 class="mt-3">Instructions</h2>
|
||||
<h2 class="mt-3">{{ $t('recipe.instructions') }}</h2>
|
||||
<p v-for="item in recipe.instructions">
|
||||
{{ item }}
|
||||
</p>
|
||||
<p v-if="recipe.url" class="text-xs-center">
|
||||
<v-btn :href="recipe.url">
|
||||
<v-btn :href="recipe.url" :title="$t('recipe.website')">
|
||||
<v-icon class="fa-icon">fa-external-link</v-icon>
|
||||
</v-btn>
|
||||
<v-btn @click.stop="deleteConfirm = true">
|
||||
<v-btn @click.stop="deleteConfirm = true" :title="$t('recipe.delete')">
|
||||
<v-icon>delete</v-icon>
|
||||
</v-btn>
|
||||
<v-btn @click.stop="refetchConfirm = true">
|
||||
<v-btn @click.stop="refetchConfirm = true" :title="$t('recipe.refetch')">
|
||||
<v-icon>autorenew</v-icon>
|
||||
</v-btn>
|
||||
</p>
|
||||
|
44
cuizin/js_src/helpers.js
Normal file
44
cuizin/js_src/helpers.js
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Get all locales supported by the client browser.
|
||||
*/
|
||||
export function getBrowserLocales() {
|
||||
let langs = [];
|
||||
|
||||
if (navigator.languages) {
|
||||
// Chrome does not currently set navigator.language correctly
|
||||
// https://code.google.com/p/chromium/issues/detail?id=101138
|
||||
// but it does set the first element of navigator.languages correctly
|
||||
langs = navigator.languages;
|
||||
} else if (navigator.userLanguage) {
|
||||
// IE only
|
||||
langs = [navigator.userLanguage];
|
||||
} else {
|
||||
// as of this writing the latest version of firefox + safari set this correctly
|
||||
langs = [navigator.language];
|
||||
}
|
||||
|
||||
// Some browsers does not return uppercase for second part
|
||||
const locales = langs.map((lang) => {
|
||||
const locale = lang.split('-');
|
||||
return locale[1] ? `${locale[0]}-${locale[1].toUpperCase()}` : lang;
|
||||
});
|
||||
|
||||
return locales;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the best matching locale from the browser preferred locales.
|
||||
*
|
||||
* @param messages A translation object with supported locales as keys.
|
||||
* @param defaultLocale An optional default locale.
|
||||
* @return The best locale to use.
|
||||
*/
|
||||
export function getBestMatchingLocale(messages, defaultLocale = 'en') {
|
||||
const locales = getBrowserLocales();
|
||||
|
||||
let bestLocale = defaultLocale;
|
||||
// Get best matching locale
|
||||
locales.some(locale => (messages[locale] && (bestLocale = locale)));
|
||||
return bestLocale;
|
||||
}
|
43
cuizin/js_src/i18n/en/index.js
Normal file
43
cuizin/js_src/i18n/en/index.js
Normal file
@ -0,0 +1,43 @@
|
||||
export default {
|
||||
error: {
|
||||
title: 'Error',
|
||||
unable_import_recipe: 'Unable to import recipe: ',
|
||||
unable_load_recipes: 'Unable to load recipes: ',
|
||||
},
|
||||
home: {
|
||||
onboarding: 'Start by adding a recipe with the "+" button on the top right corner!',
|
||||
},
|
||||
misc: {
|
||||
cancel: 'Cancel',
|
||||
Nmins: '1 min | {count} mins',
|
||||
},
|
||||
new: {
|
||||
add: 'Add',
|
||||
add_manually: 'Add manually',
|
||||
cooking_time: 'Cooking time',
|
||||
import: 'Import',
|
||||
import_from_url: 'Import from URL',
|
||||
importing: 'Importing…',
|
||||
ingredients: 'Ingredients',
|
||||
instructions: 'Instructions',
|
||||
mins: 'mins',
|
||||
nb_opersons: 'Number of persons',
|
||||
picture: 'Picture',
|
||||
preparation_time: 'Preparation time',
|
||||
short_description: 'Short description',
|
||||
title: 'Title',
|
||||
},
|
||||
recipe: {
|
||||
cooking: 'Cooking:',
|
||||
delete: 'Delete',
|
||||
delete_recipe: 'Delete recipe',
|
||||
delete_recipe_description: 'This will delete this recipe. Are you sure?',
|
||||
ingredients: 'Ingredients',
|
||||
instructions: 'Instructions',
|
||||
preparation: 'Preparation:',
|
||||
refetch: 'Refetch',
|
||||
refetch_recipe: 'Refetch recipe',
|
||||
refetch_recipe_description: 'This will refetch the recipe from the website and replace all current data with newly fetched ones. Are you sure?',
|
||||
website: 'Recipe web page',
|
||||
},
|
||||
};
|
43
cuizin/js_src/i18n/fr/index.js
Normal file
43
cuizin/js_src/i18n/fr/index.js
Normal file
@ -0,0 +1,43 @@
|
||||
export default {
|
||||
error: {
|
||||
title: 'Erreur',
|
||||
unable_import_recipe: 'Impossible d\'importer la recette : ',
|
||||
unable_load_recipes: 'Impossible de charger les recettes : ',
|
||||
},
|
||||
home: {
|
||||
onboarding: 'Commencez par ajouter une recette avec le bouton "+" dans le coin supérieur droit !',
|
||||
},
|
||||
misc: {
|
||||
cancel: 'Annuler',
|
||||
Nmins: '1 min | {count} mins',
|
||||
},
|
||||
new: {
|
||||
add: 'Ajouter',
|
||||
add_manually: 'Ajouter manuellement',
|
||||
cooking_time: 'Temps de cuisson',
|
||||
import: 'Importer',
|
||||
import_from_url: 'Importer depuis l\'URL',
|
||||
importing: 'En cours d\'import…',
|
||||
ingredients: 'Ingrédients',
|
||||
instructions: 'Instructions',
|
||||
mins: 'mins',
|
||||
nb_opersons: 'Nombre de personnes',
|
||||
picture: 'Photo',
|
||||
preparation_time: 'Temps de préparation',
|
||||
short_description: 'Description brève',
|
||||
title: 'Titre',
|
||||
},
|
||||
recipe: {
|
||||
cooking: 'Cuisson :',
|
||||
delete: 'Supprimer',
|
||||
delete_recipe: 'Suppression d\'une recette',
|
||||
delete_recipe_description: 'Vous allez supprimer cette recette. En êtes-vous sûr ?',
|
||||
ingredients: 'Ingrédients',
|
||||
instructions: 'Instructions',
|
||||
preparation: 'Préparation :',
|
||||
refetch: 'Réactualiser',
|
||||
refetch_recipe: 'Réactualiser la recette',
|
||||
refetch_recipe_description: 'Vous allez remplacer le contenu de cette recette par des données à jour récupérées depuis le site web de la recette. En êtes-vous sûr ?',
|
||||
website: 'Page de la recette',
|
||||
},
|
||||
};
|
7
cuizin/js_src/i18n/index.js
Normal file
7
cuizin/js_src/i18n/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
import en from './en';
|
||||
import fr from './fr';
|
||||
|
||||
export default {
|
||||
en,
|
||||
fr,
|
||||
};
|
@ -2,25 +2,35 @@
|
||||
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
||||
import Vue from 'vue';
|
||||
import Vuetify from 'vuetify';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import 'roboto-fontface/css/roboto/roboto-fontface.css';
|
||||
import 'font-awesome/css/font-awesome.css';
|
||||
import 'material-design-icons/iconfont/material-icons.css';
|
||||
import 'vuetify/dist/vuetify.min.css';
|
||||
|
||||
import App from './App';
|
||||
import messages from './i18n';
|
||||
import router from './router';
|
||||
import { getBestMatchingLocale } from './helpers';
|
||||
|
||||
// Isomorphic fetch
|
||||
require('es6-promise').polyfill();
|
||||
require('isomorphic-fetch');
|
||||
|
||||
Vue.use(Vuetify);
|
||||
Vue.use(VueI18n);
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: getBestMatchingLocale(messages),
|
||||
messages,
|
||||
});
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: '#app',
|
||||
i18n,
|
||||
router,
|
||||
components: { App },
|
||||
template: '<App/>',
|
||||
|
@ -17,6 +17,7 @@
|
||||
"material-design-icons": "^3.0.1",
|
||||
"roboto-fontface": "^0.9.0",
|
||||
"vue": "^2.5.2",
|
||||
"vue-i18n": "^7.4.2",
|
||||
"vue-router": "^3.0.1",
|
||||
"vuetify": "^1.0.0"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user