Add locize cli script

This commit is contained in:
Daniel Egger 2023-07-05 16:15:17 +02:00
parent 828ea32a46
commit 6e7935a005
13 changed files with 1064 additions and 52 deletions

View File

@ -101,19 +101,35 @@ Preferences -> Tools -> Actions on Save
## Translations ## Translations
We use (vue-i18n)[https://kazupon.github.io/vue-i18n/] for translations We use (Locize)[https://locize.com] together with (i18next)[https://www.i18next.com/]
and (vue-i18n-extract)[https://github.com/Spittal/vue-i18n-extract] for helper for translations on the Frontend.
scripts.
``` The master for translated files is on Locize. Missing keys get automatically added to
# will create a report on command line with missing translations Locize, no manual step is needed.
npm run vue-i18n-extract The translations are done on Locize.
# add missing translations to files, see docs for more options Please make sure that the required environment variables are set
cd client (see ./env_secrets/local_daniel.env for the values):
npx vue-i18n-extract --add
* LOCIZE_PROJECT_ID
* LOCIZE_API_KEY
The files in ./client/locales are only used as reference and are not the master!
But you can still sync the local locale files with Locize with the following command.
Please be careful and do not lose translations. The savest way is to only add the
keys to the German file and let Locize translate them.
```bash
npm run locize:sync
``` ```
### "_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 ## Deployment to CapRover
### CapRover Dev (vbv-lernwelt.control.iterativ.ch) ### CapRover Dev (vbv-lernwelt.control.iterativ.ch)

View File

@ -1,6 +1,6 @@
import { Preview, setup } from "@storybook/vue3"; import { Preview, setup } from "@storybook/vue3";
import { createI18n } from "vue-i18n"; import { createI18n } from "vue-i18n";
import de from "../src/locales/de.json"; import de from "../src/locales/de/translation.json";
import "../tailwind.css"; import "../tailwind.css";
import { withVueRouter } from "./mockRouter"; import { withVueRouter } from "./mockRouter";

958
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,8 @@
"storybook": "storybook dev -p 6006", "storybook": "storybook dev -p 6006",
"tailwind": "tailwindcss -i tailwind.css -o ../server/vbv_lernwelt/static/css/tailwind.css --watch", "tailwind": "tailwindcss -i tailwind.css -o ../server/vbv_lernwelt/static/css/tailwind.css --watch",
"test": "vitest run", "test": "vitest run",
"typecheck": "npm run codegen && vue-tsc --noEmit -p tsconfig.app.json --composite false" "typecheck": "npm run codegen && vue-tsc --noEmit -p tsconfig.app.json --composite false",
"locize:sync": "locize sync --path ./src/locales"
}, },
"dependencies": { "dependencies": {
"@headlessui/tailwindcss": "^0.1.3", "@headlessui/tailwindcss": "^0.1.3",
@ -35,6 +36,7 @@
"graphql": "^16.6.0", "graphql": "^16.6.0",
"i18next-locize-backend": "^6.2.2", "i18next-locize-backend": "^6.2.2",
"i18next-vue": "^2.2.0", "i18next-vue": "^2.2.0",
"locize": "^2.4.6",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"loglevel": "^1.8.0", "loglevel": "^1.8.0",
"mitt": "^3.0.0", "mitt": "^3.0.0",
@ -79,6 +81,7 @@
"eslint-plugin-storybook": "^0.6.12", "eslint-plugin-storybook": "^0.6.12",
"eslint-plugin-vue": "^9.15.0", "eslint-plugin-vue": "^9.15.0",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"locize-cli": "^7.14.6",
"postcss": "^8.4.14", "postcss": "^8.4.14",
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",
"prettier": "^2.8.8", "prettier": "^2.8.8",

View File

@ -1,9 +1,17 @@
import type { AvailableLanguages } from "@/stores/user"; import type { AvailableLanguages } from "@/stores/user";
import i18next from "i18next"; import i18next from "i18next";
import { locizePlugin } from "locize";
import Backend from "i18next-locize-backend"; import Backend from "i18next-locize-backend";
import { nextTick } from "vue"; import { nextTick } from "vue";
declare module "i18next" {
interface CustomTypeOptions {
returnNull: false;
}
}
export const SUPPORT_LOCALES: AvailableLanguages[] = ["de", "fr", "it"]; export const SUPPORT_LOCALES: AvailableLanguages[] = ["de", "fr", "it"];
export function i18nextInit() { export function i18nextInit() {
@ -15,13 +23,16 @@ export function i18nextInit() {
// init i18next // init i18next
// for all options read: https://www.i18next.com/overview/configuration-options // for all options read: https://www.i18next.com/overview/configuration-options
.use(Backend) .use(Backend)
.use(locizePlugin)
.init({ .init({
debug: true, debug: true,
fallbackLng: "de", fallbackLng: "de",
defaultNS: "translation", defaultNS: "translation",
returnNull: false,
saveMissing: true,
backend: { backend: {
projectId: "7518c269-cbf7-4d25-bc5c-6ceba2a8b74b", projectId: import.meta.env.VITE_LOCIZE_PROJECTID,
apiKey: "0001d5bd-04c9-4ade-bedd-308a01d86860", apiKey: import.meta.env.VITE_LOCIZE_API_KEY,
}, },
}) })
); );

View File

@ -1,14 +1,9 @@
{ {
"Benutzername": "Benutzername", "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", "MS Teams öffnen": "MS Teams öffnen",
"Nächste Termine:": "Nächste Termine:",
"Passwort": "Passwort", "Passwort": "Passwort",
"SSO Login/Registration": "SSO Login/Registration",
"Trainerunterlagen": "Trainerunterlagen", "Trainerunterlagen": "Trainerunterlagen",
"Zur Zeit sind keine Termine vorhanden": "Zur Zeit sind keine Termine vorhanden", "Zur Zeit sind keine Termine vorhanden": "Zur Zeit sind keine Termine vorhanden",
"welcome": "Hallo Welt",
"assignment": { "assignment": {
"acceptConditionsDisclaimer": "Bedingungen akzeptieren und Ergebnisse abgeben", "acceptConditionsDisclaimer": "Bedingungen akzeptieren und Ergebnisse abgeben",
"assessmentDocumentDisclaimer": "Diese geleitete Fallarbeit wird auf Grund des folgenden Beurteilungsinstrument bewertet:", "assessmentDocumentDisclaimer": "Diese geleitete Fallarbeit wird auf Grund des folgenden Beurteilungsinstrument bewertet:",
@ -69,7 +64,8 @@
"profileLink": "Profil anzeigen", "profileLink": "Profil anzeigen",
"progress": "Teilnehmende / Status", "progress": "Teilnehmende / Status",
"tasksDone": "Erledigte Transferaufträge von Teilnehmer.", "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": { "competences": {
"assessAgain": "Sich nochmals einschätzen", "assessAgain": "Sich nochmals einschätzen",
@ -92,6 +88,9 @@
"nocourses": "Du wurdest noch keinem Lehrgang zugewiesen.", "nocourses": "Du wurdest noch keinem Lehrgang zugewiesen.",
"welcome": "Willkommen, {{name}}" "welcome": "Willkommen, {{name}}"
}, },
"dueDates": {
"nextDueDates": "Nächste Termine"
},
"feedback": { "feedback": {
"answers": "Antworten", "answers": "Antworten",
"areYouSatisfied": "Wie zufrieden bist du?", "areYouSatisfied": "Wie zufrieden bist du?",
@ -143,10 +142,10 @@
"certificate_other": "Zertifikate", "certificate_other": "Zertifikate",
"circles": "Circles", "circles": "Circles",
"close": "Schliessen", "close": "Schliessen",
"exam_one": "Prüfung",
"exam_other": "Prüfungen",
"examResult_one": "Prüfungsresultat", "examResult_one": "Prüfungsresultat",
"examResult_other": "Prüfungsresultate", "examResult_other": "Prüfungsresultate",
"exam_one": "Prüfung",
"exam_other": "Prüfungen",
"feedback_one": "Feedback", "feedback_one": "Feedback",
"feedback_other": "Feedbacks", "feedback_other": "Feedbacks",
"introduction": "Einleitung", "introduction": "Einleitung",
@ -190,6 +189,12 @@
"topics": "Themen:", "topics": "Themen:",
"welcomeBack": "Willkommen zurück in deinem Lehrgang:" "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": { "mainNavigation": {
"logout": "Abmelden", "logout": "Abmelden",
"profile": "Profil" "profile": "Profil"
@ -231,5 +236,6 @@
}, },
"settings": { "settings": {
"emailNotifications": "Email Benachrichtigungen" "emailNotifications": "Email Benachrichtigungen"
} },
"welcome": "Hallo Welt"
} }

View File

@ -1,11 +1,7 @@
{ {
"Benutzername": "Nom dutilisateur", "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 ", "MS Teams öffnen": "Ouvrir MS Teams ",
"Nächste Termine:": "Prochaines réunions :",
"Passwort": "Mot de passe", "Passwort": "Mot de passe",
"SSO Login/Registration": "Connexion SSO / Inscription",
"Trainerunterlagen": "Documents du formateur / de la formatrice", "Trainerunterlagen": "Documents du formateur / de la formatrice",
"Zur Zeit sind keine Termine vorhanden": "Aucune réunion nest prévue pour le moment", "Zur Zeit sind keine Termine vorhanden": "Aucune réunion nest prévue pour le moment",
"assignment": { "assignment": {
@ -68,7 +64,8 @@
"profileLink": "Afficher le profil", "profileLink": "Afficher le profil",
"progress": "Personne participante / Statut", "progress": "Personne participante / Statut",
"tasksDone": "Exercices dapplication terminés par les participants.", "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": { "competences": {
"assessAgain": "Sévaluer à nouveau", "assessAgain": "Sévaluer à nouveau",
@ -91,6 +88,9 @@
"nocourses": "Tu nas été affecté(e) à aucune formation encore.", "nocourses": "Tu nas été affecté(e) à aucune formation encore.",
"welcome": "Bienvenue, {{name}}" "welcome": "Bienvenue, {{name}}"
}, },
"dueDates": {
"nextDueDates": "Prochaines réunions"
},
"feedback": { "feedback": {
"answers": "Réponses", "answers": "Réponses",
"areYouSatisfied": "Quel est ton degré de satisfaction ?", "areYouSatisfied": "Quel est ton degré de satisfaction ?",
@ -138,14 +138,18 @@
"backCapitalized": "@.capitalize:general.back", "backCapitalized": "@.capitalize:general.back",
"backToCircle": "Revenir au cercle", "backToCircle": "Revenir au cercle",
"backToLearningPath": "Revenir au programme de formation", "backToLearningPath": "Revenir au programme de formation",
"certificate_many": "Certificats",
"certificate_one": "Certificat", "certificate_one": "Certificat",
"certificate_other": "Certificats", "certificate_other": "Certificats",
"circles": "Cercles", "circles": "Cercles",
"close": "Fermer", "close": "Fermer",
"exam_one": "Examen", "examResult_many": "Résultats de lexamen",
"exam_other": "Examens",
"examResult_one": "Résultat de lexamen", "examResult_one": "Résultat de lexamen",
"examResult_other": "Résultats 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_one": "Feed-back",
"feedback_other": "Feed-backs", "feedback_other": "Feed-backs",
"introduction": "Introduction", "introduction": "Introduction",
@ -155,6 +159,7 @@
"next": "Continuer", "next": "Continuer",
"nextStep": "Cela continue", "nextStep": "Cela continue",
"no": "Non", "no": "Non",
"notification_many": "Notifications",
"notification_one": "Notification", "notification_one": "Notification",
"notification_other": "Notifications", "notification_other": "Notifications",
"profileLink": "Détails", "profileLink": "Détails",
@ -167,6 +172,7 @@
"start": "Cest parti !", "start": "Cest parti !",
"submission": "Remise", "submission": "Remise",
"title": "myAFA", "title": "myAFA",
"transferTask_many": "Exercices dapplication",
"transferTask_one": "Exercice dapplication", "transferTask_one": "Exercice dapplication",
"transferTask_other": "Exercices dapplication", "transferTask_other": "Exercices dapplication",
"yes": "Oui" "yes": "Oui"
@ -189,6 +195,12 @@
"topics": "Thèmes :", "topics": "Thèmes :",
"welcomeBack": "Cela fait plaisir de te revoir dans ta formation :" "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": { "mainNavigation": {
"logout": "Se déconnecter", "logout": "Se déconnecter",
"profile": "Profil" "profile": "Profil"
@ -196,6 +208,7 @@
"mediaLibrary": { "mediaLibrary": {
"handlungsfelder": { "handlungsfelder": {
"description": "Trouve toutes les ressources des champs daction, comme les outils didactiques, les liens et autres informations utiles.", "description": "Trouve toutes les ressources des champs daction, comme les outils didactiques, les liens et autres informations utiles.",
"title_many": "Champs daction",
"title_one": "Champ daction", "title_one": "Champ daction",
"title_other": "Champs daction" "title_other": "Champs daction"
}, },

View File

@ -1,11 +1,7 @@
{ {
"Benutzername": "Nome utente", "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", "MS Teams öffnen": "Aprire MS Teams",
"Nächste Termine:": "Prossime scadenze:",
"Passwort": "Password", "Passwort": "Password",
"SSO Login/Registration": "Login/Registrazione SSO",
"Trainerunterlagen": "Documenti del/della trainer", "Trainerunterlagen": "Documenti del/della trainer",
"Zur Zeit sind keine Termine vorhanden": "Al momento non ci sono scadenze", "Zur Zeit sind keine Termine vorhanden": "Al momento non ci sono scadenze",
"assignment": { "assignment": {
@ -68,7 +64,8 @@
"profileLink": "Mostrare il profilo", "profileLink": "Mostrare il profilo",
"progress": "Partecipanti / Stato", "progress": "Partecipanti / Stato",
"tasksDone": "Incarichi di trasferimento completati dal/dalla partecipante.", "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": { "competences": {
"assessAgain": "Nuova auto-valutazione", "assessAgain": "Nuova auto-valutazione",
@ -91,6 +88,9 @@
"nocourses": "Non sei ancora stato/a assegnato/a a nessun 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": { "feedback": {
"answers": "Risposte", "answers": "Risposte",
"areYouSatisfied": "Quanto sei soddisfatto/a?", "areYouSatisfied": "Quanto sei soddisfatto/a?",
@ -138,14 +138,18 @@
"backCapitalized": "@.capitalize:general.back", "backCapitalized": "@.capitalize:general.back",
"backToCircle": "Torna al Circle", "backToCircle": "Torna al Circle",
"backToLearningPath": "Torna al percorso formativo", "backToLearningPath": "Torna al percorso formativo",
"certificate_many": "Certificati",
"certificate_one": "Certificato", "certificate_one": "Certificato",
"certificate_other": "Certificati", "certificate_other": "Certificati",
"circles": "Circle", "circles": "Circle",
"close": "Chiudere", "close": "Chiudere",
"exam_one": "Esame", "examResult_many": "Risultati degli esami",
"exam_other": "Esami",
"examResult_one": "Risultato dellesame", "examResult_one": "Risultato dellesame",
"examResult_other": "Risultati degli esami", "examResult_other": "Risultati degli esami",
"exam_many": "Esami",
"exam_one": "Esame",
"exam_other": "Esami",
"feedback_many": "Feedback",
"feedback_one": "Feedback", "feedback_one": "Feedback",
"feedback_other": "Feedback", "feedback_other": "Feedback",
"introduction": "Introduzione", "introduction": "Introduzione",
@ -155,6 +159,7 @@
"next": "Avanti", "next": "Avanti",
"nextStep": "Continua", "nextStep": "Continua",
"no": "No", "no": "No",
"notification_many": "Notifiche",
"notification_one": "Notifica", "notification_one": "Notifica",
"notification_other": "Notifiche", "notification_other": "Notifiche",
"profileLink": "Mostrare i dettagli", "profileLink": "Mostrare i dettagli",
@ -167,6 +172,7 @@
"start": "Si comincia", "start": "Si comincia",
"submission": "Consegna", "submission": "Consegna",
"title": "myAFA", "title": "myAFA",
"transferTask_many": "Incarichi di trasferimento",
"transferTask_one": "Incarico di trasferimento", "transferTask_one": "Incarico di trasferimento",
"transferTask_other": "Incarichi di trasferimento", "transferTask_other": "Incarichi di trasferimento",
"yes": "Sì" "yes": "Sì"
@ -189,6 +195,12 @@
"topics": "Temi:", "topics": "Temi:",
"welcomeBack": "Bentornato/a al tuo corso:" "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": { "mainNavigation": {
"logout": "Logout", "logout": "Logout",
"profile": "Profilo" "profile": "Profilo"
@ -196,6 +208,7 @@
"mediaLibrary": { "mediaLibrary": {
"handlungsfelder": { "handlungsfelder": {
"description": "Trova tutte le risorse dei campi dazione, come materiali didattici, link e altre informazioni utili.", "description": "Trova tutte le risorse dei campi dazione, come materiali didattici, link e altre informazioni utili.",
"title_many": "Campi dazione",
"title_one": "Campo dazione", "title_one": "Campo dazione",
"title_other": "Campi dazione" "title_other": "Campi dazione"
}, },

View File

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

View File

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

View File

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

Binary file not shown.

View File

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