HTML5-Tutorium: JavaScript: Hello World Vue 06: Unterschied zwischen den Versionen
Kowa (Diskussion | Beiträge) |
Kowa (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 18: | Zeile 18: | ||
Erstellen Sie einen neuen Projektzweig (branch) innerhalb von <code>hello_world_vue</code> und fügen Sie das Package <code>uuid</code> hinzu: | Erstellen Sie einen neuen Projektzweig (branch) innerhalb von <code>hello_world_vue</code> und fügen Sie das Package <code>uuid</code> hinzu: | ||
< | <syntaxhighlight lang="bash"> | ||
git checkout v05d # Wechsle in den Branch v05d mit Express-Server | git checkout v05d # Wechsle in den Branch v05d mit Express-Server | ||
git checkout -b v06 # Klone v05d in einen neuen Branch v06 | git checkout -b v06 # Klone v05d in einen neuen Branch v06 | ||
| Zeile 25: | Zeile 25: | ||
npm i | npm i | ||
npm i vue-router | npm i vue-router | ||
</ | </syntaxhighlight> | ||
In der Datei <code>public/json/config.jso</code> | In der Datei <code>public/json/config.jso</code> | ||
| Zeile 44: | Zeile 44: | ||
Dazu werden die Section-Komponenten in den Ordner View verschoben und umbenannt: | Dazu werden die Section-Komponenten in den Ordner View verschoben und umbenannt: | ||
< | <syntaxhighlight lang="bash"> | ||
src/view/ViewHello.vue | src/view/ViewHello.vue | ||
src/view/ViewHome.vue // an Stelle von ViewForm.vue | src/view/ViewHome.vue // an Stelle von ViewForm.vue | ||
src/view/ViewError404.vue // neu | src/view/ViewError404.vue // neu | ||
</ | </syntaxhighlight> | ||
Die CSS-Dateien müssen natürlich auch entsprechend verschoben und abgepasst werden. | Die CSS-Dateien müssen natürlich auch entsprechend verschoben und abgepasst werden. | ||
| Zeile 60: | Zeile 60: | ||
Die CSS-Dateien sollten analog umbenannt und unter den neuen Namen importiert werden. Für <code>ViewError404.vue</code> muss natürlich auch eine CSS-Datei angelegt werden. Diese verweist allerdings nur auf <code>_View.css</code>. | Die CSS-Dateien sollten analog umbenannt und unter den neuen Namen importiert werden. Für <code>ViewError404.vue</code> muss natürlich auch eine CSS-Datei angelegt werden. Diese verweist allerdings nur auf <code>_View.css</code>. | ||
< | <syntaxhighlight lang="html"> | ||
<!-- src/view/ViewError404.vue --> | <!-- src/view/ViewError404.vue --> | ||
| Zeile 78: | Zeile 78: | ||
@import '/css/view/ViewError404'; | @import '/css/view/ViewError404'; | ||
</style> | </style> | ||
</ | </syntaxhighlight> | ||
=== Router-Plugin === | === Router-Plugin === | ||
| Zeile 85: | Zeile 85: | ||
Dieses muss zunächst händisch auf Basis von <code>vue-router</code> implementiert werden. | Dieses muss zunächst händisch auf Basis von <code>vue-router</code> implementiert werden. | ||
< | <syntaxhighlight lang="ecmascript"> | ||
// src/router/index.js | // src/router/index.js | ||
| Zeile 110: | Zeile 110: | ||
export default router | export default router | ||
</ | </syntaxhighlight> | ||
Nun muss man das neu erstellte Plugin in die App integrieren. | Nun muss man das neu erstellte Plugin in die App integrieren. | ||
< | <syntaxhighlight lang="ecmascript"> | ||
// src/main.js | // src/main.js | ||
| Zeile 134: | Zeile 134: | ||
window.addEventListener('load', init) | window.addEventListener('load', init) | ||
</ | </syntaxhighlight> | ||
=== index.html === | === index.html === | ||
| Zeile 141: | Zeile 141: | ||
wird es aus der Datei <code>index.html</code> entfernt. | wird es aus der Datei <code>index.html</code> entfernt. | ||
< | <syntaxhighlight lang="html"> | ||
... | ... | ||
<body id="app"> | <body id="app"> | ||
</body> | </body> | ||
... | ... | ||
</ | </syntaxhighlight> | ||
=== App.vue === | === App.vue === | ||
| Zeile 152: | Zeile 152: | ||
Nun kann in die App eine Navigation eingebaut werden. | Nun kann in die App eine Navigation eingebaut werden. | ||
< | <syntaxhighlight lang="html"> | ||
<!-- /src/App.vue --> | <!-- /src/App.vue --> | ||
| Zeile 179: | Zeile 179: | ||
</style> | </style> | ||
</ | </syntaxhighlight> | ||
Die zugehörige Navigationskomponente muss auch noch implementiert werden. | Die zugehörige Navigationskomponente muss auch noch implementiert werden. | ||
< | <syntaxhighlight lang="html"> | ||
<!-- /src/component/AppNav.vue --> | <!-- /src/component/AppNav.vue --> | ||
| Zeile 201: | Zeile 201: | ||
@import '/css/component/AppNav'; | @import '/css/component/AppNav'; | ||
</style> | </style> | ||
</ | </syntaxhighlight> | ||
< | <syntaxhighlight> | ||
/* src/css/_config.css */ | /* src/css/_config.css */ | ||
... | ... | ||
| Zeile 248: | Zeile 248: | ||
} | } | ||
} | } | ||
</ | </syntaxhighlight> | ||
=== Navigation === | === Navigation === | ||
| Zeile 255: | Zeile 255: | ||
Das ist in der App.vue der Fall (siehe zuvor) sowie in der ViewHome.vue. | Das ist in der App.vue der Fall (siehe zuvor) sowie in der ViewHome.vue. | ||
< | <syntaxhighlight lang="html"> | ||
<!-- /src/vie/ViewHome.vue --> | <!-- /src/vie/ViewHome.vue --> | ||
| Zeile 272: | Zeile 272: | ||
dictionary = greeting.dictionary | dictionary = greeting.dictionary | ||
</script> | </script> | ||
</ | </syntaxhighlight> | ||
Die Section-Verwaltung der ehemaligen Single-Page-Anwendung kann und sollte gelöscht werden: | Die Section-Verwaltung der ehemaligen Single-Page-Anwendung kann und sollte gelöscht werden: | ||
| Zeile 286: | Zeile 286: | ||
Aufgabe: Die Ausgabesprache soll vom Benutzer zur Laufzeit geändert werden können. Die Sprachen sollen durch Bilder (Flaggen) symbolisiert werden. | Aufgabe: Die Ausgabesprache soll vom Benutzer zur Laufzeit geändert werden können. Die Sprachen sollen durch Bilder (Flaggen) symbolisiert werden. | ||
< | <syntaxhighlight lang="bash"> | ||
git checkout v06 # Wechsle in den Branch v06 | git checkout v06 # Wechsle in den Branch v06 | ||
git checkout -b v06a # Klone v06 in einen neuen Branch v06a | git checkout -b v06a # Klone v06 in einen neuen Branch v06a | ||
</ | </syntaxhighlight> | ||
=== <code>config.json</code> === | === <code>config.json</code> === | ||
| Zeile 295: | Zeile 295: | ||
Um die vorhandenen Sprachen durchblättern zu können, wird ein einfacher Automat verwendet. | Um die vorhandenen Sprachen durchblättern zu können, wird ein einfacher Automat verwendet. | ||
< | <syntaxhighlight lang="json"> | ||
// src/public/json/config.json | // src/public/json/config.json | ||
| Zeile 303: | Zeile 303: | ||
"nextLanguage": {"de": "en", "en": "de"} | "nextLanguage": {"de": "en", "en": "de"} | ||
} | } | ||
</ | </syntaxhighlight> | ||
=== StoreI18n.js: Integration von nextLanguage === | === StoreI18n.js: Integration von nextLanguage === | ||
| Zeile 310: | Zeile 310: | ||
Dazu sollte zunächst die zunächst Konstante <code>nextLanguage</code> definiert werden. | Dazu sollte zunächst die zunächst Konstante <code>nextLanguage</code> definiert werden. | ||
< | <syntaxhighlight lang="ecmascript"> | ||
... | ... | ||
lang = | lang = | ||
| Zeile 319: | Zeile 319: | ||
... | ... | ||
</ | </syntaxhighlight> | ||
Diese neue Konstante wird verwendet, um die Sprache mittels <code>changeLang</code> zu definieren, sofern | Diese neue Konstante wird verwendet, um die Sprache mittels <code>changeLang</code> zu definieren, sofern | ||
kein Argument übergeben wurde. | kein Argument übergeben wurde. | ||
< | <syntaxhighlight lang="ecmascript"> | ||
changeLang = | changeLang = | ||
async (p_lang = null) => | async (p_lang = null) => | ||
| Zeile 337: | Zeile 337: | ||
resolve(); | resolve(); | ||
} | } | ||
</ | </syntaxhighlight> | ||
=== Bilder von Flaggen === | === Bilder von Flaggen === | ||
| Zeile 344: | Zeile 344: | ||
Wir kopieren die Bilder im SVG-Format aus dem NPM-Paket "https://github.com/lipis/flag-icons". | Wir kopieren die Bilder im SVG-Format aus dem NPM-Paket "https://github.com/lipis/flag-icons". | ||
< | <syntaxhighlight lang="bash"> | ||
npm i -D flag-icons | npm i -D flag-icons | ||
</ | </syntaxhighlight> | ||
Im Ordner "node_modules/flag-icons/flags/4x3" finden Sie Flaggen zahlreicher Länder im 4-zu-3-Format. | Im Ordner "node_modules/flag-icons/flags/4x3" finden Sie Flaggen zahlreicher Länder im 4-zu-3-Format. | ||
| Zeile 353: | Zeile 353: | ||
In die Datei config.js wird der Pfad, unter dem die Bilder zu finden sind, eingefügt. | In die Datei config.js wird der Pfad, unter dem die Bilder zu finden sind, eingefügt. | ||
< | <syntaxhighlight lang="ecmascript"> | ||
// public/config.js | // public/config.js | ||
... | ... | ||
| Zeile 361: | Zeile 361: | ||
"XimgPath": "/api/$1/img", | "XimgPath": "/api/$1/img", | ||
... | ... | ||
</ | </syntaxhighlight> | ||
=== <code>FormButtonIcon</code> === | === <code>FormButtonIcon</code> === | ||
| Zeile 369: | Zeile 369: | ||
Das Bild kann zur Laufzeit geändert werden. | Das Bild kann zur Laufzeit geändert werden. | ||
< | <syntaxhighlight lang="ecmascript"> | ||
<script setup> | <script setup> | ||
import { computed, inject } from 'vue' | import { computed, inject } from 'vue' | ||
| Zeile 395: | Zeile 395: | ||
@import '/css/component/form/FormButtonIcon.css'; | @import '/css/component/form/FormButtonIcon.css'; | ||
</style> | </style> | ||
</ | </syntaxhighlight> | ||
< | <syntaxhighlight lang="ecmascript"> | ||
/* src/css/component/FormButtonIcon.css */ | /* src/css/component/FormButtonIcon.css */ | ||
| Zeile 406: | Zeile 406: | ||
margin-left: $nav-dist !important; | margin-left: $nav-dist !important; | ||
} | } | ||
</ | </syntaxhighlight> | ||
Fügen Sie in <code>src/css/_config.css</code> noch | Fügen Sie in <code>src/css/_config.css</code> noch | ||
< | <syntaxhighlight lang="ecmascript"> | ||
$button-icon-width: 1em; | $button-icon-width: 1em; | ||
</ | </syntaxhighlight> | ||
ein. | ein. | ||
| Zeile 423: | Zeile 423: | ||
folgende Attribute: | folgende Attribute: | ||
< | <syntaxhighlight lang="html"> | ||
... ..., | ... ..., | ||
"navHome": "Start", | "navHome": "Start", | ||
"navHello": "Hallo" | "navHello": "Hallo" | ||
</ | </syntaxhighlight> | ||
bzw. | bzw. | ||
< | <syntaxhighlight lang="html"> | ||
... ..., | ... ..., | ||
"navHome": "Home", | "navHome": "Home", | ||
"navHello": "Hello" | "navHello": "Hello" | ||
</ | </syntaxhighlight> | ||
Damit können Sie das Navigationsmenü internationalisieren (einschließlich Auswahl der Sprache per Button). | Damit können Sie das Navigationsmenü internationalisieren (einschließlich Auswahl der Sprache per Button). | ||
< | <syntaxhighlight lang="html"> | ||
<!-- src/component/AppNav --> | <!-- src/component/AppNav --> | ||
| Zeile 461: | Zeile 461: | ||
@import '/css/component/AppNav'; | @import '/css/component/AppNav'; | ||
</style> | </style> | ||
</ | </syntaxhighlight> | ||
== Hello World Vue 06b == | == Hello World Vue 06b == | ||
| Zeile 470: | Zeile 470: | ||
Legen Sie zunächst wieder einen neuen Branch an. | Legen Sie zunächst wieder einen neuen Branch an. | ||
< | <syntaxhighlight lang="bash"> | ||
git checkout v06a # Wechsle in den Branch v06a | git checkout v06a # Wechsle in den Branch v06a | ||
git checkout -b v06b # Klone v06a in einen neuen Branch v06b | git checkout -b v06b # Klone v06a in einen neuen Branch v06b | ||
</ | </syntaxhighlight> | ||
Das Vorgehen ist im Prinzip ganz einfach. | Das Vorgehen ist im Prinzip ganz einfach. | ||
| Zeile 484: | Zeile 484: | ||
Mögliche Erweiterung: Auch die HTML-Pfade sollten internationalisiert werden. | Mögliche Erweiterung: Auch die HTML-Pfade sollten internationalisiert werden. | ||
< | <syntaxhighlight lang="bash"> | ||
npm i navigator-languages // unterstützt mehrere Browservarianten | npm i navigator-languages // unterstützt mehrere Browservarianten | ||
</ | </syntaxhighlight> | ||
=== Language Matching === | === Language Matching === | ||
| Zeile 493: | Zeile 493: | ||
Zunächst wird überprüft, ob es exakte Übereinstimmungen gibt. Sollte das nicht der Fall sein, wird überprüft, ob es Matches zwischen den ersten beiden Buchstaben gibt ('de', 'de-AT', 'de-DE' matchen in diesem Schritt alle miteinander). | Zunächst wird überprüft, ob es exakte Übereinstimmungen gibt. Sollte das nicht der Fall sein, wird überprüft, ob es Matches zwischen den ersten beiden Buchstaben gibt ('de', 'de-AT', 'de-DE' matchen in diesem Schritt alle miteinander). | ||
< | <syntaxhighlight lang="ecmascript"> | ||
// src/util/chooseLanguage.js | // src/util/chooseLanguage.js | ||
| Zeile 550: | Zeile 550: | ||
export default chooseLanguage | export default chooseLanguage | ||
</ | </syntaxhighlight> | ||
=== <code>config.js</code> === | === <code>config.js</code> === | ||
| Zeile 556: | Zeile 556: | ||
Das Attribut <code>defaultLanguage</code> wird durch das Attribut <code>languages</code> ersetzt. | Das Attribut <code>defaultLanguage</code> wird durch das Attribut <code>languages</code> ersetzt. | ||
< | <syntaxhighlight lang="ecmascript"> | ||
{ "apiRoot": "/json/i18n_$1.json", | { "apiRoot": "/json/i18n_$1.json", | ||
"imgPath": "/img/$1.svg", | "imgPath": "/img/$1.svg", | ||
| Zeile 564: | Zeile 564: | ||
"nextLanguage": {"de": "en", "en": "de"} | "nextLanguage": {"de": "en", "en": "de"} | ||
} | } | ||
</ | </syntaxhighlight> | ||
=== <code>src/store/StoreI18n</code> === | === <code>src/store/StoreI18n</code> === | ||
< | <syntaxhighlight lang="ecmascript"> | ||
... | ... | ||
import chooseLanguage from '/util/chooseLanguage' | import chooseLanguage from '/util/chooseLanguage' | ||
| Zeile 583: | Zeile 583: | ||
... | ... | ||
... | ... | ||
</ | </syntaxhighlight> | ||
== Hello World Vue 06c == | == Hello World Vue 06c == | ||
| Zeile 590: | Zeile 590: | ||
geladen wird. | geladen wird. | ||
< | <syntaxhighlight lang="bash"> | ||
git checkout v06b # Wechsle in den Branch v06b | git checkout v06b # Wechsle in den Branch v06b | ||
git checkout -b v06c # Klone v06b in einen neuen Branch v06c | git checkout -b v06c # Klone v06b in einen neuen Branch v06c | ||
</ | </syntaxhighlight> | ||
Splitten Sie die Config-Datei zunächst in zwei Teile: "config.json" wird aus dem Public-Ordner geladen. | Splitten Sie die Config-Datei zunächst in zwei Teile: "config.json" wird aus dem Public-Ordner geladen. | ||
"config_backend.json" wird normalerweise vom Backend geladen, kann aber zum Entwicklungszeitpunkt auch vom Public-Ordner geladen werden, wenn das Backend noch nicht stabil läuft. | "config_backend.json" wird normalerweise vom Backend geladen, kann aber zum Entwicklungszeitpunkt auch vom Public-Ordner geladen werden, wenn das Backend noch nicht stabil läuft. | ||
< | <syntaxhighlight lang="json"> | ||
// src/public/json/config.json | // src/public/json/config.json | ||
| Zeile 609: | Zeile 609: | ||
"XimgPath": "/api/$1/img" | "XimgPath": "/api/$1/img" | ||
} | } | ||
</ | </syntaxhighlight> | ||
< | <syntaxhighlight lang="json"> | ||
// src/public/json/config_backend.json | // src/public/json/config_backend.json | ||
| Zeile 617: | Zeile 617: | ||
"nextLanguage": {"de": "en", "en": "de"} | "nextLanguage": {"de": "en", "en": "de"} | ||
} | } | ||
</ | </syntaxhighlight> | ||
Das Backend teilt dem Frontend mit, welche Sprachen unterstützt werden. | Das Backend teilt dem Frontend mit, welche Sprachen unterstützt werden. | ||
| Zeile 625: | Zeile 625: | ||
Laden Sie, nachdem Sie "config" asynchron geladen haben, "configBackend" asynchron und kopieren Sie den Inhalt dieses Objekts in das Config-Objekt. Den Pfad von "configBackend" finden Sie unter "config.configBackend". | Laden Sie, nachdem Sie "config" asynchron geladen haben, "configBackend" asynchron und kopieren Sie den Inhalt dieses Objekts in das Config-Objekt. Den Pfad von "configBackend" finden Sie unter "config.configBackend". | ||
< | <syntaxhighlight lang="ecmascript"> | ||
... | ... | ||
const | const | ||
| Zeile 642: | Zeile 642: | ||
.mount('#app') // app is shown to the user | .mount('#app') // app is shown to the user | ||
} | } | ||
</ | </syntaxhighlight> | ||
=== <code>storeI18n.js</code> === | === <code>storeI18n.js</code> === | ||
| Zeile 648: | Zeile 648: | ||
Die Datei "index.html" sollte bei jeder Änderung der Sprache angepasst werden. | Die Datei "index.html" sollte bei jeder Änderung der Sprache angepasst werden. | ||
< | <syntaxhighlight lang="json"> | ||
... | ... | ||
changeLang = | changeLang = | ||
| Zeile 657: | Zeile 657: | ||
} | } | ||
... | ... | ||
</ | </syntaxhighlight> | ||
<!-- | <!-- | ||
Version vom 29. April 2025, 09:48 Uhr
Dieser Artikel erfüllt die GlossarWiki-Qualitätsanforderungen nur teilweise:
| Korrektheit: 3 (zu größeren Teilen überprüft) |
Umfang: 4 (unwichtige Fakten fehlen) |
Quellenangaben: 3 (wichtige Quellen vorhanden) |
Quellenarten: 5 (ausgezeichnet) |
Konformität: 3 (gut) |
Inhalt | Teil 1 | Teil 2 | Teil 3 | Teil 4 | Teil 5 | Teil 6 | Vue 1 | Vue 2 | Vue 3 | Vue 4 | Vue 5 | Vue 6
Musterlösung
Git-Repository, git checkout v06, ... v06a, ... v06b
Anwendungsfälle (Use Cases)
Die Version des fünften Teils des Vue-Tutoriums wird so angepasst, dass die Sprache zur Laufzeit geändert werden kann.
Zunächst wird jedoch eine Navigationskomponente eingeführt. Dies hat den Zweck, das Routing an einem Beispiel zu erklären.
Aufgabe
Aufgabe: Konfigurieren und internationalisieren Sie die Anwendung mit Hilfe von dynamischen JSON-Dateien.
Erstellen eines neuen Projektzweigs
Erstellen Sie einen neuen Projektzweig (branch) innerhalb von hello_world_vue und fügen Sie das Package uuid hinzu:
git checkout v05d # Wechsle in den Branch v05d mit Express-Server
git checkout -b v06 # Klone v05d in einen neuen Branch v06
rm -rf express_Hello_world
cd frontend
npm i
npm i vue-router
In der Datei public/json/config.jso
aktivieren wir wieder den Zugriff auf die lokalen JSON-Dateien.
Den Zugriff auf den Backend-Server, um die Bilder von dort zu holen, behandeln wir später.
{ "startSection": "form",
"apiRoot": "/json/i18n_$1.json",
"XapiRoot": "/api/$1",
"defaultLanguage": "de"
}
Views
Anstelle von Sections werden im Frontend Views verwendet. Dazu werden die Section-Komponenten in den Ordner View verschoben und umbenannt:
src/view/ViewHello.vue
src/view/ViewHome.vue // an Stelle von ViewForm.vue
src/view/ViewError404.vue // neu
Die CSS-Dateien müssen natürlich auch entsprechend verschoben und abgepasst werden.
ViewForm.vue wird in ViewHome.vue unbenannt, da diese Seite als Startseite verwendet wird.
Zusätzlich wird eine Seite ViewError404.vue angelegt, die immer dann ausgeliefert wird, wenn der Benutzer auf eine URL zugreift, der keine View-Datei zugeordnet ist.
Die CSS-Dateien sollten analog umbenannt und unter den neuen Namen importiert werden. Für ViewError404.vue muss natürlich auch eine CSS-Datei angelegt werden. Diese verweist allerdings nur auf _View.css.
<!-- src/view/ViewError404.vue -->
<script setup>
import router from '/router'
import storeI18n from '/store/StoreI18n'
const phrases = storeI18n().phrases
</script>
<template>
<h1>404</h1>
<p>{{phrases.pageNotFound.replace('$1', router.currentRoute.value.path)}}</p>
</template>
<style scoped>
@import '/css/view/ViewError404';
</style>
Router-Plugin
Um mit Vue Multipage-Anwendungen realisieren zu können, benötigt man einRouter-Plugin.
Dieses muss zunächst händisch auf Basis von vue-router implementiert werden.
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import ViewHome from '/view/ViewHome.vue'
const
routes =
[ { path: '/',
name: 'home',
component: ViewHome,
},
{ path: '/hello',
name: 'hello',
component: () => import('/view/ViewHello.vue'),
},
{ path: '/:pathMatch(.*)',
component: () => import('/view/ViewError404.vue'),
}
],
router =
createRouter({ history: createWebHistory(), routes })
export default router
Nun muss man das neu erstellte Plugin in die App integrieren.
// src/main.js
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import getJson from '/service/getJson'
import router from './router' // './router/index.js'
import App from './App.vue'
const
pinia = createPinia(),
app = createApp(App),
init = async () =>
{ app
.provide('config', await getJson('/json/config.json'))
.use(pinia)
.use(router)
.mount('#app') // app is shown to the user
}
window.addEventListener('load', init)
index.html
Da das main-Tag nun innerhalb von App.vue verwendet werden soll,
wird es aus der Datei index.html entfernt.
...
<body id="app">
</body>
...
App.vue
Nun kann in die App eine Navigation eingebaut werden.
<!-- /src/App.vue -->
<script setup>
import AppNav from '/component/AppNav.vue'
import storeSession from '/store/StoreSession'
import storeI18n from '/store/StoreI18n'
const session = storeSession()
storeI18n();
session.initialize();
</script>
<template>
<p v-if="!session.isInitialized">Loading ...</p>
<AppNav v-if="session.isInitialized"/>
<main v-if="session.isInitialized">
<router-view/>
</main>
</template>
<style>
@import '/css/body';
</style>
Die zugehörige Navigationskomponente muss auch noch implementiert werden.
<!-- /src/component/AppNav.vue -->
<script setup>
</script>
<template>
<nav>
<ul>
<li><router-link to="/">Home</router-link></li>
<li><router-link to="/hello">Hello</router-link></li>
</ul>
</nav>
</template>
<style scoped>
@import '/css/component/AppNav';
</style>
/* src/css/_config.css */
...
--background-color: #C5EFFC;
--link-color: #0027d4;
...
--nav-dist: 0.5em;
/* src/css/AppNav.css */
@import 'config.cdd';
nav
{ top: 0;
width: 100%;
position: fixed;
ul
{ list-style-type: none;
margin: 0 var(--nav-dist);
padding: 0;
li
{ display: inline;
a { padding: 0 var(--nav-dist 0.2em); }
}
a
{ text-decoration: none;
color: var(--link-color);
font-weight: bold;
}
a:hover, .router-link-exact-active
{ text-decoration: underline;
color: var(--link-color);
font-weight: bold;
}
}
label, input
{ margin: 0;
}
}Die Navigation muss in allen Komponenten, in den Seiten gewechselt werden, angepasst werden.
Das ist in der App.vue der Fall (siehe zuvor) sowie in der ViewHome.vue.
<!-- /src/vie/ViewHome.vue -->
<script setup>
import FormButton from '/component/form/FormButton.vue'
import FormTextfield from '/component/form/FormTextfield.vue'
import storeGreeting from '/store/StoreGreeting'
import router from '/router'
const
session = storeSession(),
sayHello = () => router.push('/hello'),
greeting = storeGreeting(),
dictionary = greeting.dictionary
</script>
Die Section-Verwaltung der ehemaligen Single-Page-Anwendung kann und sollte gelöscht werden:
config.jsstoreSession.jsViewHome.vueViewHello.vue
Die Datei HelloWorld.vue kann (samt CSS-Datei) ganz gelöscht werden.
Hello World Vue 06a
Aufgabe: Die Ausgabesprache soll vom Benutzer zur Laufzeit geändert werden können. Die Sprachen sollen durch Bilder (Flaggen) symbolisiert werden.
git checkout v06 # Wechsle in den Branch v06
git checkout -b v06a # Klone v06 in einen neuen Branch v06a
config.json
Um die vorhandenen Sprachen durchblättern zu können, wird ein einfacher Automat verwendet.
// src/public/json/config.json
{ "apiRoot": "/json/i18n_$1.json",
"XapiRoot": "/api/$1",
"defaultLanguage": "en",
"nextLanguage": {"de": "en", "en": "de"}
}
StoreI18n.js: Integration von nextLanguage
Der Automat nextLanguage wird in den StoreI18n integriert. Wenn die Methode changeLang ohne Argument aufgerufen wird, soll der Automat die aktuelle Sprache lang durch die Nachfolgersprache ersetzen.
Dazu sollte zunächst die zunächst Konstante nextLanguage definiert werden.
...
lang =
ref(null),
nextLanguage =
reactive(config.nextLanguage),
...
Diese neue Konstante wird verwendet, um die Sprache mittels changeLang zu definieren, sofern
kein Argument übergeben wurde.
changeLang =
async (p_lang = null) =>
{ lang.value = p_lang ?? nextLanguage[lang.value];
await getI18n(lang.value);
document.getElementsByTagName('html')[0].lang = lang.value;
}
initialize =
async resolve =>
{ await changeLang(config.defaultLanguage);
resolve();
}
Bilder von Flaggen
Zur Visualisierung der gewählten Sprache werden Bilder von Flaggen eingesetzt. Wir kopieren die Bilder im SVG-Format aus dem NPM-Paket "https://github.com/lipis/flag-icons".
npm i -D flag-icons
Im Ordner "node_modules/flag-icons/flags/4x3" finden Sie Flaggen zahlreicher Länder im 4-zu-3-Format.
Kopieren Sie die Flaggen der Länder Deutschland, Großbritanien und Frankreich in den Ordner public/img.
In die Datei config.js wird der Pfad, unter dem die Bilder zu finden sind, eingefügt.
// public/config.js
...
"apiRoot": "/json/i18n_$1.json",
"imgPath": "/img/$1.svg",
"XapiRoot": "/api/$1",
"XimgPath": "/api/$1/img",
...
FormButtonIcon
Eine neue Komponente wird benötigt: FormButtonIcon Dabei handelt es sich um einen Button, der ein Icon anzeigt. Das Bild kann zur Laufzeit geändert werden.
<script setup>
import { computed, inject } from 'vue'
const
props = defineProps
({ image: { type: String },
width: { type: String, default: '3em'},
}),
config = inject('config'),
emit = defineEmits(['click']),
click = () => emit('click'),
require = v_url => import.meta.url == null
? v_url
: new URL(v_url, import.meta.url).href,
imgPath = config.imgPath,
buttonImage = computed(() => require(imgPath.replace('$1', props.image)))
</script>
<template>
<input type="image" :src="buttonImage" :style="{width}" @click="click" />
</template>
<style scoped>
@import '/css/component/form/FormButtonIcon.css';
</style>
/* src/css/component/FormButtonIcon.css */
@import 'FormButton';
input
{ width: $button_icon_width !important;
margin-left: $nav-dist !important;
}
Fügen Sie in src/css/_config.css noch
$button-icon-width: 1em;
ein.
Der Sprachwahl-Button muss noch in die Navigation eingebaut werden. In diesem Rahmen wird auch gleich die Navigation etwas internationalisiert.
Ergänzen Sie in den Dictionaries der Dateien public/json/i18n_de.json und public/json/i18n_en.json
folgende Attribute:
... ...,
"navHome": "Start",
"navHello": "Hallo"
bzw.
... ...,
"navHome": "Home",
"navHello": "Hello"
Damit können Sie das Navigationsmenü internationalisieren (einschließlich Auswahl der Sprache per Button).
<!-- src/component/AppNav -->
<script setup>
import FormButtonIcon from '/component/form/FormButtonIcon.vue'
import storeI18n from '/store/StoreI18n'
const i18n = storeI18n()
</script>
<template>
<nav>
<ul>
<li><router-link to="/">{{i18n.dictionary.navHome}}</router-link></li>
<li><router-link to="/hello">{{i18n.dictionary.navHello}}</router-link></li>
<li><FormButtonIcon :image="i18n.lang" @click="i18n.changeLang()"/></li>
</ul>
</nav>
</template>
<style scoped>
@import '/css/component/AppNav';
</style>
Hello World Vue 06b
Aufgabe: Defaultmäßig soll dem Benutzer die App in der Sprache abgezeigt werden, die der Benutzer im Browser voreingestellt hat. Sollte die voreingestellte Sprache nicht unterstütz werden, wird die App in der Defaultsprache angezeigt.
Legen Sie zunächst wieder einen neuen Branch an.
git checkout v06a # Wechsle in den Branch v06a
git checkout -b v06b # Klone v06a in einen neuen Branch v06b
Das Vorgehen ist im Prinzip ganz einfach.
Mittels des NPM-Packets "navigator-languages" ermittelt man die vom Browser unterstützten Sprachen. In der Konfigurationsdatei listet man die Sprachen auf, die von der App unterstütz werden. Besser wäre es natürlich, diese Liste vom Backend zu laden, damit die App mit bekommt, wenn neue Sprachen zur Verfügung stehen. Am aufwändigsten ist es, diese beiden Array zu matchen.
Mögliche Erweiterung: Auch die HTML-Pfade sollten internationalisiert werden.
npm i navigator-languages // unterstützt mehrere Browservarianten
Language Matching
Das Language Matching erfolgt zweimal mit zwei verschachtelten Schleifen. Zunächst wird überprüft, ob es exakte Übereinstimmungen gibt. Sollte das nicht der Fall sein, wird überprüft, ob es Matches zwischen den ersten beiden Buchstaben gibt ('de', 'de-AT', 'de-DE' matchen in diesem Schritt alle miteinander).
// src/util/chooseLanguage.js
import navigatorLanguages from 'navigator-languages'
const
/**
* @function
* @param { Array<string> } p_langs_available
* An array of language strings containing the languages
* supported by the app
* @param { Array<string> } [p_langs_required = navigatorLanguages()]
* An array of language strings containing the languages
* requested by the client
* @return { string }
* The language to be displayed
*/
chooseLanguage =
(p_langs_available, p_langs_required = navigatorLanguages()) =>
{ let
lang = p_langs_available[0],
i=0, j=0, n=p_langs_required.length, m=p_langs_available.length
outer:
{ while (i<n)
{ const c_lang_required = p_langs_required[i]
while (j<m)
{ const c_lang_availabe = p_langs_available[j]
if (c_lang_availabe === c_lang_required)
// both languages match perfectly
{ lang = c_lang_availabe;
break outer;
}
j++
}
i++
}
i=0, j=0
while (i<n)
{ const c_lang_required = p_langs_required[i]
while (j<m)
{ const c_lang_availabe = p_langs_available[j]
if (c_lang_availabe.substring(0,2) === c_lang_required.substring(0,2))
// main part of the languages match
{ lang = c_lang_availabe;
break outer;
}
j++
}
i++
}
}
return lang
}
export default chooseLanguage
config.js
Das Attribut defaultLanguage wird durch das Attribut languages ersetzt.
{ "apiRoot": "/json/i18n_$1.json",
"imgPath": "/img/$1.svg",
"XapiRoot": "/api/$1",
"XimgPath": "/api/$1/img",
"languages": ["de", "en"],
"nextLanguage": {"de": "en", "en": "de"}
}
src/store/StoreI18n
...
import chooseLanguage from '/util/chooseLanguage'
...
() =>
{ const
...
initialize =
async resolve =>
{ await changeLang(chooseLanguage(config.languages));
resolve();
}
...
...
Hello World Vue 06c
Aufgabe: Sorgen Sie dafür, dass die unterstützten Sprachen und der Sprach-Automat vom Backend geladen wird.
git checkout v06b # Wechsle in den Branch v06b
git checkout -b v06c # Klone v06b in einen neuen Branch v06c
Splitten Sie die Config-Datei zunächst in zwei Teile: "config.json" wird aus dem Public-Ordner geladen. "config_backend.json" wird normalerweise vom Backend geladen, kann aber zum Entwicklungszeitpunkt auch vom Public-Ordner geladen werden, wenn das Backend noch nicht stabil läuft.
// src/public/json/config.json
{ "configBackend": "/json/config_backend.json",
"apiRoot": "/json/i18n_$1.json",
"imgPath": "/img/$1.svg",
"XconfigBackend": "/api/config",
"XapiRoot": "/api/$1",
"XimgPath": "/api/$1/img"
}
// src/public/json/config_backend.json
{ "languages": ["de", "en"],
"nextLanguage": {"de": "en", "en": "de"}
}
Das Backend teilt dem Frontend mit, welche Sprachen unterstützt werden.
main.js
Laden Sie, nachdem Sie "config" asynchron geladen haben, "configBackend" asynchron und kopieren Sie den Inhalt dieses Objekts in das Config-Objekt. Den Pfad von "configBackend" finden Sie unter "config.configBackend".
...
const
...
init = async () =>
{ const
config = await getJson('/json/config.json'),
configBackend = await getJson(config.configBackend)
Object.assign(config, configBackend)
app
.provide('config', config)
.use(pinia)
.use(router)
.mount('#app') // app is shown to the user
}
storeI18n.js
Die Datei "index.html" sollte bei jeder Änderung der Sprache angepasst werden.
...
changeLang =
async (p_lang = null) =>
{ lang.value = p_lang ?? nextLanguage[lang.value];
await getI18n(lang.value);
document.getElementsByTagName('html')[0].lang = lang.value
}
...
Quellen
- Kowarschick (WebProg): Wolfgang Kowarschick; Vorlesung „Web-Programmierung“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2024; Quellengüte: 3 (Vorlesung)
