Browse Source

feat: 国际化

shuisheng 1 năm trước cách đây
mục cha
commit
6a434f2c95

+ 1 - 0
package.json

@@ -53,6 +53,7 @@
     "video.js": "^8.3.0",
     "vue": "^3.3.4",
     "vue-demi": "^0.13.11",
+    "vue-i18n": "9",
     "vue-router": "^4.0.13",
     "webrtc-adapter": "^8.2.2",
     "worker-timers": "^7.0.74"

+ 36 - 0
pnpm-lock.yaml

@@ -68,6 +68,9 @@ dependencies:
   vue-demi:
     specifier: ^0.13.11
     version: 0.13.11(vue@3.3.4)
+  vue-i18n:
+    specifier: '9'
+    version: 9.9.1(vue@3.3.4)
   vue-router:
     specifier: ^4.0.13
     version: 4.1.6(vue@3.3.4)
@@ -2253,6 +2256,27 @@ packages:
     engines: {node: '>=6.9.0'}
     dev: true
 
+  /@intlify/core-base@9.9.1:
+    resolution: {integrity: sha512-qsV15dg7jNX2faBRyKMgZS8UcFJViWEUPLdzZ9UR0kQZpFVeIpc0AG7ZOfeP7pX2T9SQ5jSiorq/tii9nkkafA==}
+    engines: {node: '>= 16'}
+    dependencies:
+      '@intlify/message-compiler': 9.9.1
+      '@intlify/shared': 9.9.1
+    dev: false
+
+  /@intlify/message-compiler@9.9.1:
+    resolution: {integrity: sha512-zTvP6X6HeumHOXuAE1CMMsV6tTX+opKMOxO1OHTCg5N5Sm/F7d8o2jdT6W6L5oHUsJ/vvkGefHIs7Q3hfowmsA==}
+    engines: {node: '>= 16'}
+    dependencies:
+      '@intlify/shared': 9.9.1
+      source-map-js: 1.0.2
+    dev: false
+
+  /@intlify/shared@9.9.1:
+    resolution: {integrity: sha512-b3Pta1nwkz5rGq434v0psHwEwHGy1pYCttfcM22IE//K9owbpkEvFptx9VcuRAxjQdrO2If249cmDDjBu5wMDA==}
+    engines: {node: '>= 16'}
+    dev: false
+
   /@jridgewell/gen-mapping@0.1.1:
     resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
     engines: {node: '>=6.0.0'}
@@ -10970,6 +10994,18 @@ packages:
       - supports-color
     dev: true
 
+  /vue-i18n@9.9.1(vue@3.3.4):
+    resolution: {integrity: sha512-xyQ4VspLdNSPTKBFBPWa1tvtj+9HuockZwgFeD2OhxxXuC2CWeNvV4seu2o9+vbQOyQbhAM5Ez56oxUrrnTWdw==}
+    engines: {node: '>= 16'}
+    peerDependencies:
+      vue: ^3.0.0
+    dependencies:
+      '@intlify/core-base': 9.9.1
+      '@intlify/shared': 9.9.1
+      '@vue/devtools-api': 6.5.0
+      vue: 3.3.4
+    dev: false
+
   /vue-loader@17.2.2(@vue/compiler-sfc@3.3.4)(vue@3.3.4)(webpack@5.76.2):
     resolution: {integrity: sha512-aqNvKJvnz2A/6VWeJZodAo8XLoAlVwBv+2Z6dama+LHsAF+P/xijQ+OfWrxIs0wcGSJduvdzvTuATzXbNKkpiw==}
     peerDependencies:

+ 11 - 0
public/worker.js

@@ -0,0 +1,11 @@
+self.addEventListener('message', function (e) {
+  if (e.data.type === 'start-loop') {
+    setInterval(function () {
+      self.postMessage({
+        type: 'workder-msg',
+        time: new Date().toLocaleString(),
+        data: e.data,
+      });
+    }, e.data.delay);
+  }
+});

+ 18 - 6
src/components/QrPay/index.vue

@@ -1,6 +1,8 @@
 <template>
   <div class="qr-pay-wrap">
-    <div class="money">金额:{{ formatMoney(props.money * 100) }}元</div>
+    <div class="money">
+      {{ t('common.payMoney', { money: formatMoney(props.money) }) }}
+    </div>
     <div
       class="qrcode-wrap"
       v-loading="loading"
@@ -30,13 +32,22 @@
     </div>
     <div v-if="aliPayBase64 !== ''">
       <div class="bottom">
-        <div class="sao">打开支付宝扫一扫</div>
+        <div class="sao">
+          {{ t('common.aliPayScanTip') }}
+        </div>
         <div
           class="expr"
           v-if="!isExpired"
         >
-          有效期5分钟({{
-            formatDownTime({ endTime: downTimeEnd, startTime: downTimeStart })
+          {{
+            t('common.aliPayScanTip', {
+              minutes: 5,
+            })
+          }}({{
+            formatDownTime({
+              endTime: downTimeEnd,
+              startTime: downTimeStart,
+            })
           }})
         </div>
         <div
@@ -66,6 +77,7 @@
 import { hrefToTarget, isMobile } from 'billd-utils';
 import QRCode from 'qrcode';
 import { onMounted, onUnmounted, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
 
 import { fetchAliPay, fetchAliPayStatus } from '@/api/order';
 import { PayStatusEnum } from '@/interface';
@@ -82,7 +94,7 @@ const loading = ref(false);
 const isExpired = ref(false);
 
 const currentPayStatus = ref(PayStatusEnum.wait);
-
+const { t } = useI18n();
 const props = withDefaults(
   defineProps<{
     money: number;
@@ -140,7 +152,7 @@ async function handleStartPay() {
   clearInterval(downTimer.value);
   try {
     const res = await fetchAliPay({
-      money: props.money * 100,
+      money: props.money,
       goodsId: props.goodsId,
       liveRoomId: props.liveRoomId,
     });

+ 10 - 0
src/hooks/use-i18n.ts

@@ -0,0 +1,10 @@
+import { createI18n } from 'vue-i18n';
+
+import { messages } from '@/locales';
+
+export const i18n = createI18n({
+  legacy: false,
+  locale: 'zh', // set locale
+  fallbackLocale: 'zh', // set fallback locale
+  messages, // set locale messages
+});

+ 98 - 32
src/layout/pc/head/index.vue

@@ -18,7 +18,7 @@
             href="/"
             @click.prevent="router.push('/')"
           >
-            首页
+            {{ t('layout.home') }}
           </a>
           <a
             class="item"
@@ -27,7 +27,7 @@
             }"
             @click.prevent="router.push({ name: routerName.area })"
           >
-            分区
+            {{ t('layout.area') }}
           </a>
           <a
             class="item"
@@ -36,21 +36,21 @@
             }"
             @click.prevent="router.push({ name: routerName.shop })"
           >
-            商店
+            {{ t('layout.shop') }}
           </a>
           <a
             class="item"
             :href="COMMON_URL.admin"
             @click.prevent="openToTarget(COMMON_URL.admin)"
           >
-            直播后台
+            {{ t('layout.liveAdmin') }}
           </a>
           <a
             class="item"
             :href="COMMON_URL.mobileApk"
             @click.prevent="openToTarget(COMMON_URL.mobileApk)"
           >
-            App下载
+            {{ t('layout.appdownload') }}
             <div class="badge">
               <div class="txt">new</div>
             </div>
@@ -71,7 +71,8 @@
         <Dropdown class="doc">
           <template #btn>
             <div class="btn">
-              文档<VPIconChevronDown class="icon"></VPIconChevronDown>
+              <span>{{ t('layout.doc') }}</span>
+              <VPIconChevronDown class="icon"></VPIconChevronDown>
             </div>
           </template>
           <template #list>
@@ -80,14 +81,14 @@
                 class="item"
                 @click="quickStart"
               >
-                <div class="txt">快速上手</div>
+                <div class="txt">{{ t('layout.guide') }}</div>
               </a>
               <a
                 class="item"
                 :href="COMMON_URL.apifox"
                 @click.prevent="openToTarget(COMMON_URL.apifox)"
               >
-                <div class="txt">接口文档</div>
+                <div class="txt">{{ t('layout.apiDoc') }}</div>
                 <VPIconExternalLink class="icon"></VPIconExternalLink>
               </a>
               <a
@@ -97,7 +98,7 @@
                   openToTarget(COMMON_URL.bilibiliCollectiondetail)
                 "
               >
-                <div class="txt">b站教程</div>
+                <div class="txt">{{ t('layout.bilibiliTutorial') }}</div>
                 <VPIconExternalLink class="icon"></VPIconExternalLink>
               </a>
               <a
@@ -105,7 +106,7 @@
                 :href="COMMON_URL.payCoursesArticle"
                 @click.prevent="openToTarget(COMMON_URL.payCoursesArticle)"
               >
-                <div class="txt">billd-live付费课</div>
+                <div class="txt">{{ t('layout.vipCourses') }}</div>
                 <VPIconExternalLink class="icon"></VPIconExternalLink>
               </a>
             </div>
@@ -115,12 +116,13 @@
         <Dropdown class="ecosystem">
           <template #btn>
             <div class="btn">
-              生态系统<VPIconChevronDown class="icon"></VPIconChevronDown>
+              <span>{{ t('layout.ecosystem') }}</span>
+              <VPIconChevronDown class="icon"></VPIconChevronDown>
             </div>
           </template>
           <template #list>
             <div class="list">
-              <div class="title">资源</div>
+              <div class="title">{{ t('layout.resource') }}</div>
               <a
                 v-for="(item, index) in resource"
                 :key="index"
@@ -135,7 +137,7 @@
                 ></VPIconExternalLink>
               </a>
               <div class="hr"></div>
-              <div class="title">官方库</div>
+              <div class="title">{{ t('layout.libraries') }}</div>
               <a
                 v-for="(item, index) in plugins"
                 :key="index"
@@ -156,7 +158,8 @@
         <Dropdown class="about">
           <template #btn>
             <div class="btn">
-              关于<VPIconChevronDown class="icon"></VPIconChevronDown>
+              <span>{{ t('layout.about') }}</span>
+              <VPIconChevronDown class="icon"></VPIconChevronDown>
             </div>
           </template>
           <template #list>
@@ -172,7 +175,7 @@
                     : openToTarget(item.url)
                 "
               >
-                <div class="txt">{{ item.label }}</div>
+                <div class="txt">{{ t(item.label) }}</div>
                 <VPIconExternalLink
                   v-if="item.url"
                   class="icon"
@@ -190,7 +193,7 @@
           href="/sponsors"
           @click.prevent="router.push({ name: routerName.sponsors })"
         >
-          赞助
+          {{ t('layout.sponsor') }}
         </a>
         <a
           class="signin"
@@ -201,7 +204,7 @@
           }"
           @click="handleSignin"
         >
-          签到
+          {{ t('layout.signin') }}
           <div
             class="red-dot"
             v-if="appStore.showSigninRedDot"
@@ -219,7 +222,7 @@
             router.push({ name: routerName.privatizationDeployment })
           "
         >
-          私有化部署
+          {{ t('layout.deploy') }}
           <div class="badge">
             <div class="txt">new</div>
           </div>
@@ -238,36 +241,36 @@
 
         <Dropdown class="start-live">
           <template #btn>
-            <div class="btn">我要开播</div>
+            <div class="btn">{{ t('layout.startLive') }}</div>
           </template>
           <template #list>
             <div class="list">
               <a class="item">
-                <div class="txt">腾讯云开播</div>
+                <div class="txt">{{ t('layout.tencentCssLive') }}</div>
               </a>
               <a
                 class="item"
                 @click.prevent="handleStartLive(LiveRoomTypeEnum.user_srs)"
               >
-                <div class="txt">srs开播</div>
+                <div class="txt">{{ t('layout.srsLive') }}</div>
               </a>
               <a
                 class="item"
                 @click.prevent="handleStartLive(LiveRoomTypeEnum.user_wertc)"
               >
-                <div class="txt">webrtc开播</div>
+                <div class="txt">{{ t('layout.webrtcLive') }}</div>
               </a>
               <a
                 class="item"
                 @click.prevent="handleStartLive(LiveRoomTypeEnum.user_msr)"
               >
-                <div class="txt">msr开播</div>
+                <div class="txt">{{ t('layout.msrLive') }}</div>
               </a>
               <a
                 class="item"
                 @click.prevent="handleStartLive(LiveRoomTypeEnum.user_pk)"
               >
-                <div class="txt">一对一打PK</div>
+                <div class="txt">{{ t('layout.pkLive') }}</div>
               </a>
             </div>
           </template>
@@ -278,7 +281,7 @@
           class="qqlogin"
           @click="appStore.showLoginModal = true"
         >
-          <div class="btn">登录</div>
+          <div class="btn">{{ t('layout.login') }}</div>
         </div>
         <Dropdown
           v-else
@@ -296,13 +299,35 @@
                 class="item"
                 @click.prevent="router.push({ name: routerName.account })"
               >
-                <div class="txt">个人信息</div>
+                <div class="txt">{{ t('layout.profile') }}</div>
               </a>
               <a
                 class="item"
                 @click.prevent="handleLogout"
               >
-                <div class="txt">退出</div>
+                <div class="txt">{{ t('layout.logout') }}</div>
+              </a>
+            </div>
+          </template>
+        </Dropdown>
+
+        <Dropdown class="switch-lang">
+          <template #btn>
+            <div class="btn">
+              {{ localeMap[locale] }}
+              <VPIconChevronDown class="icon"></VPIconChevronDown>
+            </div>
+          </template>
+          <template #list>
+            <div class="list">
+              <a
+                class="item"
+                v-for="(item, index) in localeMap"
+                :key="index"
+                :class="{ active: item === localeMap[locale] }"
+                @click="locale = index"
+              >
+                <div class="txt">{{ item }}</div>
               </a>
             </div>
           </template>
@@ -315,6 +340,7 @@
 <script lang="ts" setup>
 import { openToTarget, windowReload } from 'billd-utils';
 import { onMounted, ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { useRouter } from 'vue-router';
 
 import { fetchCreateSignin, fetchTodayIsSignin } from '@/api/signin';
@@ -328,29 +354,35 @@ import { useAppStore } from '@/store/app';
 import { useUserStore } from '@/store/user';
 import { LiveRoomTypeEnum } from '@/types/ILiveRoom';
 
+const { t, locale } = useI18n();
 const router = useRouter();
 const userStore = useUserStore();
 const appStore = useAppStore();
 const githubStar = ref('');
 
+const localeMap = {
+  zh: '中文',
+  en: 'English',
+};
+
 const about = ref([
   {
-    label: '常见问题',
+    label: 'layout.faq',
     routerName: routerName.faq,
     url: '',
   },
   {
-    label: '团队',
+    label: 'layout.team',
     routerName: routerName.team,
     url: '',
   },
   {
-    label: '官方群',
+    label: 'layout.officialGroup',
     routerName: routerName.group,
     url: '',
   },
   {
-    label: '版本发布',
+    label: 'layout.release',
     routerName: routerName.release,
     url: '',
   },
@@ -693,6 +725,8 @@ function handleStartLive(key: LiveRoomTypeEnum) {
         }
       }
       .qqlogin {
+        margin-right: 20px;
+
         .btn {
           display: flex;
           align-items: center;
@@ -702,7 +736,7 @@ function handleStartLive(key: LiveRoomTypeEnum) {
           height: 35px;
           border-radius: 50%;
           background-color: papayawhip;
-          font-size: 13px;
+          font-size: 12px;
           cursor: pointer;
 
           @extend %containBg;
@@ -720,6 +754,38 @@ function handleStartLive(key: LiveRoomTypeEnum) {
           }
         }
       }
+      .switch-lang {
+        .btn {
+          display: flex;
+          align-items: center;
+          .icon {
+            margin-left: 5px;
+            width: 13px;
+
+            fill: currentColor;
+          }
+        }
+        .list {
+          width: 80px;
+          .item {
+            display: flex;
+            align-items: center;
+            margin-bottom: 5px;
+            padding: 0 15px;
+            cursor: pointer;
+
+            &:hover,
+            &.active {
+              color: $theme-color-gold;
+            }
+            &.disabled {
+              color: initial !important;
+              opacity: 0.5;
+              cursor: not-allowed;
+            }
+          }
+        }
+      }
     }
   }
 }

+ 9 - 5
src/layout/pc/sidebar/index.vue

@@ -5,34 +5,38 @@
       @click="router.push({ name: routerName.rank })"
     >
       <div class="ico rank"></div>
-      <div class="txt">排行榜</div>
+      <div class="txt">{{ t('layout.rank') }}</div>
     </div>
     <div
       class="item"
       @click="router.push({ name: routerName.shop })"
     >
       <div class="ico shop"></div>
-      <div class="txt">商店</div>
+      <div class="txt">{{ t('layout.shop') }}</div>
     </div>
     <div
       class="item"
       @click="router.push({ name: routerName.order })"
     >
       <div class="ico data"></div>
-      <div class="txt">全站充值</div>
+      <div class="txt">{{ t('layout.siteOrder') }}</div>
     </div>
     <div
       class="item"
       @click="router.push({ name: routerName.wallet })"
     >
       <div class="ico wallet"></div>
-      <div class="txt">我的收支</div>
+      <div class="txt">{{ t('layout.myWallet') }}</div>
     </div>
   </aside>
 </template>
 
 <script lang="ts" setup>
+import { useI18n } from 'vue-i18n';
+
 import router, { routerName } from '@/router';
+
+const { t } = useI18n();
 </script>
 
 <style lang="scss" scoped>
@@ -42,7 +46,7 @@ import router, { routerName } from '@/router';
   right: 0;
   z-index: 10;
   padding: 15px 10px;
-  width: 60px;
+  width: 70px;
   border-radius: 20px 0 0 20px;
   background-color: white;
   box-shadow: 0 0 20px 1px rgba($theme-color-gold, 0.15);

+ 11 - 0
src/locales/en.ts

@@ -0,0 +1,11 @@
+import common from '@/locales/en/common';
+import home from '@/locales/en/home';
+import layout from '@/locales/en/layout';
+import rank from '@/locales/en/rank';
+
+export default {
+  ...common,
+  ...layout,
+  ...home,
+  ...rank,
+};

+ 10 - 0
src/locales/en/common.ts

@@ -0,0 +1,10 @@
+import { nameSpaceWrap } from '@/locales/util';
+
+export default nameSpaceWrap('common', {
+  nonedata: 'none data',
+  living: 'living',
+  wallet: 'wallet',
+  payMoney: 'Amount:{money} ¥',
+  aliPayScanTip: 'Open Alipay to scan',
+  payValidity: '有效期{minutes}分钟',
+});

+ 8 - 0
src/locales/en/home.ts

@@ -0,0 +1,8 @@
+import { nameSpaceWrap } from '@/locales/util';
+
+export default nameSpaceWrap('home', {
+  recommendLive: 'Recommend Live',
+  noliveRoomTip: 'There are currently no live rooms available online',
+  copyrightTip:
+    'Part of the content is sourced from the internet. If there is any infringement, please contact me to delete it',
+});

+ 40 - 0
src/locales/en/layout.ts

@@ -0,0 +1,40 @@
+import { nameSpaceWrap } from '@/locales/util';
+
+export default nameSpaceWrap('layout', {
+  home: 'Home',
+  area: 'Area',
+  shop: 'Shop',
+  liveAdmin: 'LiveAdmin',
+  appdownload: 'App Download',
+  doc: 'Doc',
+  ecosystem: 'Ecosystem',
+  about: 'About',
+  sponsor: 'Sponsor',
+  signin: 'Signin',
+  deploy: 'Private Deploy',
+  startLive: 'Start Live',
+  login: 'Login',
+  logout: 'Logout',
+  profile: 'Profile',
+  resource: 'Resource',
+  libraries: 'Official libraries',
+  rank: 'Rank',
+  siteOrder: 'Site Order',
+  myWallet: 'My Wallet',
+
+  guide: 'Guide',
+  apiDoc: 'Api Doc',
+  bilibiliTutorial: 'Bilibili Tutorial',
+  vipCourses: 'Vip Courses',
+
+  faq: 'FAQ',
+  team: 'Team',
+  officialGroup: 'Official Group',
+  release: 'Version Release',
+
+  tencentCssLive: 'Tencent Css Live',
+  srsLive: 'SRS Live',
+  webrtcLive: 'WebRTC Live',
+  msrLive: 'Msr Live',
+  pkLive: 'PK Live',
+});

+ 9 - 0
src/locales/en/rank.ts

@@ -0,0 +1,9 @@
+import { nameSpaceWrap } from '@/locales/util';
+
+export default nameSpaceWrap('rank', {
+  liveRank: 'Live Rank',
+  userRank: 'User Rank',
+  richRank: 'Rich Rank',
+  signinRank: 'Signin Rank',
+  accumulatedSignin: 'Signin: {nums}day',
+});

+ 7 - 0
src/locales/index.ts

@@ -0,0 +1,7 @@
+import enLocales from '@/locales/en';
+import zhLocales from '@/locales/zh';
+
+export const messages = {
+  en: enLocales,
+  zh: zhLocales,
+};

+ 10 - 0
src/locales/util.ts

@@ -0,0 +1,10 @@
+export const nameSpaceWrap = (
+  nameSpace: string,
+  obj: Record<string, string>
+) => {
+  const temp: any = {};
+  Object.keys(obj).forEach((val: string) => {
+    temp[`${nameSpace}.${val}`] = obj[val];
+  });
+  return temp;
+};

+ 11 - 0
src/locales/zh.ts

@@ -0,0 +1,11 @@
+import common from '@/locales/zh/common';
+import home from '@/locales/zh/home';
+import layout from '@/locales/zh/layout';
+import rank from '@/locales/zh/rank';
+
+export default {
+  ...common,
+  ...layout,
+  ...home,
+  ...rank,
+};

+ 10 - 0
src/locales/zh/common.ts

@@ -0,0 +1,10 @@
+import { nameSpaceWrap } from '@/locales/util';
+
+export default nameSpaceWrap('common', {
+  nonedata: '暂无数据',
+  living: '直播中',
+  wallet: '钱包',
+  payMoney: '金额:{money}元',
+  aliPayScanTip: '打开支付宝扫一扫',
+  payValidity: 'Validity period of {minutes} minutes',
+});

+ 7 - 0
src/locales/zh/home.ts

@@ -0,0 +1,7 @@
+import { nameSpaceWrap } from '@/locales/util';
+
+export default nameSpaceWrap('home', {
+  recommendLive: '推荐直播',
+  noliveRoomTip: '当前没有在线的直播间',
+  copyrightTip: '部分内容来源网络,如有侵权,请联系我删除',
+});

+ 40 - 0
src/locales/zh/layout.ts

@@ -0,0 +1,40 @@
+import { nameSpaceWrap } from '@/locales/util';
+
+export default nameSpaceWrap('layout', {
+  home: '首页',
+  area: '分区',
+  shop: '商店',
+  liveAdmin: '直播后台',
+  appdownload: 'App下载',
+  doc: '文档',
+  ecosystem: '生态系统',
+  about: '关于',
+  sponsor: '赞助',
+  signin: '签到',
+  deploy: '私有化部署',
+  startLive: '我要开播',
+  login: '登录',
+  logout: '退出',
+  profile: '个人信息',
+  resource: '资源',
+  libraries: '官方库',
+  rank: '排行榜',
+  siteOrder: '全站充值',
+  myWallet: '我的收支',
+
+  guide: '快速上升',
+  apiDoc: '接口文档',
+  bilibiliTutorial: 'b站教程',
+  vipCourses: 'billd-live付费课',
+
+  faq: '常见问题',
+  team: '团队',
+  officialGroup: '官方群',
+  release: '版本发布',
+
+  tencentCssLive: '腾讯云开播',
+  srsLive: 'SRS开播',
+  webrtcLive: 'WebRTC开播',
+  msrLive: 'Msr开播',
+  pkLive: '一对一打PK',
+});

+ 9 - 0
src/locales/zh/rank.ts

@@ -0,0 +1,9 @@
+import { nameSpaceWrap } from '@/locales/util';
+
+export default nameSpaceWrap('rank', {
+  liveRank: '直播榜',
+  userRank: '用户榜',
+  richRank: '土豪榜',
+  signinRank: '签到榜',
+  accumulatedSignin: '累计签到: {nums}天',
+});

+ 4 - 2
src/main.ts

@@ -7,13 +7,15 @@ import { createApp } from 'vue';
 
 import Message from '@/components/Message/index.vue';
 import registerDirectives from '@/directives';
-import router from '@/router/index';
-import store from '@/store/index';
+import { i18n } from '@/hooks/use-i18n';
+import router from '@/router';
+import store from '@/store';
 
 import App from './App.vue';
 
 const app = createApp(App);
 registerDirectives(app);
+app.use(i18n);
 app.use(store);
 app.use(router);
 

+ 1 - 0
src/utils/index.ts

@@ -3,6 +3,7 @@ import { computeBox, getRangeRandom } from 'billd-utils';
 import sparkMD5 from 'spark-md5';
 
 export function formatMoney(money?: number) {
+  console.log('formatMoneyformatMoney', money);
   if (!money) {
     return '0.00';
   }

+ 3 - 2
src/views/area/id/index.vue

@@ -44,7 +44,7 @@
           v-if="!liveRoomList.length"
           class="null"
         >
-          暂无数据
+          {{ t('common.nonedata') }}
         </div>
       </div>
     </div>
@@ -67,6 +67,7 @@
 
 <script lang="ts" setup>
 import { onMounted, reactive, ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { useRoute } from 'vue-router';
 
 import { fetchLiveRoomList } from '@/api/area';
@@ -79,7 +80,7 @@ import {
 } from '@/types/ILiveRoom';
 
 const liveRoomList = ref<ILiveRoom[]>([]);
-
+const { t } = useI18n();
 const route = useRoute();
 
 const loading = ref(false);

+ 3 - 1
src/views/h5/area/index.vue

@@ -41,7 +41,7 @@
         v-if="!liveRoomList.length"
         class="null"
       >
-        暂无数据
+        {{ t('common.nonedata') }}
       </div>
     </div>
   </div>
@@ -49,6 +49,7 @@
 
 <script lang="ts" setup>
 import { onMounted, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { useRoute } from 'vue-router';
 
 import { fetchLiveRoomList } from '@/api/area';
@@ -61,6 +62,7 @@ import {
 } from '@/types/ILiveRoom';
 
 const liveRoomList = ref<ILiveRoom[]>([]);
+const { t } = useI18n();
 
 const route = useRoute();
 function goRoom(item: ILiveRoom) {

+ 3 - 1
src/views/h5/index.vue

@@ -66,7 +66,7 @@
             v-if="!item.area_live_rooms?.length"
             class="null"
           >
-            暂无数据
+            {{ t('common.nonedata') }}
           </div>
         </div>
       </div>
@@ -76,6 +76,7 @@
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
 
 import { fetchAreaLiveRoomList } from '@/api/area';
 import { IArea, IAreaLiveRoom } from '@/interface';
@@ -89,6 +90,7 @@ import {
 
 const appStore = useAppStore();
 const liveRoomList = ref<IArea[]>([]);
+const { t } = useI18n();
 
 const swiperList = ref([
   {

+ 6 - 4
src/views/home/index.vue

@@ -135,14 +135,14 @@
             v-else
             class="none"
           >
-            当前没有在线的直播间
+            {{ t('home.noliveRoomTip') }}
           </div>
         </div>
       </div>
     </div>
     <div class="area-container">
       <div class="area-item">
-        <div class="title">推荐直播</div>
+        <div class="title">{{ t('home.recommendLive') }}</div>
         <div class="live-room-list">
           <div
             v-for="(iten, indey) in otherLiveRoomList"
@@ -182,7 +182,7 @@
             v-if="!otherLiveRoomList.length"
             class="null"
           >
-            暂无数据
+            {{ t('common.nonedata') }}
           </div>
         </div>
       </div>
@@ -192,13 +192,14 @@
       class="foot"
       v-if="MODULE_CONFIG_SWITCH.copyrightNotice"
     >
-      *部分内容来源网络,如有侵权,请联系我删除~
+      *{{ t('home.copyrightTip') }}~
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
 import { onMounted, ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { useRoute, useRouter } from 'vue-router';
 
 import { fetchLiveList } from '@/api/live';
@@ -234,6 +235,7 @@ const videoWrapTmpRef = ref<HTMLDivElement>();
 const remoteVideoRef = ref<HTMLDivElement>();
 const docW = document.documentElement.clientWidth;
 
+const { t } = useI18n();
 const {
   videoWrapRef,
   videoLoading,

+ 3 - 1
src/views/order/index.vue

@@ -41,19 +41,21 @@
         </div>
         <div class="time">{{ item.send_pay_date || '-' }}</div>
       </div>
-      <div v-if="!payList.length">暂无数据</div>
+      <div v-if="!payList.length">{{ t('common.nonedata') }}</div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
 
 import { fetchOrderList } from '@/api/order';
 import { fullLoading } from '@/components/FullLoading';
 import { IOrder, PayStatusEnum } from '@/interface';
 
 const payList = ref<IOrder[]>([]);
+const { t } = useI18n();
 
 const headList = ref([
   {

+ 4 - 1
src/views/pull/index.vue

@@ -95,7 +95,7 @@
                     <div class="nums">x{{ item.nums }}</div>
                   </div>
                 </template>
-                <span v-else>暂无</span>
+                <span v-else>{{ t('common.nonedata') }}</span>
               </div>
             </n-popover>
             <div class="tag">人气榜</div>
@@ -400,6 +400,7 @@
 <script lang="ts" setup>
 import { getRandomString, openToTarget } from 'billd-utils';
 import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { useRoute } from 'vue-router';
 
 import {
@@ -438,6 +439,8 @@ const route = useRoute();
 const userStore = useUserStore();
 const appStore = useAppStore();
 const networkStore = useNetworkStore();
+const { t } = useI18n();
+
 const roomId = ref(route.params.roomId as string);
 const configBg = ref();
 const configVideo = ref();

+ 2 - 2
src/views/pull/recharge/index.vue

@@ -91,9 +91,9 @@ async function startPay() {
   const res = await fetchFindByTypeGoods(GoodsTypeEnum.recharge);
   if (res.code === 200) {
     showQrPay.value = false;
-    console.log(money.value, '-----');
+    console.log('dddd', money.value);
     nextTick(() => {
-      goodsInfo.money = money.value;
+      goodsInfo.money = money.value * 100;
       goodsInfo.goodsId = res.data.id!;
       showQrPay.value = true;
     });

+ 21 - 12
src/views/rank/index.vue

@@ -7,7 +7,7 @@
         :class="{ item: 1, active: item.type === currRankType }"
         @click="changeCurrRankType(item.type)"
       >
-        {{ item.label }}
+        {{ t(item.label) }}
       </div>
     </div>
 
@@ -45,20 +45,23 @@
               class="living"
               @click="handleJoin(item.live)"
             >
-              直播中
+              {{ t('common.living') }}
             </div>
           </div>
           <div
             class="wallet"
             v-if="currRankType === RankTypeEnum.wallet"
           >
-            钱包:{{ formatMoney(item.balance) }}元
+            <span>{{ t('common.wallet') }}: </span>
+            <span>{{ formatMoney(item.balance) }}¥</span>
           </div>
           <div
             class="signin"
             v-if="currRankType === RankTypeEnum.signin"
           >
-            累计签到:{{ item.signin_nums }}天
+            <span>
+              {{ t('rank.accumulatedSignin', { nums: item.signin_nums }) }}
+            </span>
           </div>
         </div>
       </div>
@@ -91,20 +94,25 @@
               class="wallet"
               v-if="currRankType === RankTypeEnum.wallet"
             >
-              (钱包:{{ formatMoney(item.balance) }}元)
+              <span>{{ t('common.wallet') }}: </span>
+              <span>{{ formatMoney(item.balance) }}¥</span>
             </div>
             <div
               class="signin"
               v-if="currRankType === RankTypeEnum.signin"
             >
-              (累计签到:{{ item.signin_nums }}天)
+              <span>
+                ({{
+                  t('rank.accumulatedSignin', { nums: item.signin_nums })
+                }})
+              </span>
             </div>
             <div
               v-if="item.live?.live && currRankType === RankTypeEnum.liveRoom"
               class="living-tag"
               @click.stop="handleJoin(item.live)"
             >
-              直播中
+              {{ t('common.living') }}
             </div>
           </div>
           <div class="right"></div>
@@ -116,6 +124,7 @@
 
 <script lang="ts" setup>
 import { onMounted, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
 
 import { fetchLiveRoomList } from '@/api/liveRoom';
 import { fetchSigninList } from '@/api/signin';
@@ -135,19 +144,19 @@ export interface IRankType {
 const rankTypeList = ref<IRankType[]>([
   {
     type: RankTypeEnum.liveRoom,
-    label: '直播榜',
+    label: 'rank.liveRank',
   },
   {
     type: RankTypeEnum.user,
-    label: '用户榜',
+    label: 'rank.userRank',
   },
   {
     type: RankTypeEnum.wallet,
-    label: '土豪榜',
+    label: 'rank.richRank',
   },
   {
     type: RankTypeEnum.signin,
-    label: '签到榜',
+    label: 'rank.signinRank',
   },
   // {
   //   type: RankTypeEnum.blog,
@@ -158,7 +167,7 @@ const rankTypeList = ref<IRankType[]>([
 const mockDataNums = 4;
 
 const currRankType = ref(RankTypeEnum.liveRoom);
-
+const { t } = useI18n();
 const mockRank: {
   users: { id; username; avatar }[];
   rank: number;

+ 1 - 0
src/views/shop/index.vue

@@ -127,6 +127,7 @@ function startPay(item: IGoods) {
   }
   showQrPay.value = false;
   nextTick(() => {
+    console.log(item.price, '333');
     goodsInfo.money = item.price!;
     goodsInfo.goodsId = item.id!;
     showQrPay.value = true;

+ 3 - 1
src/views/wallet/index.vue

@@ -32,13 +32,14 @@
           <span>{{ formatMoney(item.amount) }}元</span>
         </div>
       </div>
-      <div v-if="!walletRecordList.length">暂无数据</div>
+      <div v-if="!walletRecordList.length">{{ t('common.nonedata') }}</div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
 
 import { fetchWalletRecordMyList } from '@/api/walletRecord';
 import { fullLoading } from '@/components/FullLoading';
@@ -52,6 +53,7 @@ import { formatMoney } from '@/utils';
 
 const userStore = useUserStore();
 const walletRecordList = ref<IWalletRecord[]>([]);
+const { t } = useI18n();
 
 const headList = ref([
   {

+ 22 - 0
test/test.vue

@@ -0,0 +1,22 @@
+<template>
+  <div></div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+
+const worker = ref<Worker>();
+
+onMounted(() => {
+  worker.value = new Worker('worker.js');
+  worker.value.addEventListener('message', (e) => {
+    console.log('从 Worker 接收到的消息:', e.data);
+  });
+  worker.value.postMessage({ type: 'start-loop', delay: 1000 / 30 }); // 发送消息到 Worker
+  // setInterval(function () {
+  //   console.log(`setInterval在工作${new Date().toLocaleString()}`);
+  // }, 100); // 设定间隔为1000毫秒,即1秒
+});
+</script>
+
+<style lang="scss" scoped></style>