Merged in feature/i18next (pull request #153)

Feature/i18next

Approved-by: Christian Cueni
This commit is contained in:
Daniel Egger 2023-07-07 16:42:10 +00:00
commit 926ecb0ae0
37 changed files with 1403 additions and 505 deletions

View File

@ -101,19 +101,61 @@ Preferences -> Tools -> Actions on Save
## Translations
We use (vue-i18n)[https://kazupon.github.io/vue-i18n/] for translations
and (vue-i18n-extract)[https://github.com/Spittal/vue-i18n-extract] for helper
scripts.
We use (Locize)[https://locize.com] (see 1Password for credentials)
together with (i18next)[https://www.i18next.com/]
for translations on the Frontend.
```
# will create a report on command line with missing translations
npm run vue-i18n-extract
Please make sure that the required environment variables are set
(see ./env_secrets/local_daniel.env for the values):
# add missing translations to files, see docs for more options
cd client
npx vue-i18n-extract --add
* LOCIZE_PROJECT_ID
* LOCIZE_API_KEY
The master for translated files is on Locize!
That means, that the app will take the translations/texts from Locize
to show in the app.
The files in ./client/locales are only used as reference and are not the master!
There are multiple ways on how to add new translations to Locize:
### Process one: Let Locize add missing keys automatically
When running the app, it will automatically add the missing translation
keys to Locize.
There you can translate them, and also add the German translation.
### Process two: Add keys manually
You can add the new keys manually to the German locale file in
./client/locales/de/translation.json
Then you can run the following command to add the keys to Locize:
### Helpers
The following command could help find missing and/or unused keys.
But manual review is still needed.
```bash
npx vue-i18n-extract report --vueFiles './src/**/*.?(ts|vue)' --languageFiles './src/locales/**/*.?(json|yml|yaml)'
```
```bash
npm run locize:sync
```
The command will add the keys and the German translation to Locize.
Bonus: Use the "i18n ally" plugin in VSCode or IntelliJ to get extract untranslated
texts directly from the code to the translation.json file.
### "_many" plural form in French and Italian
See https://github.com/i18next/i18next/issues/1691#issuecomment-968063348
for an explanation why this plural form is needed in French and Italian.
But not in German and English.
## Deployment to CapRover
### CapRover Dev (vbv-lernwelt.control.iterativ.ch)

View File

@ -1,16 +1,9 @@
import { Preview, setup } from "@storybook/vue3";
import { createI18n } from "vue-i18n";
import de from "../src/locales/de.json";
import "../tailwind.css";
import { withVueRouter } from "./mockRouter";
setup((app) => {
const i18n = createI18n({
locale: "de", // set locale
messages: { de },
});
withVueRouter(app);
app.use(i18n);
});
const preview: Preview = {

1333
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@
"tailwind": "tailwindcss -i tailwind.css -o ../server/vbv_lernwelt/static/css/tailwind.css --watch",
"test": "vitest run",
"typecheck": "npm run codegen && vue-tsc --noEmit -p tsconfig.app.json --composite false",
"vue-i18n-extract": "vue-i18n-extract report"
"locize:sync": "locize sync --path ./src/locales --compare-modification-time=true"
},
"dependencies": {
"@headlessui/tailwindcss": "^0.1.3",
@ -34,13 +34,15 @@
"d3": "^7.8.5",
"dayjs": "^1.11.8",
"graphql": "^16.6.0",
"i18next": "^23.2.8",
"i18next-locize-backend": "^6.2.2",
"i18next-vue": "^2.2.0",
"locize": "^2.4.6",
"lodash": "^4.17.21",
"loglevel": "^1.8.0",
"mitt": "^3.0.0",
"pinia": "^2.1.4",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
"vue-i18n-extract": "^2.0.7",
"vue-router": "^4.2.2"
},
"devDependencies": {
@ -80,6 +82,7 @@
"eslint-plugin-storybook": "^0.6.12",
"eslint-plugin-vue": "^9.15.0",
"jsdom": "^22.1.0",
"locize-cli": "^7.14.6",
"postcss": "^8.4.14",
"postcss-import": "^15.1.0",
"prettier": "^2.8.8",

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import { SUPPORT_LOCALES } from "@/i18n";
import type { AvailableLanguages } from "@/stores/user";
import { useUserStore } from "@/stores/user";
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue";
import * as log from "loglevel";
import { SUPPORT_LOCALES } from "@/i18nextWrapper";
log.debug("AppFooter created");

View File

@ -17,12 +17,12 @@ import { useMutation } from "@urql/vue";
import { useRouteQuery } from "@vueuse/router";
import log from "loglevel";
import { computed, onMounted, reactive, ref } from "vue";
import { useI18n } from "vue-i18n";
import { useTranslation } from "i18next-vue";
const props = defineProps<{ page: LearningContentFeedback }>();
const courseSessionsStore = useCourseSessionsStore();
const circleStore = useCircleStore();
const { t } = useI18n();
const { t } = useTranslation();
onMounted(async () => {
log.debug("Feedback mounted");

View File

@ -2,7 +2,7 @@
<div class="mb-4 bg-white px-6 py-5">
<h3 class="heading-3 mb-4 flex items-center gap-2">
<it-icon-feedback-large class="h-16 w-16"></it-icon-feedback-large>
<div>{{ $t("general.feedback", 2) }}</div>
<div>{{ $t("general.feedback_other") }}</div>
</h3>
<ol v-if="feedbackSummary.length > 0">
<ItRow v-for="feedbacks in feedbackSummary" :key="feedbacks.circle_id">
@ -12,7 +12,7 @@
<template #center>
<div class="flex w-full justify-between">
<div>Circle: {{ feedbacks.circle.title }}</div>
<div>{{ $t("feedback.sentByUsers", feedbacks.count) }}</div>
<div>{{ $t("feedback.sentByUsers", { count: feedbacks.count }) }}</div>
</div>
</template>
<template #link>

View File

@ -13,7 +13,7 @@ import { useRouteLookups } from "@/utils/route";
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
import { computed, onMounted, reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useTranslation } from "i18next-vue";
log.debug("MainNavigationBar created");
@ -24,7 +24,7 @@ const notificationsStore = useNotificationsStore();
const { inCockpit, inCompetenceProfile, inCourse, inLearningPath, inMediaLibrary } =
useRouteLookups();
const { t } = useI18n();
const { t } = useTranslation();
const state = reactive({
showMobileNavigationMenu: false,
showMobileProfileMenu: false,

View File

@ -4,7 +4,9 @@ import { PopoverButton } from "@headlessui/vue";
</script>
<template>
<div class="pb-2 text-lg text-black">{{ $t("general.notification", 2) }}</div>
<div class="pb-2 text-lg text-black">
{{ $t("general.notification_other") }}
</div>
<div class="border-t bg-white">
<NotificationList :num-notifications-to-show="4" />
<router-link to="/notifications">

View File

@ -74,9 +74,9 @@
import QuestionSummary from "@/components/ui/QuestionSummary.vue";
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { useTranslation } from "i18next-vue";
const { t } = useI18n();
const { t } = useTranslation();
type RGB = [number, number, number];
const red: RGB = [221, 103, 81]; // red-600

View File

@ -1,45 +0,0 @@
import type { AvailableLanguages } from "@/stores/user";
import dayjs from "dayjs";
import { nextTick } from "vue";
import { createI18n } from "vue-i18n";
// https://vue-i18n.intlify.dev/guide/advanced/lazy.html
export const SUPPORT_LOCALES: AvailableLanguages[] = ["de", "fr", "it"];
let i18n: any = null;
export function setupI18n(
options = { locale: "de", legacy: false, fallbackLocale: "de" }
) {
i18n = createI18n(options);
setI18nLanguage(options.locale);
dayjs.locale(options.locale);
return i18n;
}
export function setI18nLanguage(locale: string) {
if (i18n.mode === "legacy") {
i18n.global.locale = locale;
} else {
i18n.global.locale.value = 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
*/
document.querySelector("html")?.setAttribute("lang", locale);
}
export async function loadLocaleMessages(locale: any) {
// load locale messages with dynamic import
const messages = await import(
/* webpackChunkName: "locale-[request]" */ `./locales/${locale}.json`
);
// set locale and locale message
i18n.global.setLocaleMessage(locale, messages.default);
return nextTick();
}

View File

@ -0,0 +1,65 @@
import type { AvailableLanguages } from "@/stores/user";
import i18next from "i18next";
import Backend from "i18next-locize-backend";
import { locizePlugin } from "locize";
import { nextTick } from "vue";
declare module "i18next" {
interface CustomTypeOptions {
returnNull: false;
}
}
export const SUPPORT_LOCALES: AvailableLanguages[] = ["de", "fr", "it"];
export function i18nextInit() {
return (
i18next
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
// .use(LanguageDetector)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.use(Backend)
.use(locizePlugin)
.init({
debug: true,
supportedLngs: SUPPORT_LOCALES,
fallbackLng: "de",
defaultNS: "translation",
returnNull: false,
saveMissing: import.meta.env.DEV,
backend: {
projectId:
import.meta.env.VITE_LOCIZE_PROJECTID ||
"7518c269-cbf7-4d25-bc5c-6ceba2a8b74b",
apiKey: import.meta.env.DEV ? import.meta.env.VITE_LOCIZE_API_KEY : undefined,
fallbackLng: "de",
allowedAddOrUpdateHosts: ["localhost"],
},
})
);
}
export function setI18nLanguage(locale: string) {
/**
* 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
*/
i18next.changeLanguage(locale);
document.querySelector("html")?.setAttribute("lang", locale);
}
export async function loadI18nextLocaleMessages(locale: any) {
// load locale messages with dynamic import
// unused with locize
const messages = await import(`./locales/${locale}.json`);
i18next.addResourceBundle(locale, "messages", messages, true, true);
return nextTick();
}

View File

@ -1,11 +1,7 @@
{
"Benutzername": "Benutzername",
"Hier findest du die Trainerunterlagen (Lösungsblätter, Präsentationen etc.) für deinen Circle.": "Hier findest du die Trainerunterlagen (Lösungsblätter, Präsentationen etc.) für deinen Circle.",
"Klicke auf den Button, um dich über SSO anzumelden oder zu registrieren.": "Klicke auf den Button, um dich über SSO anzumelden oder zu registrieren.",
"MS Teams öffnen": "MS Teams öffnen",
"Nächste Termine:": "Nächste Termine:",
"Passwort": "Passwort",
"SSO Login/Registration": "SSO Login/Registration",
"Trainerunterlagen": "Trainerunterlagen",
"Zur Zeit sind keine Termine vorhanden": "Zur Zeit sind keine Termine vorhanden",
"assignment": {
@ -15,9 +11,9 @@
"assignmentSubmitted": "Du hast deine Ergebnisse erfolgreich abgegeben.",
"confirmSubmitPerson": "Hiermit bestätige ich, dass die folgende Person meine Ergebnisse bewerten soll.",
"confirmSubmitResults": "Hiermit bestätige ich, dass ich die Zusammenfassung meiner Ergebnisse überprüft habe und so abgeben will.",
"dueDateIntroduction": "Reiche deine Ergebnisse pünktlich ein bis am {date} um {time} Uhr ein.",
"dueDateIntroduction": "Reiche deine Ergebnisse pünktlich ein bis am {{date}} um {{time}} Uhr ein.",
"dueDateNotSet": "Keine Abgabedaten wurden erfasst für diese Durchführung",
"dueDateSubmission": "Einreichungstermin: {date}",
"dueDateSubmission": "Einreichungstermin: {{date}}",
"dueDateTitle": "Abgabetermin",
"edit": "Bearbeiten",
"effortTitle": "Zeitaufwand",
@ -25,7 +21,7 @@
"lastChangesNotSaved": "Die letzte Änderung konnte nicht gespeichert werden.",
"performanceObjectivesTitle": "Leistungsziele",
"showAssessmentDocument": "Bewertungsinstrument anzeigen",
"submissionNotificationDisclaimer": "{name} wird deine Ergebnisse bewerten. Du wirst per Benachrichtigung informiert, sobald die Bewertung für dich freigegeben wurde.",
"submissionNotificationDisclaimer": "{{name}} wird deine Ergebnisse bewerten. Du wirst per Benachrichtigung informiert, sobald die Bewertung für dich freigegeben wurde.",
"submitAssignment": "Ergebnisse abgeben",
"taskDefinition": "Bearbeite die Teilaufgaben und dokumentiere deine Ergebnisse.",
"taskDefinitionTitle": "Aufgabenstellung"
@ -33,14 +29,14 @@
"circlePage": {
"circleContentBoxTitle": "Das lernst du in diesem Circle",
"contactExpertButton": "Trainer/-in kontaktieren",
"contactExpertDescription": "Tausche dich mit der Trainer/-in für den Circle {circleName} aus.",
"contactExpertDescription": "Tausche dich mit der Trainer/-in für den Circle {{circleName}} aus.",
"documents": {
"action": "Unterlagen hochladen",
"chooseLearningSequence": "Bitte wähle eine Lernsequenz aus",
"chooseName": "Bitte wähle einen Namen",
"chooseSequence": "Wähle eine Lernsequenz aus",
"deleteModalTitle": "Unterlage löschen",
"deleteModalWarning": "Willst du die Unterlage <strong>\"{title}\"</strong> löschen?<br> Diese Aktion ist nicht umkehrbar.",
"deleteModalWarning": "Willst du die Unterlage <strong>\"{{title}}\"</strong> löschen?<br> Diese Aktion ist nicht umkehrbar.",
"expertDescription": "Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.",
"fileLabel": "Datei",
"maxFileSize": "Maximale Dateigrösse: 20 MB",
@ -68,7 +64,8 @@
"profileLink": "Profil anzeigen",
"progress": "Teilnehmende / Status",
"tasksDone": "Erledigte Transferaufträge von Teilnehmer.",
"title": "Cockpit"
"title": "Cockpit",
"trainerFilesText": "Hier findest du die Trainerunterlagen (Lösungsblätter, Präsentationen etc.) für deinen Circle."
},
"competences": {
"assessAgain": "Sich nochmals einschätzen",
@ -89,7 +86,10 @@
"dashboard": {
"courses": "Lehrgang",
"nocourses": "Du wurdest noch keinem Lehrgang zugewiesen.",
"welcome": "Willkommen, {name}"
"welcome": "Willkommen, {{name}}"
},
"dueDates": {
"nextDueDates": "Nächste Termine"
},
"feedback": {
"answers": "Antworten",
@ -97,7 +97,7 @@
"average": "Durchschnitt",
"circleFeedback": "Feedback zum Circle",
"completionDescription": "Dein Feedback ist anonym. Dein Vor- und Nachname werden bei deiner Trainer/-in nicht angezeigt.",
"completionTitle": "Schicke dein Feedback an {name}",
"completionTitle": "Schicke dein Feedback an {{name}}",
"courseNegativeFeedbackLabel": "Wo siehst du Verbesserungspotential?",
"coursePositiveFeedbackLabel": "Was hat dir besonders gut gefallen?",
"feedbackPageInfo": "Teilnehmer haben das Feedback ausgefüllt",
@ -108,7 +108,7 @@
"instructorCompetenceLabel": "Wie beurteilst du die Themensicherheit und Fachkompetenz des Kursleiters/der Kursleiterin?",
"instructorOpenFeedbackLabel": "Was möchtest du dem Kursleiter/der Kursleiterin sonst noch sagen?",
"instructorRespectLabel": "Wurden Fragen und Anregungen der Kursteilnehmenden ernst genommen und aufgegriffen?",
"intro": "{name}, dein/e Trainer/-in, bittet dich, ihm/ihr Feedback zu geben. Das ist freiwillig, würde aber ihm/ihr helfen, deine Lernerlebniss zu verbessern.",
"intro": "{{name}}, dein/e Trainer/-in, bittet dich, ihm/ihr Feedback zu geben. Das ist freiwillig, würde aber ihm/ihr helfen, deine Lernerlebniss zu verbessern.",
"materialsRatingLabel": "Falls ja: Wie beurteilen Sie die Vorbereitungsunterlagen (z.B. eLearning)?",
"noFeedbacks": "Es wurden noch keine Feedbacks abgegeben",
"preparationTaskClarityLabel": "Waren die Vorbereitungsaufträge klar und verständlich?",
@ -117,12 +117,15 @@
"recommendLabel": "Würdest du den Kurs weiterempfehlen?",
"satisfactionLabel": "Zufriedenheit insgesamt",
"sendFeedback": "Feedback abschicken",
"sentByUsers": "Von {count} Teilnehmern ausgefüllt",
"sentByUsers": "Von {{count}} Teilnehmern ausgefüllt",
"showDetails": "Details anzeigen",
"unhappy": "Unzufrieden",
"veryHappy": "Sehr zufrieden",
"veryUnhappy": "Sehr unzufrieden"
},
"foobar": {
"hello": "Hallo von der Innoweek"
},
"footer": {
"contact": "Kontakt",
"contactLink": "https://www.vbv.ch/de/der-vbv/organisation/kontakt",
@ -138,12 +141,16 @@
"backCapitalized": "@.capitalize:general.back",
"backToCircle": "zurück zum Circle",
"backToLearningPath": "zurück zum Lernpfad",
"certificate": "Zertifikat | Zertifikate",
"certificate_one": "Zertifikat",
"certificate_other": "Zertifikate",
"circles": "Circles",
"close": "Schliessen",
"exam": "Prüfung | Prüfungen",
"examResult": "Prüfungsresultat | Prüfungsresultate",
"feedback": "Feedback | Feedbacks",
"examResult_one": "Prüfungsresultat",
"examResult_other": "Prüfungsresultate",
"exam_one": "Prüfung",
"exam_other": "Prüfungen",
"feedback_one": "Feedback",
"feedback_other": "Feedbacks",
"introduction": "Einleitung",
"learningPath": "Lernpfad",
"learningSequence": "Lernsequenz",
@ -151,7 +158,8 @@
"next": "Weiter",
"nextStep": "Weiter geht's",
"no": "Nein",
"notification": "Benachrichtigung | Benachrichtigungen",
"notification_one": "Benachrichtigung",
"notification_other": "Benachrichtigungen",
"profileLink": "Details anzeigen",
"save": "Speichern",
"send": "Senden",
@ -162,7 +170,8 @@
"start": "Los geht's",
"submission": "Abgabe",
"title": "myVBV",
"transferTask": "Transferauftrag | Transferaufträge",
"transferTask_one": "Transferauftrag",
"transferTask_other": "Transferaufträge",
"yes": "Ja"
},
"language": {
@ -178,11 +187,17 @@
"listView": "Listenansicht",
"nextStep": "Nächster Schritt",
"pathView": "Pfadansicht",
"progressText": "Du hast { inProgressCount } von { allCount } Circles bearbeitet",
"progressText": "Du hast {{ inProgressCount }} von {{ allCount }} Circles bearbeitet",
"showListView": "Listenansicht anzeigen",
"topics": "Themen:",
"welcomeBack": "Willkommen zurück in deinem Lehrgang:"
},
"login": {
"demoLogin": "Demo Login",
"login": "Login",
"ssoLogin": "SSO Login/Registration",
"ssoText": "Klicke auf den Button, um dich über SSO anzumelden oder zu registrieren."
},
"mainNavigation": {
"logout": "Abmelden",
"profile": "Profil"
@ -190,7 +205,8 @@
"mediaLibrary": {
"handlungsfelder": {
"description": "Finde alle Ressourcen der Handlungsfelder wie Lernmedien, Links und andere nützliche Informationen.",
"title": "Handlungsfeld | Handlungsfelder"
"title_one": "Handlungsfeld",
"title_other": "Handlungsfelder"
},
"learningMedia": {
"description": "Finde eine vollständige Liste der Bücher und anderen Medien, auf die im Kurs verwiesen wird.",
@ -217,8 +233,8 @@
"selfEvaluation": "Selbsteinschätzung",
"selfEvaluationNo": "@:selfEvaluation: Muss ich nochmals anschauen.",
"selfEvaluationYes": "@:selfEvaluation: Ich kann das.",
"steps": "Schritt {current} von {max}",
"title": "@:selfEvaluation.selfEvaluation {title}",
"steps": "Schritt {{current}} von {{max}}",
"title": "@:selfEvaluation.selfEvaluation {{title}}",
"yes": "Ja, ich kann das"
},
"settings": {

View File

@ -1,11 +1,7 @@
{
"Benutzername": "Nom dutilisateur",
"Hier findest du die Trainerunterlagen (Lösungsblätter, Präsentationen etc.) für deinen Circle.": "Tu trouves ici les documents de formation (feuilles de solution, présentations, etc.) pour ton cercle.",
"Klicke auf den Button, um dich über SSO anzumelden oder zu registrieren.": "Clique sur le bouton pour te connecter via le SSO ou tinscrire.",
"MS Teams öffnen": "Ouvrir MS Teams ",
"Nächste Termine:": "Prochaines réunions :",
"Passwort": "Mot de passe",
"SSO Login/Registration": "Connexion SSO / Inscription",
"Trainerunterlagen": "Documents du formateur / de la formatrice",
"Zur Zeit sind keine Termine vorhanden": "Aucune réunion nest prévue pour le moment",
"assignment": {
@ -15,9 +11,9 @@
"assignmentSubmitted": "Tes résultats ont bien été transmis.",
"confirmSubmitPerson": "Par la présente, je confirme que la personne suivante doit évaluer mes résultats.",
"confirmSubmitResults": "Par la présente, je confirme que jai vérifié la synthèse de mes résultats et que je souhaite la remettre telle quelle.",
"dueDateIntroduction": "Envoie tes résultats dans les délais avant le {date} à {time} heures.",
"dueDateIntroduction": "Envoie tes résultats dans les délais avant le {{date}} à {{time}} heures.",
"dueDateNotSet": "Aucune date de remise na été spécifiée pour cette opération.",
"dueDateSubmission": "Date de clôture : {date}",
"dueDateSubmission": "Date de clôture : {{date}}",
"dueDateTitle": "Date de remise",
"edit": "Traiter",
"effortTitle": "Temps nécessaire",
@ -25,7 +21,7 @@
"lastChangesNotSaved": "La dernière modification na pas pu être enregistrée.",
"performanceObjectivesTitle": "Objectifs",
"showAssessmentDocument": "Afficher loutil dévaluation",
"submissionNotificationDisclaimer": "{name} va procéder à lévaluation de tes résultats. Tu recevras une notification dès que lévaluation aura été validée et que tu pourras la consulter.",
"submissionNotificationDisclaimer": "{{name}} va procéder à lévaluation de tes résultats. Tu recevras une notification dès que lévaluation aura été validée et que tu pourras la consulter.",
"submitAssignment": "Remettre les résultats",
"taskDefinition": "Résous les exercices et documente tes résultats.",
"taskDefinitionTitle": "Énoncé du problème"
@ -33,14 +29,14 @@
"circlePage": {
"circleContentBoxTitle": "Ce que tu vas apprendre dans ce cercle",
"contactExpertButton": "Contacter le formateur / la formatrice",
"contactExpertDescription": "Échanger avec le formateur / la formatrice si tu as des questions sur le cercle {circleName}.",
"contactExpertDescription": "Échanger avec le formateur / la formatrice si tu as des questions sur le cercle {{circleName}}.",
"documents": {
"action": "Télécharger les documents",
"chooseLearningSequence": "Sélectionne une séquence",
"chooseName": "Sélectionne un nom",
"chooseSequence": "Sélectionne une séquence",
"deleteModalTitle": "Supprimer les documents",
"deleteModalWarning": "Veux-tu <strong>\"{title}\"</strong> supprimer les documents ?<br> Cette action est irréversible.",
"deleteModalWarning": "Veux-tu <strong>\"{{title}}\"</strong> supprimer les documents ?<br> Cette action est irréversible.",
"expertDescription": "Mets des contenus supplémentaires à la disposition de tes apprenants.",
"fileLabel": "Fichier",
"maxFileSize": "Taille maximale du fichier : 20 Mo",
@ -68,7 +64,8 @@
"profileLink": "Afficher le profil",
"progress": "Personne participante / Statut",
"tasksDone": "Exercices dapplication terminés par les participants.",
"title": "Cockpit"
"title": "Cockpit",
"trainerFilesText": "Tu trouves ici les documents de formation (feuilles de solution, présentations, etc.) pour ton cercle."
},
"competences": {
"assessAgain": "Sévaluer à nouveau",
@ -89,7 +86,10 @@
"dashboard": {
"courses": "Formation",
"nocourses": "Tu nas été affecté(e) à aucune formation encore.",
"welcome": "Bienvenue, {name}"
"welcome": "Bienvenue, {{name}}"
},
"dueDates": {
"nextDueDates": "Prochaines réunions"
},
"feedback": {
"answers": "Réponses",
@ -97,7 +97,7 @@
"average": "Moyen",
"circleFeedback": "Feed-back sur le cercle",
"completionDescription": "Tes commentaires seront anonymes. Ton nom et ton prénom ne seront pas indiqués au formateur / à la formatrice.",
"completionTitle": "Envoie ton feed-back à {name}",
"completionTitle": "Envoie ton feed-back à {{name}}",
"courseNegativeFeedbackLabel": "À ton avis, quels sont les points qui pourraient être améliorés ?",
"coursePositiveFeedbackLabel": "Quest-ce qui ta particulièrement plu ?",
"feedbackPageInfo": "Les participants ont rempli le feed-back",
@ -108,7 +108,7 @@
"instructorCompetenceLabel": "Que penses-tu des compétences techniques de la personne chargée du cours et de sa maîtrise du sujet ?",
"instructorOpenFeedbackLabel": "Souhaites-tu ajouter quelque chose à lintention de la personne chargée du cours ?",
"instructorRespectLabel": "Les questions et les suggestions des participants ont-elles été prises au sérieux et traitées correctement ?",
"intro": "{name}, ton formateur / ta formatrice, tinvite à lui adresser un feed-back. Cela nest pas obligatoire, mais lui permettrait daméliorer son cours.",
"intro": "{{name}}, ton formateur / ta formatrice, tinvite à lui adresser un feed-back. Cela nest pas obligatoire, mais lui permettrait daméliorer son cours.",
"materialsRatingLabel": "Si oui : Que pensez-vous des documents fournis pour le travail préparatoire (par ex. eLearning) ?",
"noFeedbacks": "Aucun feed-back na encore été remis",
"preparationTaskClarityLabel": "Les travaux préparatoires étaient-ils clairs et compréhensibles ?",
@ -117,12 +117,15 @@
"recommendLabel": "Est-ce que tu recommandes ce cours ?",
"satisfactionLabel": "Degré de satisfaction au global",
"sendFeedback": "Envoyer le feed-back",
"sentByUsers": "Rempli par {count} participants",
"sentByUsers": "Rempli par {{count}} participants",
"showDetails": "Afficher les détails",
"unhappy": "Insatisfait(e)",
"veryHappy": "Très satisfait(e)",
"veryUnhappy": "Très insatisfait(e)"
},
"foobar": {
"hello": "Salut Innoweek"
},
"footer": {
"contact": "Contact",
"contactLink": "https://www.vbv.ch/fr/lafa/organisation/contact",
@ -138,12 +141,20 @@
"backCapitalized": "@.capitalize:general.back",
"backToCircle": "Revenir au cercle",
"backToLearningPath": "Revenir au programme de formation",
"certificate": "Certificat | Certificats",
"certificate_many": "Certificats",
"certificate_one": "Certificat",
"certificate_other": "Certificats",
"circles": "Cercles",
"close": "Fermer",
"exam": "Examen | Examens",
"examResult": "Résultat de lexamen | Résultats de lexamen",
"feedback": "Feed-back | Feed-backs",
"examResult_many": "Résultats de lexamen",
"examResult_one": "Résultat de lexamen",
"examResult_other": "Résultats de lexamen",
"exam_many": "Examens",
"exam_one": "Examen",
"exam_other": "Examens",
"feedback_many": "Feed-backs",
"feedback_one": "Feed-back",
"feedback_other": "Feed-backs",
"introduction": "Introduction",
"learningPath": "Programme de formation",
"learningSequence": "Séquence",
@ -151,7 +162,9 @@
"next": "Continuer",
"nextStep": "Cela continue",
"no": "Non",
"notification": "Notification | Notifications",
"notification_many": "Notifications",
"notification_one": "Notification",
"notification_other": "Notifications",
"profileLink": "Détails",
"save": "Enregistrer",
"send": "Envoyer",
@ -162,7 +175,9 @@
"start": "Cest parti !",
"submission": "Remise",
"title": "myAFA",
"transferTask": "Exercice dapplication | Exercices dapplication",
"transferTask_many": "Exercices dapplication",
"transferTask_one": "Exercice dapplication",
"transferTask_other": "Exercices dapplication",
"yes": "Oui"
},
"language": {
@ -178,11 +193,17 @@
"listView": "Affichage sous forme de liste",
"nextStep": "Étape suivante",
"pathView": "Affichage sous forme de parcours",
"progressText": "Tu as traité { inProgressCount } de { allCount } cercles",
"progressText": "Tu as traité {{ inProgressCount }} de {{ allCount }} cercles",
"showListView": "Afficher la liste",
"topics": "Thèmes :",
"welcomeBack": "Cela fait plaisir de te revoir dans ta formation :"
},
"login": {
"demoLogin": "Connexion Demo",
"login": "Login",
"ssoLogin": "Connexion SSO / Inscription",
"ssoText": "Clique sur le bouton pour te connecter via le SSO ou tinscrire."
},
"mainNavigation": {
"logout": "Se déconnecter",
"profile": "Profil"
@ -190,7 +211,9 @@
"mediaLibrary": {
"handlungsfelder": {
"description": "Trouve toutes les ressources des champs daction, comme les outils didactiques, les liens et autres informations utiles.",
"title": "Champ daction | Champs daction"
"title_many": "Champs daction",
"title_one": "Champ daction",
"title_other": "Champs daction"
},
"learningMedia": {
"description": "Trouve une liste complète des livres, manuels et autres outils qui ont été mentionnés pendant le cours.",
@ -217,8 +240,8 @@
"selfEvaluation": "Auto-évaluation",
"selfEvaluationNo": "@:selfEvaluation: Il faut que je regarde cela encore une fois de plus près.",
"selfEvaluationYes": "@:selfEvaluation: Je maîtrise cette question.",
"steps": "Étape {current} sur {max}",
"title": "@:selfEvaluation.selfEvaluation {title}",
"steps": "Étape {{current}} sur {{max}}",
"title": "@:selfEvaluation.selfEvaluation {{title}}",
"yes": "Oui, je maîtrise cette question"
},
"settings": {

View File

@ -1,11 +1,7 @@
{
"Benutzername": "Nome utente",
"Hier findest du die Trainerunterlagen (Lösungsblätter, Präsentationen etc.) für deinen Circle.": "Qui trovi i documenti del/della trainer (soluzioni, presentazioni ecc.) per il tuo Circle.",
"Klicke auf den Button, um dich über SSO anzumelden oder zu registrieren.": "Clicca sul pulsante per effettuare il login o registrarti tramite SSO.",
"MS Teams öffnen": "Aprire MS Teams",
"Nächste Termine:": "Prossime scadenze:",
"Passwort": "Password",
"SSO Login/Registration": "Login/Registrazione SSO",
"Trainerunterlagen": "Documenti del/della trainer",
"Zur Zeit sind keine Termine vorhanden": "Al momento non ci sono scadenze",
"assignment": {
@ -15,9 +11,9 @@
"assignmentSubmitted": "I tuoi risultati sono stati consegnati con successo.",
"confirmSubmitPerson": "Confermo che i miei risultati dovranno essere valutati dalla seguente persona.",
"confirmSubmitResults": "Confermo di aver controllato il riepilogo dei miei risultati e di volerli consegnare.",
"dueDateIntroduction": "Presenta i tuoi risultati entro il {date} alle {time}.",
"dueDateIntroduction": "Presenta i tuoi risultati entro il {{date}} alle {{time}}.",
"dueDateNotSet": "Non sono stati registrati dati di consegna per questo svolgimento",
"dueDateSubmission": "Termine di presentazione: {date}",
"dueDateSubmission": "Termine di presentazione: {{date}}",
"dueDateTitle": "Termine di consegna",
"edit": "Modificare",
"effortTitle": "Tempo richiesto",
@ -25,7 +21,7 @@
"lastChangesNotSaved": "Non è stato possibile salvare lultima modifica.",
"performanceObjectivesTitle": "Obiettivi di valutazione",
"showAssessmentDocument": "Mostrare lo strumento di valutazione",
"submissionNotificationDisclaimer": "I tuoi risultati saranno valutati da {name}. Riceverai una notifica non appena la tua valutazione sarà disponibile.",
"submissionNotificationDisclaimer": "I tuoi risultati saranno valutati da {{name}}. Riceverai una notifica non appena la tua valutazione sarà disponibile.",
"submitAssignment": "Consegnare i risultati",
"taskDefinition": "Svolgi le attività parziali e documenta i tuoi risultati.",
"taskDefinitionTitle": "Compito"
@ -33,14 +29,14 @@
"circlePage": {
"circleContentBoxTitle": "Cosa apprenderai in questo Circle",
"contactExpertButton": "Contattare il/la trainer",
"contactExpertDescription": "Confrontati con il/la trainer per il Circle {circleName}.",
"contactExpertDescription": "Confrontati con il/la trainer per il Circle {{circleName}}.",
"documents": {
"action": "Caricare i documenti",
"chooseLearningSequence": "Seleziona una sequenza di apprendimento",
"chooseName": "Seleziona un nome",
"chooseSequence": "Seleziona una sequenza di apprendimento",
"deleteModalTitle": "Eliminare il documento",
"deleteModalWarning": "Desideri davvero eliminare il documento <strong>\"{title}\"</strong>?<br> Una volta eseguita, loperazione non potrà più essere annullata.",
"deleteModalWarning": "Desideri davvero eliminare il documento <strong>\"{{title}}\"</strong>?<br> Una volta eseguita, loperazione non potrà più essere annullata.",
"expertDescription": "Fornisci ai tuoi allievi e alle tue allieve contenuti aggiuntivi.",
"fileLabel": "File",
"maxFileSize": "Dimensioni massime del file: 20 MB",
@ -68,7 +64,8 @@
"profileLink": "Mostrare il profilo",
"progress": "Partecipanti / Stato",
"tasksDone": "Incarichi di trasferimento completati dal/dalla partecipante.",
"title": "Cockpit"
"title": "Cockpit",
"trainerFilesText": "Qui trovi i documenti del/della trainer (soluzioni, presentazioni ecc.) per il tuo Circle."
},
"competences": {
"assessAgain": "Nuova auto-valutazione",
@ -89,7 +86,10 @@
"dashboard": {
"courses": "Corso",
"nocourses": "Non sei ancora stato/a assegnato/a a nessun corso.",
"welcome": "Ti diamo il benvenuto, {name}"
"welcome": "Ti diamo il benvenuto, {{name}}"
},
"dueDates": {
"nextDueDates": "Prossime scadenze"
},
"feedback": {
"answers": "Risposte",
@ -97,7 +97,7 @@
"average": "Media",
"circleFeedback": "Feedback sul Circle",
"completionDescription": "Il tuo feedback è anonimo. Il/La trainer non visualizzerà il tuo nome e cognome.",
"completionTitle": "Invia il tuo feedback a {name}",
"completionTitle": "Invia il tuo feedback a {{name}}",
"courseNegativeFeedbackLabel": "Dove vedi un potenziale di miglioramento?",
"coursePositiveFeedbackLabel": "Coshai apprezzato particolarmente?",
"feedbackPageInfo": "I/Le partecipanti hanno fornito il feedback",
@ -108,7 +108,7 @@
"instructorCompetenceLabel": "Come valuti il livello di preparazione sui temi e le competenze specialistiche dellistruttore/istruttrice del corso?",
"instructorOpenFeedbackLabel": "Cosaltro vorresti ancora dire allistruttore/istruttrice del corso?",
"instructorRespectLabel": "Le domande e i suggerimenti dei/delle partecipanti al corso sono stati accolti e presi sul serio?",
"intro": "{name}, il tuo/la tua trainer ti invita a fornire un feedback su di lui/lei. È facoltativo, ma sarebbe utile per migliorare la tua esperienza di apprendimento.",
"intro": "{{name}}, il tuo/la tua trainer ti invita a fornire un feedback su di lui/lei. È facoltativo, ma sarebbe utile per migliorare la tua esperienza di apprendimento.",
"materialsRatingLabel": "Se sì: qual è la valutazione dei documenti di preparazione (ad es. eLearning)?",
"noFeedbacks": "Non è stato fornito ancora nessun feedback",
"preparationTaskClarityLabel": "Gli incarichi di preparazione erano chiari e comprensibili?",
@ -117,12 +117,15 @@
"recommendLabel": "Raccomanderesti il corso?",
"satisfactionLabel": "Soddisfazione complessiva",
"sendFeedback": "Inviare il feedback",
"sentByUsers": "Fornito da {count} partecipanti",
"sentByUsers": "Fornito da {{count}} partecipanti",
"showDetails": "Mostrare i dettagli",
"unhappy": "Insoddisfatto/a",
"veryHappy": "Molto soddisfatto/a",
"veryUnhappy": "Molto insoddisfatto/a"
},
"foobar": {
"hello": "Ciao Innoweek"
},
"footer": {
"contact": "Contatti",
"contactLink": "https://www.vbv.ch/it/afa/organizzazione/contatto",
@ -138,12 +141,20 @@
"backCapitalized": "@.capitalize:general.back",
"backToCircle": "Torna al Circle",
"backToLearningPath": "Torna al percorso formativo",
"certificate": "Certificato | Certificati",
"certificate_many": "Certificati",
"certificate_one": "Certificato",
"certificate_other": "Certificati",
"circles": "Circle",
"close": "Chiudere",
"exam": "Esame| Esami",
"examResult": "Risultato dellesame | Risultati degli esami",
"feedback": "Feedback | Feedback",
"examResult_many": "Risultati degli esami",
"examResult_one": "Risultato dellesame",
"examResult_other": "Risultati degli esami",
"exam_many": "Esami",
"exam_one": "Esame",
"exam_other": "Esami",
"feedback_many": "Feedback",
"feedback_one": "Feedback",
"feedback_other": "Feedback",
"introduction": "Introduzione",
"learningPath": "Percorso formativo",
"learningSequence": "Sequenza di apprendimento",
@ -151,7 +162,9 @@
"next": "Avanti",
"nextStep": "Continua",
"no": "No",
"notification": "Notifica | Notifiche",
"notification_many": "Notifiche",
"notification_one": "Notifica",
"notification_other": "Notifiche",
"profileLink": "Mostrare i dettagli",
"save": "Salvare",
"send": "Inviare",
@ -162,7 +175,9 @@
"start": "Si comincia",
"submission": "Consegna",
"title": "myAFA",
"transferTask": "Incarico di trasferimento | Incarichi di trasferimento",
"transferTask_many": "Incarichi di trasferimento",
"transferTask_one": "Incarico di trasferimento",
"transferTask_other": "Incarichi di trasferimento",
"yes": "Sì"
},
"language": {
@ -178,11 +193,17 @@
"listView": "Vista elenco",
"nextStep": "Prossimo passo",
"pathView": "Vista percorso",
"progressText": "Hai svolto { inProgressCount } Circle su { allCount }",
"progressText": "Hai svolto {{ inProgressCount }} Circle su {{ allCount }}",
"showListView": "Mostrare la vista elenco",
"topics": "Temi:",
"welcomeBack": "Bentornato/a al tuo corso:"
},
"login": {
"demoLogin": "Login Demo",
"login": "Login",
"ssoLogin": "Login/Registrazione SSO",
"ssoText": "Clicca sul pulsante per effettuare il login o registrarti tramite SSO."
},
"mainNavigation": {
"logout": "Logout",
"profile": "Profilo"
@ -190,7 +211,9 @@
"mediaLibrary": {
"handlungsfelder": {
"description": "Trova tutte le risorse dei campi dazione, come materiali didattici, link e altre informazioni utili.",
"title": "Campo dazione | Campi dazione"
"title_many": "Campi dazione",
"title_one": "Campo dazione",
"title_other": "Campi dazione"
},
"learningMedia": {
"description": "Trova un elenco completo di libri e altri materiali a cui si rimanda nel corso.",
@ -217,8 +240,8 @@
"selfEvaluation": "Auto-valutazione",
"selfEvaluationNo": "@:selfEvaluation: Devo riguardarlo ancora una volta.",
"selfEvaluationYes": "@:selfEvaluation: Ho compreso tutto.",
"steps": "Passo {current} di {max}",
"title": "@:selfEvaluation.selfEvaluation {title}",
"steps": "Passo {{current}} di {{max}}",
"title": "@:selfEvaluation.selfEvaluation {{title}}",
"yes": "Sì, ho compreso tutto"
},
"settings": {

View File

@ -1,11 +1,13 @@
import { i18nextInit } from "@/i18nextWrapper";
import * as Sentry from "@sentry/vue";
import i18next from "i18next";
import I18NextVue from "i18next-vue";
import * as log from "loglevel";
import { createPinia } from "pinia";
import { createApp, markRaw } from "vue";
import type { Router } from "vue-router";
import "../tailwind.css";
import App from "./App.vue";
import { loadLocaleMessages, setupI18n } from "./i18n";
import router from "./router";
declare module "pinia" {
@ -25,9 +27,15 @@ if (appEnv.startsWith("prod")) {
const commit = "VBV_VERSION_BUILD_NUMBER_VBV";
log.warn(`application started appEnv=${appEnv}, build=${commit}`);
const i18n = setupI18n();
const app = createApp(App);
app.use(router);
const pinia = createPinia();
pinia.use(({ store }) => {
store.router = markRaw(router);
});
app.use(pinia);
Sentry.init({
app,
environment: appEnv,
@ -36,16 +44,7 @@ Sentry.init({
enabled: appEnv.startsWith("prod") || appEnv.startsWith("stage"),
});
// todo: define lang setup
loadLocaleMessages("de").then(() => {
app.use(router);
const pinia = createPinia();
pinia.use(({ store }) => {
store.router = markRaw(router);
});
app.use(pinia);
app.use(i18n);
i18nextInit().then(() => {
app.use(I18NextVue, { i18next });
app.mount("#app");
});

View File

@ -25,7 +25,7 @@ const userStore = useUserStore();
<template>
<main class="bg-gray-200 lg:px-12 lg:py-12">
<div class="container-medium">
<h1 class="mb-8">Login</h1>
<h1 class="mb-8">{{ $t("login.login") }}</h1>
<form
v-if="loginMethod === 'local'"
@ -73,20 +73,16 @@ const userStore = useUserStore();
<div v-if="loginMethod === 'sso'" class="bg-white p-4 lg:p-8">
<p>
{{
$t(
"Klicke auf den Button, um dich über SSO anzumelden oder zu registrieren."
)
}}
{{ $t("login.ssoText") }}
</p>
<p class="btn-primary mt-8">
<a :href="`/sso/login/?lang=${userStore.language}`">
{{ $t("SSO Login/Registration") }}
{{ $t("login.ssoLogin") }}
</a>
</p>
<p class="mt-8">
<a href="/login-local">
{{ $t("Demo Login") }}
{{ $t("login.demoLogin") }}
</a>
</p>
</div>

View File

@ -16,7 +16,7 @@ async function loadAdditionalNotifications() {
<div class="bg-gray-200">
<div class="container-large px-8 py-8">
<header class="mb-6">
<h1>{{ $t("general.notification", 2) }}</h1>
<h1>{{ $t("general.notification_other") }}</h1>
</header>
<main>
<div class="bg-white px-4 py-4">

View File

@ -74,13 +74,13 @@ function setActiveClasses(isActive: boolean) {
<button>{{ $t("competences.competences") }}</button>
</li>
<li class="mr-12">
<button>{{ $t("general.transferTask", 2) }}</button>
<button>{{ $t("general.transferTask_other") }}</button>
</li>
<li class="mr-12">
<button>{{ $t("general.exam", 2) }}</button>
<button>{{ $t("general.exam_other") }}</button>
</li>
<li class="mr-12">
<button>{{ $t("general.certificate", 2) }}</button>
<button>{{ $t("general.certificate_other") }}</button>
</li>
</ul>
<div>

View File

@ -63,7 +63,7 @@ import { useCurrentCourseSession } from "@/composables";
import { itGet } from "@/fetchHelpers";
import * as log from "loglevel";
import { onMounted, reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useTranslation } from "i18next-vue";
interface FeedbackData {
amount: number;
@ -80,7 +80,7 @@ const props = defineProps<{
log.debug("FeedbackPage created", props.circleId);
const courseSession = useCurrentCourseSession();
const { t } = useI18n();
const { t } = useTranslation();
const orderedQuestions = [
{

View File

@ -99,11 +99,7 @@ function setActiveClasses(translationKey: string) {
{{ $t("Trainerunterlagen") }}
</h3>
<div class="mb-4">
{{
$t(
"Hier findest du die Trainerunterlagen (Lösungsblätter, Präsentationen etc.) für deinen Circle."
)
}}
{{ $t("cockpit.trainerFilesText") }}
</div>
</div>
<div>

View File

@ -6,7 +6,7 @@ import type { CourseCompletionStatus } from "@/types";
import * as log from "loglevel";
import type { Ref } from "vue";
import { computed, ref } from "vue";
import { useI18n } from "vue-i18n";
import { useTranslation } from "i18next-vue";
const props = defineProps<{
courseSlug: string;
@ -28,7 +28,7 @@ const shownCriteria = computed(() => {
});
});
const { t } = useI18n();
const { t } = useTranslation();
const mobileMenuItems: MenuItem[] = [
{
@ -64,7 +64,7 @@ function updateActiveState(status: CourseCompletionStatus) {
:to="`${competenceStore.competenceProfilePage()?.frontend_url}`"
>
<it-icon-arrow-left />
<span>zurück</span>
<span>{{ $t("general.back") }}</span>
</router-link>
</nav>
<div class="mb-4 flex flex-col items-center justify-between lg:mb-10 lg:flex-row">

View File

@ -73,9 +73,9 @@ import type { CircleDocument, DocumentUploadData } from "@/types";
import dialog from "@/utils/confirm-dialog";
import log from "loglevel";
import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import DocumentListItem from "./DocumentListItem.vue";
import DocumentUploadForm from "./DocumentUploadForm.vue";
import { useTranslation } from "i18next-vue";
const courseSessionsStore = useCourseSessionsStore();
const circleStore = useCircleStore();
@ -90,7 +90,7 @@ const dropdownLearningSequences = computed(() =>
);
// confirm dialog
const { t } = useI18n();
const { t } = useTranslation();
const deleteDocument = async (doc: CircleDocument) => {
const options = {
title: t("circlePage.documents.deleteModalTitle"),

View File

@ -2,7 +2,7 @@
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import type { DocumentUploadData, DropdownSelectable } from "@/types";
import { reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useTranslation } from "i18next-vue";
export interface Props {
learningSequences?: DropdownSelectable[];
@ -10,7 +10,7 @@ export interface Props {
isUploading?: boolean;
}
const { t } = useI18n();
const { t } = useTranslation();
const props = withDefaults(defineProps<Props>(), {
learningSequences: () => [],

View File

@ -11,7 +11,7 @@ import { useMutation } from "@urql/vue";
import type { Dayjs } from "dayjs";
import log from "loglevel";
import { computed, reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useTranslation } from "i18next-vue";
const props = defineProps<{
assignment: Assignment;
@ -26,7 +26,7 @@ const emit = defineEmits<{
const courseSessionsStore = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
const { t } = useI18n();
const { t } = useTranslation();
const state = reactive({
confirmInput: false,

View File

@ -22,9 +22,9 @@ import { useRouteQuery } from "@vueuse/router";
import dayjs from "dayjs";
import * as log from "loglevel";
import { computed, onMounted, reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useTranslation } from "i18next-vue";
const { t } = useI18n();
const { t } = useTranslation();
const courseSession = useCurrentCourseSession();
const userStore = useUserStore();

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import eventBus from "@/utils/eventBus";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { useTranslation } from "i18next-vue";
export type ClosingButtonVariant = "close" | "mark_as_done";
@ -13,7 +13,7 @@ const props = defineProps<{
closingButtonVariant: ClosingButtonVariant;
}>();
const { t } = useI18n();
const { t } = useTranslation();
// eslint-disable-next-line vue/return-in-computed-property
const closingButtonText = computed(() => {

View File

@ -1,5 +1,5 @@
<template>
<p class="mb-4 font-bold">{{ $t("Nächste Termine:") }}</p>
<p class="mb-4 font-bold">{{ $t("dueDates.nextDueDates") }}:</p>
<!-- ul>
<li class="border-b border-t py-3">
<p class="pr-12">24. November 2022, 11 Uhr - Austausch mit Trainer</p>

View File

@ -77,7 +77,7 @@ function hasMoreItemsForType<T>(itemType: MediaBlockType, items: T[]) {
<div class="flex justify-between md:flex-col lg:flex-row">
<div class="lg:w-6/12">
<h3 class="text-large mb-3 font-normal text-gray-900">
{{ $t("mediaLibrary.handlungsfelder.title", 1) }}
{{ $t("mediaLibrary.handlungsfelder.title_one") }}
</h3>
<h1 class="mb-4 lg:mb-8" data-cy="hf-title">{{ mediaCategory.title }}</h1>
<p class="text-large">{{ mediaCategory.introduction_text }}</p>

View File

@ -27,7 +27,9 @@ watch(dropdownSelected, (newValue) =>
<template>
<div class="container-large">
<div class="mb-10 mt-6 flex flex-col items-center justify-between lg:flex-row">
<h1>{{ $t("mediaLibrary.handlungsfelder.title", categories.length) }}</h1>
<h1>
{{ $t("mediaLibrary.handlungsfelder.title", { count: categories.length }) }}
</h1>
<!-- <ItDropdownSelect v-model="dropdownSelected" :items="mediaStore.availableLearningPaths"></ItDropdownSelect>-->
</div>
<div v-if="mediaStore.mediaLibraryPage">

View File

@ -35,7 +35,7 @@ watch(dropdownSelected, (newValue) =>
</div>
<OverviewCard
v-if="mediaStore.mediaLibraryPage"
:title="$t('mediaLibrary.handlungsfelder.title', 2)"
:title="$t('mediaLibrary.handlungsfelder.title_other')"
:call2-action="$t('general.show')"
:link="`${mediaStore.mediaLibraryPage.frontend_url}/category`"
:description="$t('mediaLibrary.handlungsfelder.description')"

View File

@ -1,7 +1,7 @@
import log from "loglevel";
import { bustItGetCache, itGetCached, itPost } from "@/fetchHelpers";
import { loadLocaleMessages, setI18nLanguage } from "@/i18n";
import { setI18nLanguage } from "@/i18nextWrapper";
import dayjs from "dayjs";
import { defineStore } from "pinia";
@ -74,7 +74,6 @@ async function setLocale(language: AvailableLanguages) {
await import("dayjs/locale/it");
}
dayjs.locale(language);
await loadLocaleMessages(language);
setI18nLanguage(language);
}

View File

@ -1,9 +1,8 @@
import { fileURLToPath, URL } from "url";
import alias from "@rollup/plugin-alias";
import vue from "@vitejs/plugin-vue";
import { defineConfig, loadEnv } from "vite";
// import vueI18n from '@intlify/vite-plugin-vue-i18n'
import alias from "@rollup/plugin-alias";
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
@ -33,11 +32,7 @@ export default defineConfig(({ mode }) => {
// ]
}),
],
define: {
__VUE_I18N_FULL_INSTALL__: true,
__VUE_I18N_LEGACY_API__: false,
__INTLIFY_PROD_DEVTOOLS__: false,
},
define: {},
server: {
port: 5173,
hmr: { port: 5173 },

Binary file not shown.

51
package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "vbv_lernwelt_cypress",
"version": "1.0.0",
"dependencies": {
"i18next": "^23.2.6",
"pa11y": "^6.2.3"
},
"devDependencies": {
@ -16,10 +17,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz",
"integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==",
"dev": true,
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
"integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
@ -1673,6 +1673,28 @@
"node": ">=8.12.0"
}
},
"node_modules/i18next": {
"version": "23.2.6",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.2.6.tgz",
"integrity": "sha512-i0P2XBisewaICJ7UQtwymeJj6cXUigM+s8XNIXmWk4oJ8iTok2taCbOTX0ps+u9DFcQ6FWH6xLIU0dLEnMaNbA==",
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"dependencies": {
"@babel/runtime": "^7.22.5"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -2753,8 +2775,7 @@
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"dev": true
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/request-progress": {
"version": "3.0.0",
@ -3400,10 +3421,9 @@
},
"dependencies": {
"@babel/runtime": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz",
"integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==",
"dev": true,
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
"integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==",
"requires": {
"regenerator-runtime": "^0.13.11"
}
@ -4644,6 +4664,14 @@
"integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==",
"dev": true
},
"i18next": {
"version": "23.2.6",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.2.6.tgz",
"integrity": "sha512-i0P2XBisewaICJ7UQtwymeJj6cXUigM+s8XNIXmWk4oJ8iTok2taCbOTX0ps+u9DFcQ6FWH6xLIU0dLEnMaNbA==",
"requires": {
"@babel/runtime": "^7.22.5"
}
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -5398,8 +5426,7 @@
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"dev": true
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"request-progress": {
"version": "3.0.0",

View File

@ -6,14 +6,14 @@
"test": "echo \"Error: no test specified\" && exit 1",
"cypress:open": "cypress open",
"cypress:ci": "cypress-cloud run --parallel --record --ci-build-id $(echo -n ${BITBUCKET_STEP_UUID}-${BITBUCKET_BUILD_NUMBER})",
"prettier": "npm run prettier --prefix client",
"vue-i18n-extract": "npm run vue-i18n-extract --prefix client"
"prettier": "npm run prettier --prefix client"
},
"devDependencies": {
"cypress": "^12.15.0",
"cypress-cloud": "^1.7.4"
},
"dependencies": {
"i18next": "^23.2.6",
"pa11y": "^6.2.3"
}
}

View File

@ -506,11 +506,12 @@ CSP_DEFAULT_SRC = [
"ws://127.0.0.1:5173",
"localhost:8000",
"localhost:8001",
"*.locize.app",
"blob:",
"data:",
"http://*",
]
CSP_FRAME_ANCESTORS = ("'self'",)
CSP_FRAME_ANCESTORS = ("'self'", "https://www.locize.app")
SPECTACULAR_SETTINGS = {
"TITLE": "VBV Lernwelt API",