Browse Source

feat: Installed i18n on frontend

Owen Diffey 2 years ago
parent
commit
786cc97ed7

+ 470 - 4
frontend/package-lock.json

@@ -9,6 +9,7 @@
       "version": "3.8.0-dev",
       "license": "GPL-3.0",
       "dependencies": {
+        "@intlify/vite-plugin-vue-i18n": "^6.0.1",
         "@vitejs/plugin-vue": "^3.0.3",
         "can-autoplay": "^3.0.2",
         "chart.js": "^3.9.1",
@@ -27,6 +28,7 @@
         "vue-chartjs": "^4.1.1",
         "vue-content-loader": "^2.0.1",
         "vue-draggable-list": "^0.1.1",
+        "vue-i18n": "^9.2.2",
         "vue-json-pretty": "^2.2.2",
         "vue-router": "^4.1.5",
         "vue-tippy": "^6.0.0-alpha.63"
@@ -589,6 +591,160 @@
       "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
       "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
     },
+    "node_modules/@intlify/bundle-utils": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-3.1.0.tgz",
+      "integrity": "sha512-ghlJ0kR2cCQ8D+poKknC0Xx0ncOt3J3os7CcIAqqIWVF7k6AtGoCDnIru+YzlZcvFRNmP9wEZ7jKliojCdAWNg==",
+      "dependencies": {
+        "@intlify/message-compiler": "next",
+        "@intlify/shared": "next",
+        "jsonc-eslint-parser": "^1.0.1",
+        "source-map": "0.6.1",
+        "yaml-eslint-parser": "^0.3.2"
+      },
+      "engines": {
+        "node": ">= 12"
+      },
+      "peerDependenciesMeta": {
+        "petite-vue-i18n": {
+          "optional": true
+        },
+        "vue-i18n": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@intlify/core-base": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
+      "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
+      "dependencies": {
+        "@intlify/devtools-if": "9.2.2",
+        "@intlify/message-compiler": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@intlify/core-base/node_modules/@intlify/message-compiler": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
+      "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
+      "dependencies": {
+        "@intlify/shared": "9.2.2",
+        "source-map": "0.6.1"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@intlify/core-base/node_modules/@intlify/shared": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+      "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@intlify/devtools-if": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
+      "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
+      "dependencies": {
+        "@intlify/shared": "9.2.2"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@intlify/devtools-if/node_modules/@intlify/shared": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+      "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@intlify/message-compiler": {
+      "version": "9.3.0-beta.3",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.3.0-beta.3.tgz",
+      "integrity": "sha512-j8OwToBQgs01RBMX4GCDNQfcnmw3AiDG3moKIONTrfXcf+1yt/rWznLTYH/DXbKcFMAFijFpCzMYjUmH1jVFYA==",
+      "dependencies": {
+        "@intlify/shared": "9.3.0-beta.3",
+        "source-map": "0.6.1"
+      },
+      "engines": {
+        "node": ">= 14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      }
+    },
+    "node_modules/@intlify/shared": {
+      "version": "9.3.0-beta.3",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.3.0-beta.3.tgz",
+      "integrity": "sha512-Z/0TU4GhFKRxKh+0RbwJExik9zz57gXYgxSYaPn7YQdkQ/pabSioCY/SXnYxQHL6HzULF5tmqarFm6glbGqKhw==",
+      "engines": {
+        "node": ">= 14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/kazupon"
+      }
+    },
+    "node_modules/@intlify/vite-plugin-vue-i18n": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/@intlify/vite-plugin-vue-i18n/-/vite-plugin-vue-i18n-6.0.1.tgz",
+      "integrity": "sha512-FFVcxVU4bR9vdDLNbltM5mrhndnXMErO01i0RrpdyMegEt3Nu/YLoH0sFdjRun7/RY4vaEnhTnFvVf9uO0dQvg==",
+      "dependencies": {
+        "@intlify/bundle-utils": "next",
+        "@intlify/shared": "next",
+        "@rollup/pluginutils": "^4.2.0",
+        "debug": "^4.3.1",
+        "fast-glob": "^3.2.5",
+        "source-map": "0.6.1"
+      },
+      "engines": {
+        "node": ">= 14.6"
+      },
+      "peerDependencies": {
+        "petite-vue-i18n": "*",
+        "vite": "^2.9.0 || ^3.0.0",
+        "vue-i18n": "*"
+      },
+      "peerDependenciesMeta": {
+        "petite-vue-i18n": {
+          "optional": true
+        },
+        "vite": {
+          "optional": true
+        },
+        "vue-i18n": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@intlify/vue-devtools": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
+      "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
+      "dependencies": {
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/@intlify/vue-devtools/node_modules/@intlify/shared": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+      "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/@jridgewell/gen-mapping": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
@@ -698,6 +854,18 @@
         "url": "https://opencollective.com/popperjs"
       }
     },
+    "node_modules/@rollup/pluginutils": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
+      "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
+      "dependencies": {
+        "estree-walker": "^2.0.1",
+        "picomatch": "^2.2.2"
+      },
+      "engines": {
+        "node": ">= 8.0.0"
+      }
+    },
     "node_modules/@types/json-schema": {
       "version": "7.0.11",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@@ -3227,6 +3395,67 @@
         "node": ">=6"
       }
     },
+    "node_modules/jsonc-eslint-parser": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-1.4.1.tgz",
+      "integrity": "sha512-hXBrvsR1rdjmB2kQmUjf1rEIa+TqHBGMge8pwi++C+Si1ad7EjZrJcpgwym+QGK/pqTx+K7keFAtLlVNdLRJOg==",
+      "dependencies": {
+        "acorn": "^7.4.1",
+        "eslint-utils": "^2.1.0",
+        "eslint-visitor-keys": "^1.3.0",
+        "espree": "^6.0.0",
+        "semver": "^6.3.0"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/jsonc-eslint-parser/node_modules/acorn": {
+      "version": "7.4.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+      "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/jsonc-eslint-parser/node_modules/eslint-utils": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+      "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+      "dependencies": {
+        "eslint-visitor-keys": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/mysticatea"
+      }
+    },
+    "node_modules/jsonc-eslint-parser/node_modules/eslint-visitor-keys": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+      "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/jsonc-eslint-parser/node_modules/espree": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
+      "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
+      "dependencies": {
+        "acorn": "^7.1.1",
+        "acorn-jsx": "^5.2.0",
+        "eslint-visitor-keys": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
     "node_modules/less": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
@@ -3304,8 +3533,7 @@
     "node_modules/lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-      "dev": true
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
     },
     "node_modules/lodash.merge": {
       "version": "4.6.2",
@@ -4578,6 +4806,31 @@
         "node": ">=10"
       }
     },
+    "node_modules/vue-i18n": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
+      "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
+      "dependencies": {
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2",
+        "@vue/devtools-api": "^6.2.1"
+      },
+      "engines": {
+        "node": ">= 14"
+      },
+      "peerDependencies": {
+        "vue": "^3.0.0"
+      }
+    },
+    "node_modules/vue-i18n/node_modules/@intlify/shared": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+      "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/vue-json-pretty": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/vue-json-pretty/-/vue-json-pretty-2.2.2.tgz",
@@ -4693,6 +4946,32 @@
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
       "dev": true
     },
