Browse Source

add vue-i18n

SyuanYu 2 years ago
parent
commit
6986d2a02c

+ 75 - 0
frontend/package-lock.json

@@ -12,6 +12,7 @@
         "pinia": "^2.0.28",
         "sass": "^1.57.1",
         "vue": "^3.2.45",
+        "vue-i18n": "^9.2.2",
         "vue-router": "^4.1.6",
         "vuetify": "^3.1.1"
       },
@@ -919,6 +920,63 @@
       "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
       "dev": 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/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/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/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/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/@jridgewell/gen-mapping": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
@@ -5571,6 +5629,23 @@
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
       "dev": true
     },
+    "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-router": {
       "version": "4.1.6",
       "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz",

+ 1 - 0
frontend/package.json

@@ -16,6 +16,7 @@
     "pinia": "^2.0.28",
     "sass": "^1.57.1",
     "vue": "^3.2.45",
+    "vue-i18n": "^9.2.2",
     "vue-router": "^4.1.6",
     "vuetify": "^3.1.1"
   },

+ 21 - 9
frontend/src/components/Navbar.vue

@@ -1,12 +1,23 @@
 <script setup lang="ts">
-import { reactive, ref, onMounted } from "vue";
-let items = reactive([{ title: "English" }, { title: "中文" }]);
+import { reactive } from "vue";
+import { useI18n } from "vue-i18n";
+
+const { t, locale } = useI18n();
+
+let items = reactive([
+  { title: "English", text: "en" },
+  { title: "中文", text: "zh" },
+]);
 
 let menu = reactive([
-  // { title: '首頁', link: '/' },
-  { title: "登入", link: "/login" },
-  { title: "註冊", link: "/signup" },
+  { title: "login", link: "/login" },
+  { title: "register", link: "/signup" },
 ]);
+
+function setLang(lang: String) {
+  locale.value = `${lang}`;
+  localStorage.setItem("lang", `${lang}`);
+}
 </script>
 
 <template>
