From 5985cd1fadf674285c32596d8dc54cd42bd34a42 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 24 Nov 2023 00:40:25 +0100 Subject: [PATCH] properly use i18n and enforce it --- .eslintrc.json | 4 + package-lock.json | 218 ++++++++++++++++++++++++- package.json | 1 + renderer/_default.page.client.ts | 4 +- renderer/_default.page.server.ts | 6 +- renderer/app.ts | 2 +- renderer/plugins/i18n.ts | 1 + src/components/ClickCounter.delete.vue | 4 +- src/components/menu/TopMenu.vue | 34 +++- src/locales/de.json | 52 +++++- src/locales/en.json | 52 +++++- src/pages/_error.page.vue | 8 +- src/pages/about/index.page.vue | 24 +-- src/pages/app/index.page.server.ts | 4 +- src/pages/app/index.page.vue | 48 ++++-- src/pages/index/index.page.vue | 14 +- src/stories/ExampleHeader.vue | 1 + src/stories/ExamplePage.vue | 1 + vite.config.ts | 1 + 19 files changed, 425 insertions(+), 54 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 5814052..c70e255 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,6 +13,7 @@ "plugin:promise/recommended", "plugin:security/recommended", "plugin:vue/vue3-recommended", + "plugin:@intlify/vue-i18n/recommended", "plugin:storybook/recommended" ], "parserOptions": { @@ -32,6 +33,9 @@ "import/resolver": { "typescript": true, "node": true + }, + "vue-i18n": { + "localeDir": "./src/locales/*.json" } }, "rules": { diff --git a/package-lock.json b/package-lock.json index 8586c17..ebcb226 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.1.0", + "@intlify/eslint-plugin-vue-i18n": "^2.0.0", "@storybook/addon-essentials": "^7.5.3", "@storybook/addon-interactions": "^7.5.3", "@storybook/addon-links": "^7.5.3", @@ -3123,6 +3124,212 @@ "url": "https://github.com/sponsors/kazupon" } }, + "node_modules/@intlify/eslint-plugin-vue-i18n": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@intlify/eslint-plugin-vue-i18n/-/eslint-plugin-vue-i18n-2.0.0.tgz", + "integrity": "sha512-ECBD0TvQNa56XKyuM6FPIGAAl7MP6ODcgjBQJrzucNxcTb8fYTWmZ+xgBuvmvAtA0iE0D4Wp18UMild2N0bGyw==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.2.0", + "@intlify/core-base": "^9.1.9", + "@intlify/message-compiler": "^9.1.9", + "debug": "^4.3.1", + "glob": "^8.0.0", + "ignore": "^5.0.5", + "is-language-code": "^3.1.0", + "js-yaml": "^4.0.0", + "json5": "^2.1.3", + "jsonc-eslint-parser": "^2.0.0", + "lodash": "^4.17.11", + "parse5": "^7.0.0", + "semver": "^7.3.4", + "vue-eslint-parser": "^9.0.0", + "yaml-eslint-parser": "^1.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/minimatch/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@intlify/eslint-plugin-vue-i18n/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/@intlify/message-compiler": { "version": "9.7.1", "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.7.1.tgz", @@ -16323,6 +16530,15 @@ "node": ">=8" } }, + "node_modules/is-language-code": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-language-code/-/is-language-code-3.1.0.tgz", + "integrity": "sha512-zJdQ3QTeLye+iphMeK3wks+vXSRFKh68/Pnlw7aOfApFSEIOhYa8P9vwwa6QrImNNBMJTiL1PpYF0f4BxDuEgA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.14.0" + } + }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -21872,8 +22088,6 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "entities": "^4.4.0" }, diff --git a/package.json b/package.json index f95bd46..9145f78 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.1.0", + "@intlify/eslint-plugin-vue-i18n": "^2.0.0", "@storybook/addon-essentials": "^7.5.3", "@storybook/addon-interactions": "^7.5.3", "@storybook/addon-links": "^7.5.3", diff --git a/renderer/_default.page.client.ts b/renderer/_default.page.client.ts index d937753..e76dd36 100644 --- a/renderer/_default.page.client.ts +++ b/renderer/_default.page.client.ts @@ -5,7 +5,7 @@ import type { PageContext, VikePageContext } from '#types/PageContext' let app: ReturnType async function render(pageContext: VikePageContext & PageContext) { if (!app) { - app = createApp(pageContext) + app = createApp(pageContext).app app.mount('#app') } else { app.changePage(pageContext) @@ -17,9 +17,11 @@ function onHydrationEnd() { } function onPageTransitionStart() { // console.log('Page transition start') + // document.body.classList.add('page-transition') } function onPageTransitionEnd() { // console.log('Page transition end') + // document.body.classList.remove('page-transition') } export const clientRouting = true diff --git a/renderer/_default.page.server.ts b/renderer/_default.page.server.ts index f6d55fe..3f50db5 100644 --- a/renderer/_default.page.server.ts +++ b/renderer/_default.page.server.ts @@ -13,7 +13,9 @@ import type { App } from 'vue' export const passToClient = ['pageProps', /* 'urlPathname', */ 'routeParams'] async function render(pageContext: PageContextServer & PageContext) { - const app = createApp(pageContext, false) + const { app, i18n } = createApp(pageContext, false) + + const locale = i18n.global.locale.value const appHtml = await renderToString(app) @@ -23,7 +25,7 @@ async function render(pageContext: PageContextServer & PageContext) { const desc = (documentProps && documentProps.description) || META.DEFAULT_DESCRIPTION const documentHtml = escapeInject` - + diff --git a/renderer/app.ts b/renderer/app.ts index 8175dc4..ed415cf 100644 --- a/renderer/app.ts +++ b/renderer/app.ts @@ -61,7 +61,7 @@ function createApp(pageContext: VikePageContext & PageContext, isClient = true) // Make pageContext available from any Vue component setPageContext(app, pageContextReactive) - return app + return { app, i18n } } // Same as `Object.assign()` but with type inference diff --git a/renderer/plugins/i18n.ts b/renderer/plugins/i18n.ts index 9ed0a5f..6ea0ce4 100644 --- a/renderer/plugins/i18n.ts +++ b/renderer/plugins/i18n.ts @@ -7,6 +7,7 @@ import en from '#src/locales/en.json' export default createI18n({ legacy: false, // Vuetify does not support the legacy mode of vue-i18n + globalInjection: true, locale: 'de', fallbackLocale: 'en', messages: { de, en }, diff --git a/src/components/ClickCounter.delete.vue b/src/components/ClickCounter.delete.vue index c3409b6..dabfd3d 100644 --- a/src/components/ClickCounter.delete.vue +++ b/src/components/ClickCounter.delete.vue @@ -1,5 +1,7 @@ diff --git a/src/locales/de.json b/src/locales/de.json index 6c7a761..4dcdf54 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -1,3 +1,53 @@ { - "counter": "Zähler" + "about": { + "h1": "Über", + "link1": "github.com", + "link2": "Webseite", + "text1": "Um mehr über diese Kochplatte zu erfahren kannst du dir den Quellcode auf {link} anschauen.", + "text2": "Du willst mehr erfahren? Besuche uns auf {link}." + }, + "app": { + "inc": { + "h1": "Erhöhe den Zähler", + "menu": "Erhöhe", + "text": "Erhöhe: {count}" + }, + "reset": { + "h1": "Den Zähler zurücksetzen", + "menu": "Zurücksetzen", + "text": "Zurücksetzent: {count}" + }, + "value": { + "h1": "Der Zähler", + "menu": "Wert", + "text": "Der aktuelle Wert des Zählers lautet: {count}" + } + }, + "error": { + "404": { + "h1": "404 Seite nicht gefunden", + "text": "Diese Seite konnte nicht gefunden werden." + }, + "500": { + "h1": "500 Interner Fehler", + "text": "Irgendetwas ist schief gegangen." + } + }, + "home": { + "greet1": "Hochachtungsvoll", + "greet2": "Dein IT Team For Change", + "h1": "IT4C Frontend Kochplatte", + "text1": "Willkommen zu diesem minimalen Frontendstarter.", + "text2": "Es handelt sich um ein einfaches Beispiel um zu zeigen was so geht - nichts besonderes.", + "text3": "In dem Anwendungsbereich wirst du ein Zählerbeispiel finden, welches den Browserspeicher verwendet.", + "text4": "Fröhliches Programmieren" + }, + "language": { + "german": "Deutsch" + }, + "menu": { + "about": "Über", + "app": "Anwendung", + "home": "Daheim" + } } diff --git a/src/locales/en.json b/src/locales/en.json index 99f45b4..ac930f1 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1,3 +1,53 @@ { - "counter": "Counter" + "about": { + "h1": "About", + "link1": "github.com", + "link2": "website", + "text1": "To find out more about this boilerplate you can look at the sources on {link}.", + "text2": "Want to get in touch? Find out how on our {link}." + }, + "app": { + "inc": { + "h1": "Increase the Counter", + "menu": "Increase", + "text": "Increase: {count}" + }, + "reset": { + "h1": "Reset the Counter", + "menu": "Reset", + "text": "Reset: {count}" + }, + "value": { + "h1": "The Counter", + "menu": "Value", + "text": "The current value of the counter is: {count}" + } + }, + "error": { + "404": { + "h1": "404 Page Not Found", + "text": "This page could not be found." + }, + "500": { + "h1": "500 Internal Error", + "text": "Something went wrong." + } + }, + "home": { + "greet1": "Sincerly", + "greet2": "Your IT Team For Change", + "h1": "IT4C Frontend Boilerplate", + "text1": "Welcome to this minimal starter for frontends.", + "text2": "This is just a basic example to demonstrate things - nothing fancy.", + "text3": "In the App Section you will find a counter example utilizing the local storage.", + "text4": "Happy Coding" + }, + "language": { + "german": "German" + }, + "menu": { + "about": "About", + "app": "App", + "home": "Home" + } } diff --git a/src/pages/_error.page.vue b/src/pages/_error.page.vue index 13957f8..290d751 100644 --- a/src/pages/_error.page.vue +++ b/src/pages/_error.page.vue @@ -1,11 +1,11 @@ diff --git a/src/pages/about/index.page.vue b/src/pages/about/index.page.vue index 3e74b4d..ca810ef 100644 --- a/src/pages/about/index.page.vue +++ b/src/pages/about/index.page.vue @@ -1,17 +1,19 @@ diff --git a/src/pages/app/index.page.server.ts b/src/pages/app/index.page.server.ts index e1729b7..4659fa0 100644 --- a/src/pages/app/index.page.server.ts +++ b/src/pages/app/index.page.server.ts @@ -3,11 +3,9 @@ import type { PageContextBuiltInServer } from 'vike/types' export { onBeforeRender } async function onBeforeRender(pageContext: PageContextBuiltInServer) { - const { page } = pageContext.routeParams - const pageProps = { page } return { pageContext: { - pageProps, + pageProps: pageContext.routeParams, }, } } diff --git a/src/pages/app/index.page.vue b/src/pages/app/index.page.vue index cd16b08..7656316 100644 --- a/src/pages/app/index.page.vue +++ b/src/pages/app/index.page.vue @@ -2,34 +2,54 @@ diff --git a/src/pages/index/index.page.vue b/src/pages/index/index.page.vue index b66529c..a614885 100644 --- a/src/pages/index/index.page.vue +++ b/src/pages/index/index.page.vue @@ -1,16 +1,16 @@ diff --git a/src/stories/ExampleHeader.vue b/src/stories/ExampleHeader.vue index f1505cb..a3b2724 100644 --- a/src/stories/ExampleHeader.vue +++ b/src/stories/ExampleHeader.vue @@ -1,3 +1,4 @@ +