+    "node_modules/yaml": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/yaml-eslint-parser": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-0.3.2.tgz",
+      "integrity": "sha512-32kYO6kJUuZzqte82t4M/gB6/+11WAuHiEnK7FreMo20xsCKPeFH5tDBU7iWxR7zeJpNnMXfJyXwne48D0hGrg==",
+      "dependencies": {
+        "eslint-visitor-keys": "^1.3.0",
+        "lodash": "^4.17.20",
+        "yaml": "^1.10.0"
+      }
+    },
+    "node_modules/yaml-eslint-parser/node_modules/eslint-visitor-keys": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+      "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/yocto-queue": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -5091,6 +5370,103 @@
       "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
       "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
     },
+    "@intlify/bundle-utils": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-3.1.0.tgz",
+      "integrity": "sha512-ghlJ0kR2cCQ8D+poKknC0Xx0ncOt3J3os7CcIAqqIWVF7k6AtGoCDnIru+YzlZcvFRNmP9wEZ7jKliojCdAWNg==",
+      "requires": {
+        "@intlify/message-compiler": "next",
+        "@intlify/shared": "next",
+        "jsonc-eslint-parser": "^1.0.1",
+        "source-map": "0.6.1",
+        "yaml-eslint-parser": "^0.3.2"
+      }
+    },
+    "@intlify/core-base": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
+      "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
+      "requires": {
+        "@intlify/devtools-if": "9.2.2",
+        "@intlify/message-compiler": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2"
+      },
+      "dependencies": {
+        "@intlify/message-compiler": {
+          "version": "9.2.2",
+          "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
+          "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
+          "requires": {
+            "@intlify/shared": "9.2.2",
+            "source-map": "0.6.1"
+          }
+        },
+        "@intlify/shared": {
+          "version": "9.2.2",
+          "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+          "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q=="
+        }
+      }
+    },
+    "@intlify/devtools-if": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
+      "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
+      "requires": {
+        "@intlify/shared": "9.2.2"
+      },
+      "dependencies": {
+        "@intlify/shared": {
+          "version": "9.2.2",
+          "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+          "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q=="
+        }
+      }
+    },
+    "@intlify/message-compiler": {
+      "version": "9.3.0-beta.3",
+      "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.3.0-beta.3.tgz",
+      "integrity": "sha512-j8OwToBQgs01RBMX4GCDNQfcnmw3AiDG3moKIONTrfXcf+1yt/rWznLTYH/DXbKcFMAFijFpCzMYjUmH1jVFYA==",
+      "requires": {
+        "@intlify/shared": "9.3.0-beta.3",
+        "source-map": "0.6.1"
+      }
+    },
+    "@intlify/shared": {
+      "version": "9.3.0-beta.3",
+      "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.3.0-beta.3.tgz",
+      "integrity": "sha512-Z/0TU4GhFKRxKh+0RbwJExik9zz57gXYgxSYaPn7YQdkQ/pabSioCY/SXnYxQHL6HzULF5tmqarFm6glbGqKhw=="
+    },
+    "@intlify/vite-plugin-vue-i18n": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/@intlify/vite-plugin-vue-i18n/-/vite-plugin-vue-i18n-6.0.1.tgz",
+      "integrity": "sha512-FFVcxVU4bR9vdDLNbltM5mrhndnXMErO01i0RrpdyMegEt3Nu/YLoH0sFdjRun7/RY4vaEnhTnFvVf9uO0dQvg==",
+      "requires": {
+        "@intlify/bundle-utils": "next",
+        "@intlify/shared": "next",
+        "@rollup/pluginutils": "^4.2.0",
+        "debug": "^4.3.1",
+        "fast-glob": "^3.2.5",
+        "source-map": "0.6.1"
+      }
+    },
+    "@intlify/vue-devtools": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
+      "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
+      "requires": {
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2"
+      },
+      "dependencies": {
+        "@intlify/shared": {
+          "version": "9.2.2",
+          "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+          "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q=="
+        }
+      }
+    },
     "@jridgewell/gen-mapping": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
