Skip to content

Commit

Permalink
feat(i18n): add i18n configuration (French translation + language sel…
Browse files Browse the repository at this point in the history
…ector in user settings) (#154)

* feat(i18n): added config

* feat(i18n): simplified UserSettings + rm unused files

* Cleanup

* Additional translations

---------

Co-authored-by: dquirin <[email protected]>
Co-authored-by: Raphael Odini <[email protected]>
  • Loading branch information
3 people authored Jan 17, 2024
1 parent 484f092 commit fe3f54d
Show file tree
Hide file tree
Showing 37 changed files with 1,383 additions and 130 deletions.
2 changes: 2 additions & 0 deletions .env.local
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
VITE_OPEN_PRICES_API_URL = "http://127.0.0.1:8000/api/v1"
VITE_MANIFEST_PATH = "/manifest.local.json"
VITE_DEFAULT_LOCALE = en
VITE_FALLBACK_LOCALE = en
2 changes: 2 additions & 0 deletions .env.preprod
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
VITE_OPEN_PRICES_API_URL = "https://prices.openfoodfacts.net/api/v1"
VITE_MANIFEST_PATH = "/app/manifest.json"
VITE_DEFAULT_LOCALE = en
VITE_FALLBACK_LOCALE = en
2 changes: 2 additions & 0 deletions .env.prod
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
VITE_OPEN_PRICES_API_URL = "https://prices.openfoodfacts.org/api/v1"
VITE_MANIFEST_PATH = "/app/manifest.json"
VITE_DEFAULT_LOCALE = en
VITE_FALLBACK_LOCALE = en
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@intlify/unplugin-vue-i18n": "^2.0.0",
"@vue-leaflet/vue-leaflet": "^0.10.1",
"@vueuse/core": "^10.6.1",
"@vueuse/integrations": "^10.6.1",
Expand All @@ -20,6 +21,7 @@
"pinia-plugin-persistedstate": "^3.2.1",
"universal-cookie": "^6.1.1",
"vue": "^3.3.9",
"vue-i18n": "9",
"vue-router": "^4.2.5",
"vuetify": "^3.4.6"
},
Expand Down
11 changes: 8 additions & 3 deletions src/components/BarcodeScanner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
<v-dialog persistent>
<v-card>
<v-card-title>
Scan a barcode
<v-btn style="float:right;" variant="text" density="compact" icon="mdi-close" @click="close"></v-btn>
{{ $t('BarcodeScanner.Scan') }} <v-btn style="float:right;" variant="text" density="compact" icon="mdi-close" @click="close"></v-btn>
</v-card-title>

<v-divider></v-divider>
Expand All @@ -15,7 +14,13 @@
<v-divider></v-divider>