@@ -21,9 +32,9 @@ let menu = reactive([
     <v-spacer></v-spacer>
 
     <v-toolbar-items>
-      <v-btn v-for="item in menu" :to="item.link" color="gray">{{
-        item.title
-      }}</v-btn>
+      <v-btn v-for="item in menu" :to="item.link" color="gray">
+        {{ t(`${item.title}`) }}</v-btn
+      >
       <v-menu>
         <template v-slot:activator="{ props }">
           <v-btn color="gray" v-bind="props">
@@ -47,7 +58,8 @@ let menu = reactive([
           <v-list-item
             v-for="(item, index) in items"
             :key="index"
-            :value="index"
+            :value="item.text"
+            @click="setLang(item.text)"
           >
             <v-list-item-title>{{ item.title }}</v-list-item-title>
           </v-list-item>

+ 22 - 0
frontend/src/language/en.json

@@ -0,0 +1,22 @@
+{
+    "login" : "Login",
+    "loginLink" : "Login",
+    "userProfile": "User Profile",
+    "logout": "Logout",
+    "userName": "User name",
+    "password": "Password",
+    "emailAddress": "Email Address",
+    "registerPassword": "Password",
+    "confirmPassword": "Confirm Password",
+    "passwordLength": "(4-12 Characters long)",
+    "passwordConfirm": "(Type your password again)",
+    "haveAccount": "Have an account?",
+    "haventAccount": "Don't have an account?",
+    "register": "Register",
+    "registerLink": "Register",
+    "privacy_term_1": "Registeration implies acception of \n \n \n",
+    "privacy_term_2": "terms of service and privacy policy",
+    "forgotPsd": "Forgot Password",
+    "describe_1": "Make your first video for promotion, creation and life today",
+    "describe_2": "Let's get started with AI Presentors"
+}

+ 22 - 0
frontend/src/language/zh.json

@@ -0,0 +1,22 @@
+{
+    "login" : "登入",
+    "loginLink" : "立即登入",
+    "userProfile": "會員資料",
+    "logout": "登出",
+    "userName": "使用者名稱",
+    "password": "密碼",
+    "emailAddress": "電子信箱",
+    "registerPassword": "設定密碼",
+    "confirmPassword": "確認密碼",
+    "passwordLength": "(4-12 位數密碼)",
+    "passwordConfirm": "(再次輸入您的密碼)",
+    "haveAccount": "已經有帳號?",
+    "haventAccount": "還沒有帳號?",
+    "register": "註冊",
+    "registerLink": "立即註冊",
+    "privacy_term_1": "註冊即表示您已閱讀並同意",
+    "privacy_term_2": "服務條款及隱私權政策",
+    "forgotPsd": "忘記密碼",
+    "describe_1": "將您的生活、創作、宣傳做成影片",
+    "describe_2": "開始使用 AI Presentors"
+}

+ 15 - 0
frontend/src/main.ts

@@ -3,11 +3,26 @@ import router from "./router";
 import App from "./App.vue";
 import { vuetify } from "./plugins/vuetify";
 import { pinia } from "./plugins/pinia";
+import { createI18n } from "vue-i18n";
+import zh from "./language/zh.json";
+import en from "./language/en.json";
+
+const i18n = createI18n({
+  legacy: false,
+  locale: localStorage.getItem("lang") ?? "zh",
+  fallbackLocale: "zh",
+  globalInjection: true,
+  messages: {
+    "zh": zh,
+    "en": en,
+  }
+});
 
 const app = createApp(App);
 
 app.use(pinia);
 app.use(router);
 app.use(vuetify);
+app.use(i18n);
 
 app.mount("#app");

+ 13 - 11
frontend/src/views/Login.vue

@@ -4,6 +4,7 @@ import { ref, computed, onMounted } from "vue";
 import { useMainStore } from "@/stores/main";
 import { useDisplay } from "vuetify";
 import { storeToRefs } from "pinia";
+import { useI18n } from "vue-i18n";
 import Navbar from "@/components/Navbar.vue";
 
 const mainStore = useMainStore();
@@ -13,6 +14,7 @@ const mainStoreRef = storeToRefs(mainStore);
 const email = ref("");
 const password = ref("");
 const { name } = useDisplay();
+const { t } = useI18n();
 let showPassword = ref(false);
 
 // getter
@@ -48,15 +50,15 @@ onMounted(() => {});
         <section class="overflow-hidden banner-item">
           <img src="../assets/img/banner.png" alt="" />
           <h2>
-            將您的生活、創作、宣傳做成影片
+            {{ t("describe_1") }}
             <br />
-            開始使用 AI Presentors
+            {{ t("describe_2") }}
           </h2>
         </section>
       </v-col>
       <v-col :cols="width" class="px-6 my-8 my-md-0">
         <div class="form-title">
-          <h3>登入</h3>
+          <h3>{{ t("login") }}</h3>
           <span></span>
         </div>
         <v-form ref="form" class="login-form" lazy-validation>
@@ -65,7 +67,7 @@ onMounted(() => {});
             name="email"
             prepend-icon="person"
             :rules="[(v) => !!v || '請輸入您的帳號']"
-            label="使用者名稱"
+            :label="$t('emailAddress')"
             required
           ></v-text-field>
 
@@ -77,18 +79,18 @@ onMounted(() => {});
             :append-icon="showPassword ? 'visibility' : 'visibility_off'"
             :rules="[(v) => !!v || '請輸入您的密碼']"
             :type="showPassword ? 'text' : 'password'"
-            label="密碼"
+            :label="$t('password')"
             hint="4-12 位數密碼"
             @click:append="showPassword = !showPassword"
             required
           ></v-text-field>
 
           <p class="text-center">
-            還沒有帳號?
-            <router-link to="/signup">註冊</router-link>/<router-link
-              to="/recover-password"
-              >忘記密碼</router-link
-            >
+            {{ t("haventAccount") }}
+            <router-link to="/signup">{{ t("register") }}</router-link
+            >/<router-link to="/recover-password">{{
+              t("forgotPsd")
+            }}</router-link>
           </p>
 
           <v-btn
@@ -97,7 +99,7 @@ onMounted(() => {});
             @click.prevent="submit"
             class="login-btn"
           >
-            立即登入
+            {{ t("loginLink") }}
           </v-btn>
         </v-form>
       </v-col>

+ 69 - 64
frontend/src/views/Signup.vue

@@ -4,6 +4,7 @@ import { ref, reactive, computed, onMounted } from "vue";
 import { useMainStore } from "@/stores/main";
 import { useDisplay } from "vuetify";
 import { storeToRefs } from "pinia";
+import { useI18n } from "vue-i18n";
 import Navbar from "@/components/Navbar.vue";
 
 const mainStore = useMainStore();
@@ -13,6 +14,7 @@ const mainStoreRef = storeToRefs(mainStore);
 const email = ref("");
 const password = ref("");
 const { name } = useDisplay();
+const { t } = useI18n();
 const confirmPassword = ref("");
 let data = reactive({
   email: "",
@@ -59,15 +61,15 @@ async function submit() {
         <section class="overflow-hidden banner-item">
           <img src="../assets/img/banner.png" alt="" />
           <h2>
-            將您的生活、創作、宣傳做成影片
+            {{ t("describe_1") }}
             <br />
-            開始使用 AI Presentors
+            {{ t("describe_2") }}
           </h2>
         </section>
       </v-col>
       <v-col :cols="width" class="px-6 my-8 my-md-0">
         <div class="form-title">
-          <h3>註冊</h3>
+          <h3>{{ t("register") }}</h3>
           <span></span>
         </div>
         <v-form ref="form" class="login-form" lazy-validation>
@@ -81,7 +83,7 @@ async function submit() {
           <v-text-field
             v-model="data.email"
             :rules="[(v) => !!v || '請輸入您的電子信箱']"
-            label="電子信箱"
+            :label="$t('emailAddress')"
             required
           ></v-text-field>
 
@@ -90,8 +92,8 @@ async function submit() {
             :append-icon="showPassword ? 'visibility' : 'visibility_off'"
             :rules="[(v) => !!v || '請輸入您的密碼']"
             :type="showPassword ? 'text' : 'password'"
-            label="設定密碼"
-            hint="4-12 位數密碼"
+            :label="$t('registerPassword')"
+            :hint="$t('passwordLength')"
             @click:append="showPassword = !showPassword"
             required
           ></v-text-field>
@@ -101,8 +103,8 @@ async function submit() {
             :append-icon="showConfirmPassword ? 'visibility' : 'visibility_off'"
             :rules="[(v) => !!v || '請輸入您的密碼']"
             :type="showConfirmPassword ? 'text' : 'password'"
-            label="確認密碼"
-            hint="再次輸入您的密碼"
+            :label="$t('confirmPassword')"
+            :hint="$t('passwordConfirm')"
             @click:append="showConfirmPassword = !showConfirmPassword"
             required
           ></v-text-field>
@@ -112,7 +114,8 @@ async function submit() {
           </v-alert>
 
           <p class="text-center">
-            已經有帳號? <router-link to="/login">登入</router-link>
+            {{ t("haveAccount") }}
+            <router-link to="/login"> {{ t("login") }}</router-link>
           </p>
 
           <v-btn
@@ -121,65 +124,67 @@ async function submit() {
             @click.prevent="submit"
             class="login-btn"
           >
-            立即註冊
+            {{ t("registerLink") }}
           </v-btn>
 
           <section
             class="mt-5 d-flex align-center justify-center dialog-content"
           >
-            <p>註冊即表示您已閱讀並同意</p>
-            <v-dialog v-model="dialog" max-width="700" scrollable>
-              <template v-slot:activator="{ props }">
-                <v-btn
-                  variant="text"
-                  color="primary"
-                  v-bind="props"
-                  class="px-1"
-                >
-                  服務條款及隱私權政策
-                </v-btn>
-              </template>
-
-              <v-card class="terms-card">
-                <v-card-title>
-                  <v-spacer></v-spacer>
-                  <v-btn icon @click="dialog = false">
-                    <v-icon icon="md:close"></v-icon>
-                  </v-btn>
-                </v-card-title>
-                <v-card-text>
-                  <h3 class="text-h5 text-center font-weight-bold mb-5">
-                    使用者的守法義務
-                  </h3>
-                  您承諾絕不為任何非法目的或以任何非法方式使用本服務,並承諾遵守中華民國相關法規及一切使用網際網路之國際慣例。您若係中華民國以外之使用者,並同意遵守所屬國家或地域之法令。您同意並保證不得利用本服務從事侵害他人權益或違法之行為,包括但不限於:
-                  <ul class="mb-3">
-                    <li>
-                      上載、張貼、公布或傳送任何誹謗、侮辱、具威脅性、攻擊性、不雅、猥褻、不實、違反公共秩序或善良風俗或其他不法之文字、圖片或任何形式的檔案於本服務上
-                    </li>
-                    <li>
-                      侵害他人名譽、隱私權、營業秘密、商標權、著作權、專利權、其他智慧財產權及其他權利
-                    </li>
-                    <li>違反依法律或契約所應負之保密義務</li>
-                    <li>冒用他人名義使用本服務</li>
-                  </ul>
-                  <v-divider></v-divider>
-                  <h3 class="text-h5 text-center font-weight-bold mt-7 mb-5">
-                    免責聲明
-                  </h3>
-                  您明確了解並同意:ChoozMo
-                  對本服務不提供任何明示或默示的擔保,包含但不限於權利完整、商業適售性、特定目的之適用性及未侵害他人權利。本服務乃依其「現狀」及「提供使用時」之基礎提供,您使用本服務時,須自行承擔相關風險。ChoozMo
-                  不保證以下事項:
-                  <ul>
-                    <li>本服務將符合您的需求</li>
-                    <li>本服務不受干擾、及時提供、安全可靠或無錯誤</li>
-                    <li>由本服務之使用而取得之結果為正確或可靠</li>
-                  </ul>
-
-                  是否經由本服務之使用下載或取得任何資料應由您自行考量且自負風險,並拋棄因前開任何資料之下載而導致您電腦系統、網路存取、下載或播放設備之任何損壞或資料流失,對
-                  ChoozMo 提出任何請求或採取法律行動,您應自負完全責任。
-                </v-card-text>
-              </v-card>
-            </v-dialog>
+            <p>
+              {{ t("privacy_term_1") }}
+              <v-dialog v-model="dialog" max-width="700" scrollable>
+                <template v-slot:activator="{ props }">
+                  <a
+                    href="javascript:;"
+                    color="primary"
+                    v-bind="props"
+                    class="ms-1"
+                  >
+                    {{ t("privacy_term_2") }}
+                  </a>
+                </template>
+
+                <v-card class="terms-card">
+                  <v-card-title>
+                    <v-spacer></v-spacer>
+                    <v-btn icon @click="dialog = false">
+                      <v-icon icon="md:close"></v-icon>
+                    </v-btn>
+                  </v-card-title>
+                  <v-card-text>
+                    <h3 class="text-h5 text-center font-weight-bold mb-5">
+                      使用者的守法義務
+                    </h3>
+                    您承諾絕不為任何非法目的或以任何非法方式使用本服務,並承諾遵守中華民國相關法規及一切使用網際網路之國際慣例。您若係中華民國以外之使用者,並同意遵守所屬國家或地域之法令。您同意並保證不得利用本服務從事侵害他人權益或違法之行為,包括但不限於:
+                    <ul class="mb-3">
+                      <li>
+                        上載、張貼、公布或傳送任何誹謗、侮辱、具威脅性、攻擊性、不雅、猥褻、不實、違反公共秩序或善良風俗或其他不法之文字、圖片或任何形式的檔案於本服務上
+                      </li>
+                      <li>
+                        侵害他人名譽、隱私權、營業秘密、商標權、著作權、專利權、其他智慧財產權及其他權利
+                      </li>
+                      <li>違反依法律或契約所應負之保密義務</li>
+                      <li>冒用他人名義使用本服務</li>
+                    </ul>
+                    <v-divider></v-divider>
+                    <h3 class="text-h5 text-center font-weight-bold mt-7 mb-5">
+                      免責聲明
+                    </h3>
+                    您明確了解並同意:ChoozMo
+                    對本服務不提供任何明示或默示的擔保,包含但不限於權利完整、商業適售性、特定目的之適用性及未侵害他人權利。本服務乃依其「現狀」及「提供使用時」之基礎提供,您使用本服務時,須自行承擔相關風險。ChoozMo
+                    不保證以下事項:
+                    <ul>
+                      <li>本服務將符合您的需求</li>
+                      <li>本服務不受干擾、及時提供、安全可靠或無錯誤</li>
+                      <li>由本服務之使用而取得之結果為正確或可靠</li>
+                    </ul>
+
+                    是否經由本服務之使用下載或取得任何資料應由您自行考量且自負風險,並拋棄因前開任何資料之下載而導致您電腦系統、網路存取、下載或播放設備之任何損壞或資料流失,對
+                    ChoozMo 提出任何請求或採取法律行動,您應自負完全責任。
+                  </v-card-text>
+                </v-card>
+              </v-dialog>
+            </p>
           </section>
         </v-form>
       </v-col>
@@ -192,7 +197,7 @@ async function submit() {
   display: flex;
   align-items: center;
   justify-content: center;
-  font-size: 14px;
+  font-size: 13px;
   letter-spacing: 1px;
   .v-btn {
     &:hover > .v-btn__overlay {

+ 1 - 1
frontend/tsconfig.app.json

@@ -1,6 +1,6 @@
 {
   "extends": "@vue/tsconfig/tsconfig.web.json",
-  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+  "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/**/*.json"],
   "exclude": ["src/**/__tests__/*"],
   "compilerOptions": {
     "composite": true,