@@ -5177,6 +5553,15 @@
       "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
       "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw=="
     },
+    "@rollup/pluginutils": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
+      "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
+      "requires": {
+        "estree-walker": "^2.0.1",
+        "picomatch": "^2.2.2"
+      }
+    },
     "@types/json-schema": {
       "version": "7.0.11",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@@ -6897,6 +7282,48 @@
       "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
       "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
     },
+    "jsonc-eslint-parser": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-1.4.1.tgz",
+      "integrity": "sha512-hXBrvsR1rdjmB2kQmUjf1rEIa+TqHBGMge8pwi++C+Si1ad7EjZrJcpgwym+QGK/pqTx+K7keFAtLlVNdLRJOg==",
+      "requires": {
+        "acorn": "^7.4.1",
+        "eslint-utils": "^2.1.0",
+        "eslint-visitor-keys": "^1.3.0",
+        "espree": "^6.0.0",
+        "semver": "^6.3.0"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "7.4.1",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+          "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
+        },
+        "eslint-utils": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+          "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+          "requires": {
+            "eslint-visitor-keys": "^1.1.0"
+          }
+        },
+        "eslint-visitor-keys": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+          "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ=="
+        },
+        "espree": {
+          "version": "6.2.1",
+          "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
+          "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
+          "requires": {
+            "acorn": "^7.1.1",
+            "acorn-jsx": "^5.2.0",
+            "eslint-visitor-keys": "^1.1.0"
+          }
+        }
+      }
+    },
     "less": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
