diff --git a/cookbook/templates/frontend/tandoor.html b/cookbook/templates/frontend/tandoor.html
index c2edcc442..7ed82de93 100644
--- a/cookbook/templates/frontend/tandoor.html
+++ b/cookbook/templates/frontend/tandoor.html
@@ -1,12 +1,15 @@
{% load django_vite %}
+{% load i18n %}
+{% get_current_language as LANGUAGE_CODE %}
+
{% load theming_tags %}
{% load custom_tags %}
{% theme_values request as theme_values %}
-
+
Tandoor
diff --git a/vue3/src/apps/tandoor/Tandoor.vue b/vue3/src/apps/tandoor/Tandoor.vue
index 17438e476..7861d53aa 100644
--- a/vue3/src/apps/tandoor/Tandoor.vue
+++ b/vue3/src/apps/tandoor/Tandoor.vue
@@ -34,6 +34,7 @@
+ Test: {{$t('No_Results')}}
diff --git a/vue3/src/apps/tandoor/main.ts b/vue3/src/apps/tandoor/main.ts
index ec7540146..22fca1af0 100644
--- a/vue3/src/apps/tandoor/main.ts
+++ b/vue3/src/apps/tandoor/main.ts
@@ -1,6 +1,8 @@
import {createApp} from "vue";
import {createRouter, createWebHashHistory} from 'vue-router'
import {createPinia} from 'pinia'
+import en from '../../locales/en.json'
+import de from '../../locales/de.json'
// @ts-ignore
import App from './Tandoor.vue'
@@ -15,6 +17,8 @@ import RecipeEditPage from "@/pages/RecipeEditPage.vue";
import MealPlanPage from "@/pages/MealPlanPage.vue";
import SearchPage from "@/pages/SearchPage.vue";
import TestPage from "@/pages/TestPage.vue";
+import {createI18n} from "vue-i18n";
+import {getLocale, loadLocaleMessages, setI18nLanguage, setupI18n, SUPPORT_LOCALES} from "@/i18n";
const routes = [
{path: '/', component: StartPage, name: 'view_home'},
@@ -33,12 +37,29 @@ const router = createRouter({
routes,
})
+let locale = document.querySelector('html')!.getAttribute('lang')
+if (locale == null || !SUPPORT_LOCALES.includes(locale)) {
+ console.log('falling back to en because ', locale, ' is not supported as a locale')
+ locale = 'en'
+}
+const localeMessages = await import((`../../locales/${locale}.json`))
+console.log(localeMessages, de)
+const i18n = setupI18n({
+ legacy: false,
+ locale: locale,
+ fallbackLocale: 'en',
+ messages: {
+ en, de
+ }
+})
+
+
const app = createApp(App)
app.use(createPinia())
app.use(vuetify)
app.use(router)
-app.use(luxonPlugin)
+app.use(i18n)
app.use(mavonEditor) // TODO only use on pages that need it
app.mount('#app')
diff --git a/vue3/src/components/inputs/ModelSelect.vue b/vue3/src/components/inputs/ModelSelect.vue
index b9db0d2cd..a93d114e0 100644
--- a/vue3/src/components/inputs/ModelSelect.vue
+++ b/vue3/src/components/inputs/ModelSelect.vue
@@ -55,10 +55,14 @@ const props = defineProps({
allowCreate: {type: Boolean, default: false},
+ placeholder: {type: String, default: undefined},
+ noOptionsText: {type: String, default: undefined},
+ noResultsText: {type: String, default: undefined},
+
// not verified
search_on_load: {type: Boolean, default: false},
- placeholder: {type: String, default: undefined},
+
parent_variable: {type: String, default: undefined},
sticky_options: {
diff --git a/vue3/src/i18n.ts b/vue3/src/i18n.ts
new file mode 100644
index 000000000..458e047cd
--- /dev/null
+++ b/vue3/src/i18n.ts
@@ -0,0 +1,100 @@
+import {nextTick, isRef} from 'vue'
+import {createI18n} from 'vue-i18n'
+
+import type {
+ I18n,
+ I18nOptions,
+ Locale,
+ VueI18n,
+ Composer,
+ I18nMode
+} from 'vue-i18n'
+
+/**
+ * lazy loading of translation, resources:
+ * https://vue-i18n.intlify.dev/guide/advanced/lazy.html
+ * https://github.com/intlify/vue-i18n/blob/master/examples/lazy-loading/vite/src/i18n.ts
+ */
+
+// TODO not sure why/if this is needed, comment out for now and see if anything breaks
+export const SUPPORT_LOCALES = getSupportedLocales()
+
+function getSupportedLocales() {
+ let supportedLocales: string[] = []
+ let localeFiles = import.meta.glob('@/locales/*.json');
+ for (const path in localeFiles) {
+ supportedLocales.push(path.split('/').slice(-1)[0].split('.')[0]);
+ }
+ return supportedLocales
+}
+
+function isComposer(
+ instance: VueI18n | Composer,
+ mode: I18nMode
+): instance is Composer {
+ return mode === 'composition' && isRef(instance.locale)
+}
+
+export function setupI18n2() {
+ let locale = document.querySelector('html')!.getAttribute('lang')
+ if (locale == null || !SUPPORT_LOCALES.includes(locale)) {
+ locale = 'en'
+ }
+
+ const i18n = createI18n({locale: locale})
+
+
+
+ return i18n
+}
+
+export function getLocale(i18n: I18n): string {
+ if (isComposer(i18n.global, i18n.mode)) {
+ return i18n.global.locale.value
+ } else {
+ return i18n.global.locale
+ }
+}
+
+export function setLocale(i18n: I18n, locale: Locale): void {
+ if (isComposer(i18n.global, i18n.mode)) {
+ i18n.global.locale.value = locale
+ } else {
+ i18n.global.locale = locale
+ }
+}
+
+export function setupI18n(options: I18nOptions = {locale: 'en'}): I18n {
+ const i18n = createI18n(options)
+ setI18nLanguage(i18n, options.locale!)
+ return i18n
+}
+
+export function setI18nLanguage(i18n: I18n, locale: Locale): void {
+ setLocale(i18n, locale)
+ /**
+ * NOTE:
+ * If you need to specify the language setting for headers, such as the `fetch` API, set it here.
+ * The following is an example for axios.
+ *
+ * axios.defaults.headers.common['Accept-Language'] = locale
+ */
+
+ // should be done by django
+ // document.querySelector('html')!.setAttribute('lang', locale)
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const getResourceMessages = (r: any) => r.default || r
+
+export async function loadLocaleMessages(i18n: I18n, locale: Locale) {
+ // load locale messages
+ const messages = await import(`./locales/${locale}.json`).then(
+ getResourceMessages
+ )
+
+ // set locale and locale message
+ i18n.global.setLocaleMessage(locale, messages)
+
+ return nextTick()
+}
\ No newline at end of file
diff --git a/vue3/src/locales b/vue3/src/locales
new file mode 120000
index 000000000..16df897e5
--- /dev/null
+++ b/vue3/src/locales
@@ -0,0 +1 @@
+../../vue/src/locales/
\ No newline at end of file