diff --git a/client/src/components/ui/RatingScale.vue b/client/src/components/ui/RatingScale.vue
index 3b5c12f1..fa3805bc 100644
--- a/client/src/components/ui/RatingScale.vue
+++ b/client/src/components/ui/RatingScale.vue
@@ -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
diff --git a/client/src/i18n.ts b/client/src/i18n.ts
deleted file mode 100644
index 58acc25c..00000000
--- a/client/src/i18n.ts
+++ /dev/null
@@ -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();
-}
diff --git a/client/src/i18nextWrapper.ts b/client/src/i18nextWrapper.ts
new file mode 100644
index 00000000..4d3d0d3b
--- /dev/null
+++ b/client/src/i18nextWrapper.ts
@@ -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();
+}
diff --git a/client/src/locales/de.json b/client/src/locales/de/translation.json
similarity index 81%
rename from client/src/locales/de.json
rename to client/src/locales/de/translation.json
index 50ae1ca8..ad9eb840 100644
--- a/client/src/locales/de.json
+++ b/client/src/locales/de/translation.json
@@ -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 \"{title}\" löschen? Diese Aktion ist nicht umkehrbar.",
+ "deleteModalWarning": "Willst du die Unterlage \"{{title}}\" löschen? 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": {
diff --git a/client/src/locales/fr.json b/client/src/locales/fr/translation.json
similarity index 79%
rename from client/src/locales/fr.json
rename to client/src/locales/fr/translation.json
index 6157b7f4..df0983bf 100644
--- a/client/src/locales/fr.json
+++ b/client/src/locales/fr/translation.json
@@ -1,11 +1,7 @@
{
"Benutzername": "Nom d’utilisateur",
- "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 t’inscrire.",
"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 n’est 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 j’ai 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 n’a é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 n’a pas pu être enregistrée.",
"performanceObjectivesTitle": "Objectifs",
"showAssessmentDocument": "Afficher l’outil 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 \"{title}\" supprimer les documents ? Cette action est irréversible.",
+ "deleteModalWarning": "Veux-tu \"{{title}}\" supprimer les documents ? 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 d’application 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 n’as é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": "Qu’est-ce qui t’a 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 à l’intention 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, t’invite à lui adresser un feed-back. Cela n’est pas obligatoire, mais lui permettrait d’améliorer son cours.",
+ "intro": "{{name}}, ton formateur / ta formatrice, t’invite à lui adresser un feed-back. Cela n’est pas obligatoire, mais lui permettrait d’améliorer son cours.",
"materialsRatingLabel": "Si oui : Que pensez-vous des documents fournis pour le travail préparatoire (par ex. eLearning) ?",
"noFeedbacks": "Aucun feed-back n’a 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 l’examen | Résultats de l’examen",
- "feedback": "Feed-back | Feed-backs",
+ "examResult_many": "Résultats de l’examen",
+ "examResult_one": "Résultat de l’examen",
+ "examResult_other": "Résultats de l’examen",
+ "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": "C’est parti !",
"submission": "Remise",
"title": "myAFA",
- "transferTask": "Exercice d’application | Exercices d’application",
+ "transferTask_many": "Exercices d’application",
+ "transferTask_one": "Exercice d’application",
+ "transferTask_other": "Exercices d’application",
"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 t’inscrire."
+ },
"mainNavigation": {
"logout": "Se déconnecter",
"profile": "Profil"
@@ -190,7 +211,9 @@
"mediaLibrary": {
"handlungsfelder": {
"description": "Trouve toutes les ressources des champs d’action, comme les outils didactiques, les liens et autres informations utiles.",
- "title": "Champ d’action | Champs d’action"
+ "title_many": "Champs d’action",
+ "title_one": "Champ d’action",
+ "title_other": "Champs d’action"
},
"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": {
diff --git a/client/src/locales/it.json b/client/src/locales/it/translation.json
similarity index 80%
rename from client/src/locales/it.json
rename to client/src/locales/it/translation.json
index 0ad47d5d..b1dd568b 100644
--- a/client/src/locales/it.json
+++ b/client/src/locales/it/translation.json
@@ -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 l’ultima 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 \"{title}\"? Una volta eseguita, l’operazione non potrà più essere annullata.",
+ "deleteModalWarning": "Desideri davvero eliminare il documento \"{{title}}\"? Una volta eseguita, l’operazione 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": "Cos’hai 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 dell’istruttore/istruttrice del corso?",
"instructorOpenFeedbackLabel": "Cos’altro vorresti ancora dire all’istruttore/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 dell’esame | Risultati degli esami",
- "feedback": "Feedback | Feedback",
+ "examResult_many": "Risultati degli esami",
+ "examResult_one": "Risultato dell’esame",
+ "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 d’azione, come materiali didattici, link e altre informazioni utili.",
- "title": "Campo d’azione | Campi d’azione"
+ "title_many": "Campi d’azione",
+ "title_one": "Campo d’azione",
+ "title_other": "Campi d’azione"
},
"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": {
diff --git a/client/src/main.ts b/client/src/main.ts
index d56d976f..84ad3781 100644
--- a/client/src/main.ts
+++ b/client/src/main.ts
@@ -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");
});
diff --git a/client/src/pages/LoginPage.vue b/client/src/pages/LoginPage.vue
index 49bd3961..74244c8a 100644
--- a/client/src/pages/LoginPage.vue
+++ b/client/src/pages/LoginPage.vue
@@ -25,7 +25,7 @@ const userStore = useUserStore();
-
Login
+
{{ $t("login.login") }}
diff --git a/client/src/pages/NotificationsPage.vue b/client/src/pages/NotificationsPage.vue
index c278615c..248bcbfc 100644
--- a/client/src/pages/NotificationsPage.vue
+++ b/client/src/pages/NotificationsPage.vue
@@ -16,7 +16,7 @@ async function loadAdditionalNotifications() {
-
{{ $t("general.notification", 2) }}
+
{{ $t("general.notification_other") }}
diff --git a/client/src/pages/cockpit/CockpitUserProfilePage.vue b/client/src/pages/cockpit/CockpitUserProfilePage.vue
index 066aa444..af001329 100644
--- a/client/src/pages/cockpit/CockpitUserProfilePage.vue
+++ b/client/src/pages/cockpit/CockpitUserProfilePage.vue
@@ -74,13 +74,13 @@ function setActiveClasses(isActive: boolean) {
-
+
-
+
-
+
diff --git a/client/src/pages/cockpit/FeedbackPage.vue b/client/src/pages/cockpit/FeedbackPage.vue
index 21e8fcf6..d92f89fb 100644
--- a/client/src/pages/cockpit/FeedbackPage.vue
+++ b/client/src/pages/cockpit/FeedbackPage.vue
@@ -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 = [
{
diff --git a/client/src/pages/cockpit/cockpitPage/CockpitPage.vue b/client/src/pages/cockpit/cockpitPage/CockpitPage.vue
index c59bb579..cd45cbf1 100644
--- a/client/src/pages/cockpit/cockpitPage/CockpitPage.vue
+++ b/client/src/pages/cockpit/cockpitPage/CockpitPage.vue
@@ -99,11 +99,7 @@ function setActiveClasses(translationKey: string) {
{{ $t("Trainerunterlagen") }}
- {{
- $t(
- "Hier findest du die Trainerunterlagen (Lösungsblätter, Präsentationen etc.) für deinen Circle."
- )
- }}
+ {{ $t("cockpit.trainerFilesText") }}
diff --git a/client/src/pages/competence/PerformanceCriteriaPage.vue b/client/src/pages/competence/PerformanceCriteriaPage.vue
index ae84a85a..d4bb10ed 100644
--- a/client/src/pages/competence/PerformanceCriteriaPage.vue
+++ b/client/src/pages/competence/PerformanceCriteriaPage.vue
@@ -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}`"
>
- zurück
+ {{ $t("general.back") }}
diff --git a/client/src/pages/learningPath/circlePage/DocumentSection.vue b/client/src/pages/learningPath/circlePage/DocumentSection.vue
index a3378f32..edabf392 100644
--- a/client/src/pages/learningPath/circlePage/DocumentSection.vue
+++ b/client/src/pages/learningPath/circlePage/DocumentSection.vue
@@ -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"),
diff --git a/client/src/pages/learningPath/circlePage/DocumentUploadForm.vue b/client/src/pages/learningPath/circlePage/DocumentUploadForm.vue
index ac063a81..420eea55 100644
--- a/client/src/pages/learningPath/circlePage/DocumentUploadForm.vue
+++ b/client/src/pages/learningPath/circlePage/DocumentUploadForm.vue
@@ -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(), {
learningSequences: () => [],
diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue
index 76814f07..4479691b 100644
--- a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue
+++ b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue
@@ -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,
diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue
index f5771ce4..0fec60c3 100644
--- a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue
+++ b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue
@@ -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();
diff --git a/client/src/pages/learningPath/learningContentPage/layouts/LearningContentFooter.vue b/client/src/pages/learningPath/learningContentPage/layouts/LearningContentFooter.vue
index c77d0147..f4346886 100644
--- a/client/src/pages/learningPath/learningContentPage/layouts/LearningContentFooter.vue
+++ b/client/src/pages/learningPath/learningContentPage/layouts/LearningContentFooter.vue
@@ -1,7 +1,7 @@