@@ -6956,8 +7383,7 @@
     "lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
-      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-      "dev": true
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
     },
     "lodash.merge": {
       "version": "4.6.2",
@@ -7842,6 +8268,24 @@
         }
       }
     },
+    "vue-i18n": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
+      "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
+      "requires": {
+        "@intlify/core-base": "9.2.2",
+        "@intlify/shared": "9.2.2",
+        "@intlify/vue-devtools": "9.2.2",
+        "@vue/devtools-api": "^6.2.1"
+      },
+      "dependencies": {
+        "@intlify/shared": {
+          "version": "9.2.2",
+          "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
+          "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q=="
+        }
+      }
+    },
     "vue-json-pretty": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/vue-json-pretty/-/vue-json-pretty-2.2.2.tgz",
@@ -7921,6 +8365,28 @@
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
       "dev": true
     },
+    "yaml": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
+    },
+    "yaml-eslint-parser": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-0.3.2.tgz",
+      "integrity": "sha512-32kYO6kJUuZzqte82t4M/gB6/+11WAuHiEnK7FreMo20xsCKPeFH5tDBU7iWxR7zeJpNnMXfJyXwne48D0hGrg==",
+      "requires": {
+        "eslint-visitor-keys": "^1.3.0",
+        "lodash": "^4.17.20",
+        "yaml": "^1.10.0"
+      },
+      "dependencies": {
+        "eslint-visitor-keys": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+          "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ=="
+        }
+      }
+    },
     "yocto-queue": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

+ 2 - 0
frontend/package.json