<v-card-text>
<div class="float-right">powered by <a href="https://github.com/mebjas/html5-qrcode" target="_blank">html5-qrcode</a></div>
<div class="float-right">
<i18n-t keypath="BarcodeScanner.Htlm5-qrcode.Text" tag="p">
<template #url>
<a href="https://github.com/mebjas/html5-qrcode" target="_blank">html5-qrcode</a>
</template>
</i18n-t>
</div>
</v-card-text>
</v-card>
</v-dialog>
Expand Down
8 changes: 5 additions & 3 deletions src/components/Footer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
<v-footer class="bg-grey-lighten-1" style="max-height: 20%">
<v-row>
<v-col cols="12" md="6" align="center">
Open Prices is an <a href="https://world.openfoodfacts.org" target="_blank">Open Food Facts</a> project.
<i18n-t keypath="Footer.Open_Prices_Project.Text" tag="label" for="Footer.Open_Prices_Project.url">
<a href="https://world.openfoodfacts.org" target="_blank">{{ $t('Footer.Open_Prices_Project.Url') }}</a>
</i18n-t>
</v-col>
<v-col cols="12" md="6" align="center">
<v-btn class="mx-2" variant="text" href="https://prices.openfoodfacts.org" target="_blank">About</v-btn>
<v-btn class="mx-2" variant="text" to="/stats">Stats</v-btn>
<v-btn class="mx-2" variant="text" href="https://prices.openfoodfacts.org" target="_blank">{{ $t('Footer.About') }}</v-btn>
<v-btn class="mx-2" variant="text" to="/stats">{{ $t('Footer.Stats') }}</v-btn>
<v-btn class="mx-2" variant="text" href="https://github.com/openfoodfacts/open-prices-frontend" target="_blank">Github</v-btn>
</v-col>
</v-row>
Expand Down
11 changes: 5 additions & 6 deletions src/components/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
<v-app-bar-nav-icon @click.stop="showDrawerMenu = !showDrawerMenu"></v-app-bar-nav-icon>
<v-app-bar-title style="cursor:pointer" @click="$router.push('/')">
<img src="/favicon.svg" height="28" style="vertical-align:bottom">
Open Prices
</v-app-bar-title>
{{ $t('Header.Title') }} </v-app-bar-title>
<template v-slot:append>
<v-btn v-if="!username" to="/sign-in" icon="mdi-login"></v-btn>
<v-menu v-if="username">
Expand All @@ -14,9 +13,9 @@
<v-list>
<v-list-item :slim="true" prepend-icon="mdi-account" disabled>{{ username }}</v-list-item>
<v-divider></v-divider>
<v-list-item :slim="true" prepend-icon="mdi-view-dashboard-outline" to="/dashboard">Dashboard</v-list-item>
<v-list-item :slim="true" prepend-icon="mdi-cog-outline" to="/settings">Settings</v-list-item>
<v-list-item :slim="true" prepend-icon="mdi-logout" @click="signOut">Sign out</v-list-item>
<v-list-item :slim="true" prepend-icon="mdi-view-dashboard-outline" to="/dashboard">{{ $t('Header.Dashboard') }}</v-list-item>
<v-list-item :slim="true" prepend-icon="mdi-cog-outline" to="/settings">{{ $t('Header.Settings') }}</v-list-item>
<v-list-item :slim="true" prepend-icon="mdi-logout" @click="signOut">{{ $t('Header.Sign-out') }}</v-list-item>
</v-list>
</v-menu>
</template>
Expand Down Expand Up @@ -47,7 +46,7 @@ export default {
return this.$router.options.routes
.filter(r => r.meta && r.meta.drawerMenu)
.filter(r => this.username ? r.meta.requiresAuth !== false : !r.meta.requiresAuth)
.map((r => ({ title: r.meta.title, props: { 'prepend-icon': r.meta.icon, to: r.path }})))
.map((r => ({ title: this.$t(`Router.${r.meta.title}.Title`), props: { 'prepend-icon': r.meta.icon, to: r.path }})))
}
},
methods: {
Expand Down
32 changes: 23 additions & 9 deletions src/components/LocationSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
<v-dialog persistent>
<v-card>
<v-card-title>
Find your location
<v-btn style="float:right;" variant="text" density="compact" icon="mdi-close" @click="close"></v-btn>
{{ $t('LocationSelector.Title') }} <v-btn style="float:right;" variant="text" density="compact" icon="mdi-close" @click="close"></v-btn>
</v-card-title>

<v-divider></v-divider>
Expand All @@ -12,7 +11,7 @@
<v-form @submit.prevent="search">
<v-text-field
v-model="locationSearchForm.q"
label="Search by name and city"
:label="$t('LocationSelector.SearchByName')"
type="text"
append-inner-icon="mdi-magnify"
@click:append-inner="search"
Expand All @@ -26,7 +25,13 @@
<v-divider></v-divider>

<v-card-text v-if="results && Array.isArray(results)">
<h3>Results <small>{{ results.length }}</small></h3>
<h3>
<i18n-t keypath="LocationSelector.Result" tag="p">
<template v-slot:resultNumber>
<small>{{ results.length }}</small>
</template>
</i18n-t>
</h3>
<v-row>
<v-col cols="12" sm="6">
<v-card
Expand Down Expand Up @@ -62,7 +67,11 @@

<v-card-text v-if="recentLocations.length">
<h3 class="mb-1">
Recent locations <small>{{ recentLocations.length }}</small>
<i18n-t keypath="LocationSelector.RecentLocations" tag="p">
<template v-slot:recentLocationNumber>
<small>{{ recentLocations.length }}</small>
</template>
</i18n-t>
</h3>
<v-chip
class="mb-2"
Expand All @@ -77,14 +86,19 @@
</v-chip>
<br />
<v-btn size="small" @click="clearRecentLocations">
Clear
</v-btn>
{{ $t('LocationSelector.Clear') }} </v-btn>
</v-card-text>

<v-divider v-if="recentLocations.length"></v-divider>

<v-card-text>
<div class="float-right">powered by <a href="https://nominatim.openstreetmap.org" target="_blank">OpenStreetMap Nominatim</a></div>
<div class="float-right">
<i18n-t keypath="LocationSelector.OSM.text" tag="p">
<template #url>
<a href="https://nominatim.openstreetmap.org" target="_blank">OpenStreetMap Nominatim</a>
</template>
</i18n-t>
</div>
</v-card-text>
</v-card>
</v-dialog>
Expand Down Expand Up @@ -155,7 +169,7 @@ export default {
this.mapBounds = null
}
} else {
this.results = 'No results found'
this.results = this.$t('LocationSelector.NoResult')
}
})
},
Expand Down
16 changes: 11 additions & 5 deletions src/components/PriceCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</span>
<span v-if="hasProductQuantity">
<v-chip label size="small" density="comfortable" class="mr-1">
{{ product.product_quantity }} g
{{ $t('PriceCard.ProductQuantity', [product.product_quantity]) }}
</v-chip>
</span>
<span v-if="hasPriceOrigin && priceOrigin">
Expand All @@ -37,7 +37,13 @@
<p>
<span>{{ getPriceValueDisplay() }}</span>
<span v-if="hasProductQuantity"> ({{ getPricePerKilo() }})</span>
<span> on <i>{{ getDateFormatted(price.date) }}</i></span>
<span>
<i18n-t keypath="PriceCard.PriceDate" tag="p">
<template v-slot:date>
<i>{{ getDateFormatted(price.date) }}</i>
</template>
</i18n-t>
</span>
</p>
</v-sheet>
</v-col>
Expand Down Expand Up @@ -158,7 +164,7 @@ export default {
} else if (this.hasPrice && this.hasCategoryTag) {
return this.getPriceCategoryName
}
return 'Unknown product'
return this.$t('PriceCard.UnknownProduct')
},
getPriceProductCode() {
if (this.hasProduct) {
Expand Down Expand Up @@ -190,14 +196,14 @@ export default {
},
getPriceValueDisplay() {
if (this.hasCategoryTag) {
return `${this.getPriceValue(this.priceValue, this.priceCurrency)} / kg`
return this.$t('PriceCard.PriceValueDisplay', [this.getPriceValue(this.priceValue, this.priceCurrency)])
}
return this.getPriceValue(this.priceValue, this.priceCurrency)
},
getPricePerKilo() {
const productQuantity = this.price.product.product_quantity
let pricePerKilo = (this.priceValue / productQuantity) * 1000
return `${this.getPriceValue(pricePerKilo, this.priceCurrency)} / kg`
return this.$t('PriceCard.PriceValueDisplay', [this.getPriceValue(pricePerKilo, this.priceCurrency)])
},
getPriceLocationTitle() {
if (this.price.location) {
Expand Down
4 changes: 2 additions & 2 deletions src/components/ProductCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</span>
<span v-if="hasProductQuantity">
<v-chip label size="small" density="comfortable" class="mr-1">
{{ product.product_quantity }} g
{{ $t('ProductCard.ProductQuantity', [product.product_quantity]) }}
</v-chip>
</span>
<span>
Expand Down Expand Up @@ -76,7 +76,7 @@ export default {
},
methods: {
getProductTitle() {
return this.product.product_name || 'Unknown product name'
return this.product.product_name || this.$t('ProductCard.UnknownProduct')
},
goToProduct() {
if (this.readonly) {
Expand Down
11 changes: 8 additions & 3 deletions src/constants.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions src/i18n/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createI18n } from 'vue-i18n'
import en from './locales/en.json'

const i18n = createI18n({
locale: import.meta.env.VITE_DEFAULT_LOCALE,
fallbackLocale: import.meta.env.VITE_FALLBACK_LOCALE,
legacy: false,
globalInjection: true,
messages: { en },
})

// React to language changes
i18n.onLanguageChanged = (newLocale, oldLocale) => {
console.log(`Language changed from ${oldLocale} to ${newLocale}`)
// You can perform additional actions here if needed
}
export default i18n
85 changes: 85 additions & 0 deletions src/i18n/localeManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import i18n from '@/i18n'
import { nextTick } from 'vue'
import constants from '../constants'

const localeManager = {
get defaultLocale() {
return import.meta.env.VITE_DEFAULT_LOCALE
},

get supportedLocales() {
return constants.LANGUAGE_LIST
},

get currentLocale() {
return i18n.global.locale.value
},

set currentLocale(newLocale) {
i18n.global.locale.value = newLocale
},

async changeLanguage(newLocale) {
await localeManager.loadLocaleMessages(newLocale)
localeManager.currentLocale = newLocale
document.querySelector('html').setAttribute('lang', newLocale)
localStorage.setItem('user-locale', newLocale)

},

async loadLocaleMessages(locale) {
if(!i18n.global.availableLocales.includes(locale)) {
const messages = await import(`@/i18n/locales/${locale}.json`)
i18n.global.setLocaleMessage(locale, messages.default)
}

return nextTick()
},

isLocaleSupported(locale) {
return localeManager.supportedLocales.some(lang => lang.code === locale)
},

getUserLocale() {
const locale = window.navigator.language ||
window.navigator.userLanguage ||
localeManager.defaultLocale

return {
locale: locale,
localeNoRegion: locale.split('-')[0]
}
},

getPersistedLocale() {
const persistedLocale = localStorage.getItem('user-locale')

if(localeManager.isLocaleSupported(persistedLocale)) {
return persistedLocale
} else {
return null
}
},

guessDefaultLocale() {
const userPersistedLocale = localeManager.getPersistedLocale()
if(userPersistedLocale) {
return userPersistedLocale
}

const userPreferredLocale = localeManager.getUserLocale()

if (localeManager.isLocaleSupported(userPreferredLocale.locale)) {
return userPreferredLocale.locale
}

if (localeManager.isLocaleSupported(userPreferredLocale.localeNoRegion)) {
return userPreferredLocale.localeNoRegion
}

return localeManager.defaultLocale
}

}

export default localeManager
Loading

0 comments on commit fe3f54d

Please sign in to comment.