@@ -32,6 +32,7 @@
     "vue-tsc": "^0.39.5"
   },
   "dependencies": {
+    "@intlify/vite-plugin-vue-i18n": "^6.0.1",
     "@vitejs/plugin-vue": "^3.0.3",
     "can-autoplay": "^3.0.2",
     "chart.js": "^3.9.1",
@@ -50,6 +51,7 @@
     "vue-chartjs": "^4.1.1",
     "vue-content-loader": "^2.0.1",
     "vue-draggable-list": "^0.1.1",
+    "vue-i18n": "^9.2.2",
     "vue-json-pretty": "^2.2.2",
     "vue-router": "^4.1.5",
     "vue-tippy": "^6.0.0-alpha.63"

+ 9 - 0
frontend/src/i18n.ts

@@ -0,0 +1,9 @@
+import { createI18n } from "vue-i18n";
+import messages from "@intlify/vite-plugin-vue-i18n/messages";
+
+export default createI18n({
+	legacy: false,
+	locale: "en-GB",
+	fallbackLocale: "en-GB",
+	messages
+});

+ 9 - 0
frontend/src/locales/en-GB.json

@@ -0,0 +1,9 @@
+{
+    "login": "Login",
+    "register": "Register",
+    "station": "Station | Stations",
+    "manageStation": "Manage @:station",
+    "favoriteStations": "My Favourites",
+    "noStations": "There are no @.lower:station to display",
+    "home": "Home"
+}

+ 3 - 0
frontend/src/locales/en-US.json

@@ -0,0 +1,3 @@
+{
+    "favoriteStations": "My Favorites"
+}

+ 9 - 0
frontend/src/locales/template.json

@@ -0,0 +1,9 @@
+{
+    "login": "",
+    "register": "",
+    "station": "",
+    "manageStation": "",
+    "favoriteStations": "",
+    "noStations": "",
+    "home": ""
+}

+ 3 - 0
frontend/src/main.ts

@@ -12,6 +12,7 @@ import { useUserPreferencesStore } from "@/stores/userPreferences";
 import { useModalsStore } from "@/stores/modals";
 import ws from "@/ws";
 import ms from "@/ms";
+import i18n from "@/i18n";
 
 import AppComponent from "./App.vue";
 
@@ -36,6 +37,8 @@ const handleMetadata = attrs => {
 
 const app = createApp(AppComponent);
 
+app.use(i18n);
+
 app.use(VueTippy, {
 	directive: "tippy", // => v-tippy
 	flipDuration: 0,

+ 11 - 8
frontend/src/pages/Home.vue

@@ -11,6 +11,7 @@ import {
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { DraggableList } from "vue-draggable-list";
+import { useI18n } from "vue-i18n";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useModalsStore } from "@/stores/modals";
@@ -30,6 +31,8 @@ const UserLink = defineAsyncComponent(
 	() => import("@/components/UserLink.vue")
 );
 
+const { t } = useI18n();
+
 const userAuthStore = useUserAuthStore();
 const route = useRoute();
 const router = useRouter();
@@ -368,7 +371,7 @@ onBeforeUnmount(() => {
 
 <template>
 	<div>
-		<page-metadata title="Home" />
+		<page-metadata :title="t('home')" />
 		<div class="app home-page">
 			<main-header
 				:hide-logo="true"
@@ -394,14 +397,14 @@ onBeforeUnmount(() => {
 								class="button login"
 								@click="openModal('login')"
 							>
-								Login
+								{{ t("login") }}
 							</button>
 							<button
 								v-if="!siteSettings.registrationDisabled"
 								class="button register"
 								@click="openModal('register')"
 							>
-								Register
+								{{ t("register") }}
 							</button>
 						</div>
 					</div>
@@ -410,7 +413,7 @@ onBeforeUnmount(() => {
 			<div class="group" v-show="favoriteStations.length > 0">
 				<div class="group-title">
 					<div>
-						<h2>My Favorites</h2>
+						<h2>{{ t("favoriteStations") }}</h2>
 					</div>
 				</div>
 
@@ -458,7 +461,7 @@ onBeforeUnmount(() => {
 														}
 													})
 												"
-												content="Manage Station"
+												:content="t('manageStation')"
 												v-tippy
 											>
 												settings
@@ -654,7 +657,7 @@ onBeforeUnmount(() => {
 			<div class="group bottom">
 				<div class="group-title">
 					<div>
-						<h1>Stations</h1>
+						<h1>{{ t("station", 0) }}</h1>
 					</div>
 				</div>
 				<a
@@ -738,7 +741,7 @@ onBeforeUnmount(() => {
 												}
 											})
 										"
-										content="Manage Station"
+										:content="t('manageStation')"
 										v-tippy
 									>
 										settings
@@ -906,7 +909,7 @@ onBeforeUnmount(() => {
 					</div>
 				</router-link>
 				<h4 v-if="stations.length === 0">
-					There are no stations to display
+					{{ t("noStations", 0) }}
 				</h4>
 			</div>
 			<main-footer />

+ 1 - 1
frontend/tsconfig.json

@@ -13,7 +13,7 @@
       ]
     },
     "jsx": "preserve",
-    "types": ["vite/client"]
+    "types": ["vite/client", "@intlify/vite-plugin-vue-i18n/client"]
   },
   "exclude": ["./src/index.html"]
 }

+ 9 - 2
frontend/vite.config.js

@@ -1,6 +1,7 @@
 import path from "path";
 import vue from "@vitejs/plugin-vue";
 import dynamicImport from "vite-plugin-dynamic-import";
+import vueI18n from "@intlify/vite-plugin-vue-i18n";
 import config from "config";
 import fs from "fs";
 
@@ -157,9 +158,15 @@ export default {
 		MUSARE_GIT_LATEST_COMMIT: JSON.stringify(debug.git.latestCommit),
 		MUSARE_GIT_LATEST_COMMIT_SHORT: JSON.stringify(
 			debug.git.latestCommitShort
-		)
+		),
+		__VUE_I18N_LEGACY_API__: false
 	},
-	plugins: [vue(), htmlPlugin(), dynamicImport()],
+	plugins: [
+		vue(),
+		htmlPlugin(),
+		dynamicImport(),
+		vueI18n({ include: path.resolve(__dirname, "src/locales/**") })
+	],
 	css: {
 		preprocessorOptions: {
 			less: {