2
0

23 Коммиты 9316744ac8 ... 15b82c49c4

Автор SHA1 Сообщение Дата
  Jack Zhou 15b82c49c4 feat: 完善多语言 1 неделя назад
  Jack Zhou fd50eeec4c feat: 完善多语言 1 неделя назад
  Jack Zhou 6e97ffcbdc feat: 完善多语言 1 неделя назад
  Jack Zhou d7162e6067 feat: 完善多语言 1 неделя назад
  Jack Zhou bb68eb5aba feat: 完善多语言 1 неделя назад
  Jack Zhou 7aca8e2c6e feat: 完善多语言 1 неделя назад
  Jack Zhou fe420232b7 feat: 完善多语言 1 неделя назад
  Jack Zhou f5a4729c4a feat: 完善多语言 1 неделя назад
  Jack Zhou 868191b6b0 feat: 完善多语言 1 неделя назад
  Jack Zhou 508501e71f feat: 完善多语言 1 неделя назад
  Jack Zhou 90d959c160 feat: 完善多语言 1 неделя назад
  Jack Zhou 17b585e189 feat: 完善多语言硬编码 1 неделя назад
  Jack Zhou d879a60127 feat: 完善多语言硬编码 1 неделя назад
  Jack Zhou 0315d46a10 feat: 完善多语言硬编码 2 недель назад
  Jack Zhou 28f2638306 feat: 合并dev分支代码 2 недель назад
  Jack Zhou 0c9ead0e14 feat: 完善多语言硬编码 2 недель назад
  Jack Zhou 7e5b939f99 feat: 调整翻译页面 2 недель назад
  Jack Zhou a1f21cab3c feat: 提取多语言硬编码 2 недель назад
  Jack Zhou 4ddbd2144b fix: 解决Modify Booking页面时间选择后错误提示bug 2 недель назад
  Jack Zhou 30b45f171e feat: 实现多语言文件 2 недель назад
  Jack Zhou 9641c81039 feat: 提取硬编码 2 недель назад
  Jack Zhou 8637ab47ee feat: 调整delivery表格审批操作判断逻辑 3 недель назад
  Jack Zhou d64643066d feat: 调整delivery表格审批操作判断逻辑 3 недель назад
100 измененных файлов с 4333 добавлено и 1285 удалено
  1. 7 22
      src/App.vue
  2. 3 1
      src/api/index.ts
  3. 84 0
      src/api/module/multilingual.ts
  4. 7 4
      src/components/AIRobot/src/AIRobot.vue
  5. 55 53
      src/components/AddRules/src/AddRules.vue
  6. 77 39
      src/components/AddRules/src/components/DelayedType.vue
  7. 16 14
      src/components/AddRules/src/components/ETDShipments.vue
  8. 31 27
      src/components/AddRules/src/components/NotiFrequency.vue
  9. 12 8
      src/components/AddRules/src/components/NotiMethods.vue
  10. 23 20
      src/components/AddRules/src/components/ShipmentRange.vue
  11. 7 5
      src/components/AutoComplete/src/AutoComplete.vue
  12. 4 1
      src/components/AutoSelect/src/AutoSelect.vue
  13. 5 3
      src/components/ContainerStatus/src/ContainerStatus.vue
  14. 70 68
      src/components/CreateAddRules/src/CreateAddRules.vue
  15. 14 12
      src/components/CreateAddRules/src/components/DelayedType.vue
  16. 16 14
      src/components/CreateAddRules/src/components/ETDShipments.vue
  17. 30 26
      src/components/CreateAddRules/src/components/NotiFrequency.vue
  18. 13 9
      src/components/CreateAddRules/src/components/NotiMethods.vue
  19. 72 64
      src/components/CreateAddRules/src/components/ShipmentRange.vue
  20. 48 24
      src/components/CustomizeColumns/src/CustomizeColumns.vue
  21. 25 14
      src/components/DateRange/src/DateRange.vue
  22. 10 8
      src/components/DateRange/src/components/CalendarDate.vue
  23. 12 10
      src/components/DateRange/src/components/QuickCalendarDate.vue
  24. 4 2
      src/components/DateRange/src/components/QuickMonth.vue
  25. 10 8
      src/components/DateRange/src/components/VCalendarDate.vue
  26. 0 1
      src/components/FliterTags/src/FilterTags.vue
  27. 31 14
      src/components/MoreFilters/src/MoreFilters.vue
  28. 28 4
      src/components/MoreFilters/src/components/PartiesView.vue
  29. 18 3
      src/components/MoreFilters/src/components/PlacesView.vue
  30. 23 8
      src/components/MoreFilters/src/components/SelectValue.vue
  31. 5 2
      src/components/NotificationMessageCard/src/NotificationMessageCard.vue
  32. 19 12
      src/components/NotificationMessageCard/src/components/EventCard.vue
  33. 6 3
      src/components/NotificationMessageCard/src/components/FeatureUpdateCard.vue
  34. 3 1
      src/components/NotificationMessageCard/src/components/PasswordCard.vue
  35. 4 2
      src/components/ScoringGrade/components/DialogColorful.vue
  36. 21 4
      src/components/ScoringGrade/components/DialogUe.vue
  37. 23 21
      src/components/ScoringGrade/src/ScoringGrade.vue
  38. 3 1
      src/components/SeeAllIcon/src/SeeAllIcon.vue
  39. 9 7
      src/components/SelectTable/src/SelectTable.vue
  40. 7 4
      src/components/SelectTableSelect/src/SelectTableSelect.vue
  41. 3 1
      src/components/ShipmentStatus/src/ShipmentStatus.vue
  42. 3 1
      src/components/TableImgEmpty/TableImgEmpty.vue
  43. 11 8
      src/components/TransportMode/src/TransportMode.vue
  44. 8 2
      src/components/VBox/src/VBox.vue
  45. 3 1
      src/components/VBox_Dashboard/src/VBox_Dashboard.vue
  46. 9 7
      src/components/VBreadcrumb/src/VBreadcrumb.vue
  47. 5 2
      src/components/VDriverGuide/src/VDriverGuide.vue
  48. 5 3
      src/components/VEmpty/src/VEmpty.vue
  49. 3 1
      src/components/VLoading/src/VLoading.vue
  50. 8 6
      src/components/VSliderVerification/src/VSliderVerification.vue
  51. 7 4
      src/components/selectAutoSelect/src/selectAutoSelect.vue
  52. 3 0
      src/directive/VLoading.ts
  53. 1353 11
      src/locales/en.json
  54. 90 6
      src/locales/index.ts
  55. 934 0
      src/locales/zh-cn.json
  56. 2 0
      src/main.ts
  57. 71 30
      src/router/index.ts
  58. 1 1
      src/stores/modules/breadCrumb.ts
  59. 5 4
      src/stores/modules/filtersList.ts
  60. 28 3
      src/styles/theme.scss
  61. 5 3
      src/utils/table.ts
  62. 1 1
      src/views/AIApiLog/src/components/LogDialog.vue
  63. 43 38
      src/views/AIRobotChat/src/AIRobotChat.vue
  64. 4 1
      src/views/AIRobotChat/src/components/AIQuestions.vue
  65. 13 10
      src/views/Booking/src/BookingView.vue
  66. 12 10
      src/views/Booking/src/components/BookingDetail/src/BookingDetail.vue
  67. 21 8
      src/views/Booking/src/components/BookingDetail/src/components/AddReferenceDialog.vue
  68. 47 44
      src/views/Booking/src/components/BookingDetail/src/components/BasicInformation.vue
  69. 5 2
      src/views/Booking/src/components/BookingDetail/src/components/ContainersView.vue
  70. 11 8
      src/views/Booking/src/components/BookingDetail/src/components/EmailView.vue
  71. 22 21
      src/views/Booking/src/components/BookingGuide.vue
  72. 16 19
      src/views/Booking/src/components/BookingTable/src/BookingTable.vue
  73. 10 7
      src/views/Booking/src/components/BookingTable/src/components/DownloadDialog.vue
  74. 20 20
      src/views/Dashboard/src/DashboardView.vue
  75. 12 9
      src/views/Dashboard/src/components/CustomerFilter.vue
  76. 11 8
      src/views/Dashboard/src/components/DashFiters.vue
  77. 9 6
      src/views/Dashboard/src/components/DashboardGuide.vue
  78. 52 7
      src/views/Dashboard/src/components/RecentStatus.vue
  79. 9 4
      src/views/Dashboard/src/components/RevenueChart.vue
  80. 124 79
      src/views/Dashboard/src/components/ScoringSystem.vue
  81. 4 1
      src/views/Dashboard/src/components/TopMap.vue
  82. 7 5
      src/views/DestinationDelivery/src/DestinationDelivery.vue
  83. 15 7
      src/views/DestinationDelivery/src/components/CalendarTagDetailDialog.vue
  84. 30 23
      src/views/DestinationDelivery/src/components/CalendarView.vue
  85. 9 7
      src/views/DestinationDelivery/src/components/ConfiguRations/src/ConfiguRations.vue
  86. 15 12
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/ConfigurationsTable.vue
  87. 23 20
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/CreateNewRule.vue
  88. 49 26
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/RecommendDate.vue
  89. 6 3
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SelectStation.vue
  90. 4 1
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SelectValue.vue
  91. 39 37
      src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SetBookingWindow.vue
  92. 156 118
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/CreateNewbooking.vue
  93. 13 11
      src/views/DestinationDelivery/src/components/CreateNewBooking/src/components/NewbookingTable.vue
  94. 15 7
      src/views/DestinationDelivery/src/components/DeliveryDate.vue
  95. 26 17
      src/views/DestinationDelivery/src/components/ListView.vue
  96. 23 16
      src/views/DestinationDelivery/src/components/TableView/src/TableView.vue
  97. 18 17
      src/views/DestinationDelivery/src/components/TableView/src/components/BookingDetailDialog.vue
  98. 12 9
      src/views/DestinationDelivery/src/components/TableView/src/components/DownloadDialog.vue
  99. 11 8
      src/views/DestinationDelivery/src/components/TableView/src/components/EmailDialog.vue
  100. 4 1
      src/views/DestinationDelivery/src/components/TableView/src/components/OperationLogProcess.vue

+ 7 - 22
src/App.vue

@@ -4,6 +4,7 @@ import { useLangStore } from '@/stores/modules/lang'
 import { useI18n } from 'vue-i18n'
 import { useRoute } from 'vue-router'
 import VideoView from '@/views/Video/src/VideoView.vue'
+import { resolveLocaleByLangLabel, switchAppLocale } from '@/locales'
 
 const route = useRoute()
 
@@ -16,30 +17,14 @@ const langValue = ref()
 const { locale } = useI18n() // 解构出 locale
 
 langValue.value = localStorage.getItem('lang') || 'English'
-const languageChangetest = (label: string) => {
-  if (label === 'Chinese') {
-    // current.$i18n.locale = 'zh_TW'
-    locale.value = 'zh_TW' // 修改 locale 的值
-    langStore.setLang('Chinese')
-  } else {
-    // current.$i18n.locale = 'en_US'
-    locale.value = 'en_US'
-    langStore.setLang('English')
-  }
+const languageChange = async (label: string) => {
+  const targetLocale = resolveLocaleByLangLabel(label)
+  await switchAppLocale(targetLocale)
+  locale.value = targetLocale
 }
 
-languageChangetest(langValue.value)
-
-const langData = ref({})
-onMounted(async () => {
-  try {
-    const response = await fetch('./locales/en.json')
-    console.log('Fetch response:', response)
-    langData.value = await response.json()
-    console.log('Fetch response:', langData.value)
-  } catch (error) {
-    console.error('Error fetching the JSON file:', error)
-  }
+onMounted(() => {
+  languageChange(langValue.value)
 })
 </script>
 

+ 3 - 1
src/api/index.ts

@@ -8,6 +8,7 @@ import * as system from './module/system'
 import * as AIRobot from './module/AIRobot'
 import * as Delivery from './module/Delivery'
 import * as report from './module/report'
+import * as multilingual from './module/multilingual'
 /**
  * api 对象接口定义
  */
@@ -29,7 +30,8 @@ const apis = generateApiMap({
   ...system,
   ...AIRobot,
   ...Delivery,
-  ...report
+  ...report,
+  ...multilingual
 })
 export default {
   ...apis // 取出所有可遍历属性赋值在新的对象上

+ 84 - 0
src/api/module/multilingual.ts

@@ -0,0 +1,84 @@
+import HttpAxios from '@/utils/axios'
+
+const base = import.meta.env.VITE_API_HOST
+const baseUrl = `${base}/main_new_version.php`
+
+type MultilingualLangKey =
+  | 'english'
+  | 'simplifiedChinese'
+  | 'traditionalChinese'
+  | 'french'
+  | 'spanish'
+  | 'portuguese'
+  | string
+
+/**
+ * save Multilingual Config
+ */
+export const saveMultilingualConfig = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'multilingual',
+      operate: 'multilingual_save',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * get Multilingual Config
+ */
+export const getMultilingualConfig = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'multilingual',
+      operate: 'multilingual_search',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * get Multilingual pageInfo
+ */
+export const getMultilingualPageInfo = (params: any, config: any) => {
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'multilingual',
+      operate: 'multilingual_page',
+      ...params
+    },
+    config
+  )
+}
+
+/**
+ * get Multilingual json file
+ */
+export const getMultilingualJsonFile = (
+  params: {
+    langkey?: MultilingualLangKey
+    langKey?: MultilingualLangKey
+    [key: string]: any
+  },
+  config: any
+) => {
+  const { langKey, langkey, ...restParams } = params || {}
+  const targetLangKey = langkey || langKey
+
+  return HttpAxios.post(
+    `${baseUrl}`,
+    {
+      action: 'multilingual',
+      operate: 'load_all_pages_by_lang',
+      ...restParams,
+      langkey: targetLangKey
+    },
+    config
+  )
+}

+ 7 - 4
src/components/AIRobot/src/AIRobot.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { ref, onMounted } from 'vue'
 import normalPng from '../image/icon_ai_robot24_b@2x.png'
 import emitter from '@/utils/bus'
@@ -155,14 +158,14 @@ defineExpose({
       <div class="AIAvator">
         <img width="40px" src="../image/icon_ai_robot36_b@2x.png" />
       </div>
-      <div class="dialogue_title">Hi! I'm your Freight Assistant, always on call</div>
+      <div class="dialogue_title">{{ t('aiRobot.greeting') }}</div>
     </div>
     <div class="flex_end">
       <div class="dialogue_content" style="box-shadow: -10px 10px 24px rgba(58, 0, 78, 0.15)">
         <div class="dialogue_content_title">
           <div class="dialogue_title_left">
             <img src="../image/icon_faq_b@2x.png" width="24px" />
-            Frequently Asked Questions
+            {{ t('aiRobot.frequentlyAskedQuestions') }}
           </div>
         </div>
         <el-carousel class="carousel" :autoplay="false" height="190px" style="width: 452px">
@@ -194,7 +197,7 @@ defineExpose({
       </div>
     </div>
     <div class="dialogue_title" style="margin-bottom: 0">
-      Hi! I'm your Freight Assistant, always on call
+      {{ t('aiRobot.greeting') }}
     </div>
   </div>
   <!-- 悬浮icon -->
@@ -212,7 +215,7 @@ defineExpose({
         />
       </template>
       <!-- hover时显示的对话框 -->
-      <div v-if="AIRobotHoverVisible" class="AIRobot_dialog">Continue the conversation</div>
+      <div v-if="AIRobotHoverVisible" class="AIRobot_dialog">{{ t('aiRobot.continueConversation') }}</div>
     </el-popover>
   </div>
 </template>

+ 55 - 53
src/components/AddRules/src/AddRules.vue

@@ -1,5 +1,6 @@
 <script lang="ts" setup>
 import { ref, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
 import RulesShipments from './components/RulesShipments.vue'
 import AddedrluesTag from './components/AddedrluesTag.vue'
 import DelayedType from './components/DelayedType.vue'
@@ -8,6 +9,7 @@ import NotiFrequency from './components/NotiFrequency.vue'
 import NotiMethods from './components/NotiMethods.vue'
 import submitsucessful from './images/icon_success_big@2x.png'
 import moment from 'moment-timezone'
+const { t } = useI18n()
 interface CheckboxItem {
   value: string
   label: string
@@ -394,13 +396,13 @@ const Savesubscribe = () => {
       MilMethodsList.value.length == 0
     ) {
       if (OceanCheckList.value.length == 0 && AirCheckList.value.length == 0) {
-        missingmessage.value += 'Select Milestone, '
+        missingmessage.value += t('notificationRules.selectMilestone') + ', '
       }
       if (MilFrequencyList.value.length == 0) {
-        missingmessage.value += 'Notification Frequency, '
+        missingmessage.value += t('notificationRules.notificationFrequency') + ', '
       }
       if (MilMethodsList.value.length == 0 || MilMethodsList.value == undefined) {
-        missingmessage.value += 'Notification Method, '
+        missingmessage.value += t('notificationRules.notificationMethod') + ', '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
@@ -431,13 +433,13 @@ const Savesubscribe = () => {
       ConMethodsList.value.length == 0
     ) {
       if (ContainerOceanList.value.length == 0) {
-        missingmessage.value += 'Container Status, '
+        missingmessage.value += t('notificationRules.containerStatus') + ', '
       }
       if (ConFrequencyList.value.length == 0) {
-        missingmessage.value += 'Notification Frequency, '
+        missingmessage.value += t('notificationRules.notificationFrequency') + ', '
       }
       if (ConMethodsList.value.length == 0 || ConMethodsList.value == undefined) {
-        missingmessage.value += 'Notification Method, '
+        missingmessage.value += t('notificationRules.notificationMethod') + ', '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
@@ -456,13 +458,13 @@ const Savesubscribe = () => {
       DepMethodsList.value.length == 0
     ) {
       if (DelayedDeparturedList.value.length == 0 && DelayedAirdList.value.length == 0) {
-        missingmessage.value += 'Select Delayed Type, '
+        missingmessage.value += t('notificationRules.selectDelayedType') + ', '
       }
       if (DepFrequencyList.value.length == 0) {
-        missingmessage.value += 'Notification Frequency, '
+        missingmessage.value += t('notificationRules.notificationFrequency') + ', '
       }
       if (DepMethodsList.value.length == 0 || DepMethodsList.value == undefined) {
-        missingmessage.value += 'Notification Method, '
+        missingmessage.value += t('notificationRules.notificationMethod') + ', '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
@@ -491,13 +493,13 @@ const Savesubscribe = () => {
       ETDMethodsList.value.length == 0
     ) {
       if (ETDOceanList.value.length == 0 && ETDAirList.value.length == 0) {
-        missingmessage.value += 'Select Time Type, '
+        missingmessage.value += t('notificationRules.selectTimeType') + ', '
       }
       if (ETDFrequencyList.value.length == 0) {
-        missingmessage.value += 'Notification Frequency, '
+        missingmessage.value += t('notificationRules.notificationFrequency') + ', '
       }
       if (ETDMethodsList.value.length == 0 || ETDMethodsList.value == undefined) {
-        missingmessage.value += 'Notification Method, '
+        missingmessage.value += t('notificationRules.notificationMethod') + ', '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
@@ -593,12 +595,12 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Select Milestone
+                <span class="stars_red">*</span>{{ t('notificationRules.selectMilestone') }}
               </div>
             </template>
             <div>
               <RulesShipments
-                Title="Ocean Shipments"
+                :Title="t('notificationRules.oceanShipments')"
                 ref="MilOceanref"
                 @ChangeCheckRules="ChangeCheckOceanRules"
                 :CheckboxList="MilestoneOceanListInit"
@@ -607,7 +609,7 @@ defineExpose({
             </div>
             <div>
               <RulesShipments
-                Title="Air Shipments"
+                :Title="t('notificationRules.airShipments')"
                 ref="MilAirref"
                 @ChangeCheckRules="ChangeCheckAirRules"
                 :CheckboxList="MilestoneAirListInit"
@@ -631,12 +633,12 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Select Container Status
+                <span class="stars_red">*</span>{{ t('notificationRules.selectContainerStatus') }}
               </div>
             </template>
             <div>
               <RulesShipments
-                Title="Ocean Shipments"
+                :Title="t('notificationRules.oceanShipments')"
                 ref="ContainerOcean"
                 @ChangeCheckRules="ChangeContainerRules"
                 :CheckboxList="ContainerOceanListInit"
@@ -660,12 +662,12 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Select Delayed Type
+                <span class="stars_red">*</span>{{ t('notificationRules.selectDelayedType') }}
               </div>
             </template>
             <div>
               <DelayedType
-                Title="Ocean Shipments"
+                :Title="t('notificationRules.oceanShipments')"
                 ref="OceanDelayed"
                 :DelayedData="DelayedDataInit"
                 @ChangeCheckRules="ChangeDeayedRules"
@@ -673,7 +675,7 @@ defineExpose({
             </div>
             <div>
               <DelayedType
-                Title="Air Shipments"
+                :Title="t('notificationRules.airShipments')"
                 ref="AirDelayed"
                 :DelayedData="DelayedDataInitAir"
                 @ChangeCheckRules="ChangeAirRules"
@@ -696,12 +698,12 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Select Time Type
+                <span class="stars_red">*</span>{{ t('notificationRules.selectTimeType') }}
               </div>
             </template>
             <div>
               <ETDShipments
-                Title="Ocean Shipments"
+                :Title="t('notificationRules.oceanShipments')"
                 ref="OceanETD"
                 :ETDData="OceanETDInit"
                 @ChangeCheckRules="ChangeETDOceanRules"
@@ -709,7 +711,7 @@ defineExpose({
             </div>
             <div>
               <ETDShipments
-                Title="Air Shipments"
+                :Title="t('notificationRules.airShipments')"
                 ref="AirETD"
                 :ETDData="AirETDInit"
                 @ChangeCheckRules="ChangeETDAirRules"
@@ -732,7 +734,7 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Notification Frequency
+                <span class="stars_red">*</span>{{ t('notificationRules.notificationFrequency') }}
               </div>
             </template>
             <NotiFrequency
@@ -776,7 +778,7 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Notification Method
+                <span class="stars_red">*</span>{{ t('notificationRules.notificationMethod') }}
               </div>
             </template>
             <NotiMethods
@@ -808,125 +810,125 @@ defineExpose({
       </div>
     </div>
     <div class="Rules_right">
-      <div class="right_Title">Added Rules</div>
+      <div class="right_Title">{{ t('notificationRules.addedRules') }}</div>
       <AddedrluesTag
         :CheckedList="DelayedDeparturedList"
         v-if="props.TitleType == 'Departure'"
         @handleCloseRadio="handleCloseDelayed"
-        Title="Ocean Shipments"
+        :Title="t('notificationRules.oceanShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
         :CheckedList="OceanCheckList"
         @handleCloseRadio="handleCloseMilestoneOcean"
-        Title="Ocean Shipments"
+        :Title="t('notificationRules.oceanShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Container'"
         :CheckedList="ContainerOceanList"
         @handleCloseRadio="handleCloseContainer"
-        Title="Ocean Shipments"
+        :Title="t('notificationRules.oceanShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         :CheckedList="ETDOceanList"
         v-if="props.TitleType == 'ETDChange'"
-        Title="Ocean Shipments"
+        :Title="t('notificationRules.oceanShipments')"
         @handleCloseRadio="closeOceanETD"
       ></AddedrluesTag>
       <AddedrluesTag
         :CheckedList="ETDAirList"
         v-if="props.TitleType == 'ETDChange'"
         @handleCloseRadio="closeAirETD"
-        Title="Air Shipments"
+        :Title="t('notificationRules.airShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         :CheckedList="DelayedAirdList"
         v-if="props.TitleType == 'Departure'"
         @handleCloseRadio="handleCloseAirDelayed"
-        Title="Air Shipments"
+        :Title="t('notificationRules.airShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
         @handleCloseRadio="handleCloseMilestoneAir"
         :CheckedList="AirCheckList"
-        Title="Air Shipments"
+        :Title="t('notificationRules.airShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
         :CheckedList="MilFrequencyList"
-        Title="Notification Frequency"
+        :Title="t('notificationRules.notificationFrequency')"
         @handleCloseRadio="handleCloseRadio('Mil')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Container'"
         :CheckedList="ConFrequencyList"
-        Title="Notification Frequency"
+        :Title="t('notificationRules.notificationFrequency')"
         @handleCloseRadio="handleCloseRadio('Con')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Departure'"
         :CheckedList="DepFrequencyList"
-        Title="Notification Frequency"
+        :Title="t('notificationRules.notificationFrequency')"
         @handleCloseRadio="handleCloseRadio('Dep')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'ETDChange'"
         :CheckedList="ETDFrequencyList"
-        Title="Notification Frequency"
+        :Title="t('notificationRules.notificationFrequency')"
         @handleCloseRadio="handleCloseRadio('ETD')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
         :CheckedList="MilMethodsList"
-        Title="Notification Method"
+        :Title="t('notificationRules.notificationMethod')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Container'"
         :CheckedList="ConMethodsList"
-        Title="Notification Method"
+        :Title="t('notificationRules.notificationMethod')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Departure'"
         :CheckedList="DepMethodsList"
-        Title="Notification Method"
+        :Title="t('notificationRules.notificationMethod')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'ETDChange'"
         :CheckedList="ETDMethodsList"
-        Title="Notification Method"
+        :Title="t('notificationRules.notificationMethod')"
       ></AddedrluesTag>
     </div>
   </div>
   <div class="Rules_buttom">
-    <el-button class="el-button--dark rules_button" @click="Savesubscribe">Save</el-button>
+    <el-button class="el-button--dark rules_button" @click="Savesubscribe">{{ t('common.save') }}</el-button>
     <el-button @click="CancelRulesVisible = true" class="rules_button" type="default"
-      >Cancel</el-button
+      >{{ t('common.cancel') }}</el-button
     >
     <!-- 取消保存 -->
     <el-dialog v-model="CancelRulesVisible" width="480">
-      <div>You have unsaved changes.</div>
-      <div>Are you sure you want to leave this page?</div>
+      <div>{{ t('destinationDelivery.unsavedChanges') }}</div>
+      <div>{{ t('destinationDelivery.confirmLeavePage') }}</div>
       <template #footer>
         <div class="dialog-footer">
           <el-button type="default" @click="CancelRulesVisible = false" style="width: 100px"
-            >Cancel</el-button
+            >{{ t('common.cancel') }}</el-button
           >
           <el-button class="el-button--warning" @click="UnsavedCollapse" style="width: 100px">
-            OK
+            {{ t('common.ok') }}
           </el-button>
         </div>
       </template>
       <template #header>
         <div class="warning-header dialog-header">
           <span class="font_family icon-icon_fail_fill_b"></span>
-          Unsaved Changes
+          {{ t('destinationDelivery.unsavedChangesTitle') }}
         </div>
       </template>
     </el-dialog>
     <!-- 保存失败 -->
     <el-dialog v-model="UnableSaveVisible" width="480">
-      <div>{{ missingmessage }} missing.</div>
-      <div>Please complete all required fields.</div>
+      <div>{{ missingmessage }} {{ t('notificationRules.missing') }}.</div>
+      <div>{{ t('destinationDelivery.completeRequiredFields') }}</div>
       <template #footer>
         <div class="dialog-footer">
           <el-button
@@ -934,21 +936,21 @@ defineExpose({
             @click="UnableSaveVisible = false"
             style="width: 100px"
           >
-            OK
+            {{ t('common.ok') }}
           </el-button>
         </div>
       </template>
       <template #header>
         <div class="unable-save-header dialog-header">
           <span class="font_family icon-icon_fail_fill_b"></span>
-          Unable to Save
+          {{ t('destinationDelivery.unableToSave') }}
         </div>
       </template>
     </el-dialog>
     <!-- 保存成功 -->
     <el-dialog v-model="SaveedVisible" width="320" style="height: 212px">
       <div style="text-align: center"><el-image :src="submitsucessful" style="width: 64px" /></div>
-      <div style="text-align: center; margin-top: 20px">Saved successfully</div>
+      <div style="text-align: center; margin-top: 20px">{{ t('common.saveSuccess') }}</div>
     </el-dialog>
   </div>
 </template>

+ 77 - 39
src/components/AddRules/src/components/DelayedType.vue

@@ -1,5 +1,7 @@
 <script lang="ts" setup>
 import { ref, computed, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 
 const props = defineProps({
   Title: String,
@@ -53,9 +55,9 @@ let Arrivalstr: any = ''
 const clampedValue = computed({
   get: () => DepartureTime.value,
   set: (newVal) => {
-    const isValid = /^[1-9]\d*$/.test(newVal);
-    DepartureTime.value = isValid ? newVal : '';
-    if( DepartureTime.value!='') {
+    const isValid = /^[1-9]\d*$/.test(newVal)
+    DepartureTime.value = isValid ? newVal : ''
+    if (DepartureTime.value != '') {
       DepartureTime.value = Math.min(Math.max(parseInt(DepartureTime.value, 10), 1), 365)
     }
   }
@@ -63,9 +65,9 @@ const clampedValue = computed({
 const clampedArrivalValue = computed({
   get: () => ArrivalTime.value,
   set: (newVal) => {
-    const isValid = /^[1-9]\d*$/.test(newVal);
-    ArrivalTime.value = isValid ? newVal : '';
-    if( ArrivalTime.value!='') {
+    const isValid = /^[1-9]\d*$/.test(newVal)
+    ArrivalTime.value = isValid ? newVal : ''
+    if (ArrivalTime.value != '') {
       ArrivalTime.value = Math.min(Math.max(parseInt(ArrivalTime.value, 10), 1), 365)
     }
   }
@@ -73,8 +75,9 @@ const clampedArrivalValue = computed({
 const CheckChange = (val: any) => {
   if (val.includes('Departure Delayed')) {
     isDeparture.value = true
-    if(clampedValue.value != '' && clampedValue.value!= undefined) {
-      Departurestr = 'Departure Delayed (ATD-ETD)' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    if (clampedValue.value != '' && clampedValue.value != undefined) {
+      Departurestr =
+        'Departure Delayed (ATD-ETD)' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
     } else {
       Departurestr = ''
     }
@@ -83,8 +86,13 @@ const CheckChange = (val: any) => {
     }
     if (val.includes('Arrival Delayed (ATA-ETA)')) {
       isArrival.value = true
-      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
-        Arrivalstr = 'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if (clampedArrivalValue.value != '' && clampedArrivalValue.value != undefined) {
+        Arrivalstr =
+          'Arrival Delayed (ATA-ETA)' +
+          ' ≥ ' +
+          clampedArrivalValue.value +
+          ' ' +
+          ArrivalSelect.value
       } else {
         Arrivalstr = ''
       }
@@ -104,8 +112,13 @@ const CheckChange = (val: any) => {
     DepartureSelect.value = ''
     if (val.includes('Arrival Delayed (ATA-ETA)')) {
       isArrival.value = true
-      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
-        Arrivalstr = 'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if (clampedArrivalValue.value != '' && clampedArrivalValue.value != undefined) {
+        Arrivalstr =
+          'Arrival Delayed (ATA-ETA)' +
+          ' ≥ ' +
+          clampedArrivalValue.value +
+          ' ' +
+          ArrivalSelect.value
       } else {
         Arrivalstr = ''
       }
@@ -121,25 +134,26 @@ const CheckChange = (val: any) => {
   }
   emit('ChangeCheckRules', DepartureList.value)
 }
-const handleCheckboxClick = (event:any) => {
+const handleCheckboxClick = (event: any) => {
   // 判断点击的是否为复选框输入区域
   const isCheckboxInput = event.target.closest('.el-checkbox__inner')
   const isCheckboxTitle = event.target.closest('.titlecheckbox')
   if (!isCheckboxInput) {
     // 阻止默认切换行为
-    if(!isCheckboxTitle) {
+    if (!isCheckboxTitle) {
       event.preventDefault()
     }
   }
 }
 const changedeparture = (val: any) => {
-  if(props.Title != 'Air Shipments') {
+  if (props.Title != 'Air Shipments') {
     DepartureSelect.value = 'Day(s)'
     ArrivalSelect.value = 'Day(s)'
   }
   if (val == 'Departure') {
-    if(clampedValue.value != '' && clampedValue.value!= undefined) {
-      Departurestr = 'Departure Delayed (ATD-ETD)' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
+    if (clampedValue.value != '' && clampedValue.value != undefined) {
+      Departurestr =
+        'Departure Delayed (ATD-ETD)' + ' ≥ ' + clampedValue.value + ' ' + DepartureSelect.value
     } else {
       Departurestr = ''
     }
@@ -147,8 +161,13 @@ const changedeparture = (val: any) => {
       DepartureList.value.Departure = Departurestr
     }
     if (val == 'Arrival') {
-      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
-        Arrivalstr = 'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if (clampedArrivalValue.value != '' && clampedArrivalValue.value != undefined) {
+        Arrivalstr =
+          'Arrival Delayed (ATA-ETA)' +
+          ' ≥ ' +
+          clampedArrivalValue.value +
+          ' ' +
+          ArrivalSelect.value
       } else {
         Arrivalstr = ''
       }
@@ -158,8 +177,13 @@ const changedeparture = (val: any) => {
     }
   } else {
     if (val == 'Arrival') {
-      if(clampedArrivalValue.value != '' && clampedArrivalValue.value!= undefined) {
-        Arrivalstr = 'Arrival Delayed (ATA-ETA)' + ' ≥ ' + clampedArrivalValue.value + ' ' + ArrivalSelect.value
+      if (clampedArrivalValue.value != '' && clampedArrivalValue.value != undefined) {
+        Arrivalstr =
+          'Arrival Delayed (ATA-ETA)' +
+          ' ≥ ' +
+          clampedArrivalValue.value +
+          ' ' +
+          ArrivalSelect.value
       } else {
         Arrivalstr = ''
       }
@@ -215,26 +239,30 @@ defineExpose({
         </template>
         <div class="oceanCheckbox">
           <el-checkbox-group @change="CheckChange" v-model="OceanCheckedList">
-            <el-checkbox @click="handleCheckboxClick($event)" class="delayedType" value="Departure Delayed">
-              <div class="titlecheckbox">Departure Delayed (ATD-ETD)</div>
+            <el-checkbox
+              @click="handleCheckboxClick($event)"
+              class="delayedType"
+              value="Departure Delayed"
+            >
+              <div class="titlecheckbox">{{ t('notificationRules.departureDelayedAtdEtd') }}</div>
               <div v-if="isDeparture" class="flex" style="margin-top: 16px">
-                <span class="delayedTitle">Delayed Time</span>
+                <span class="delayedTitle">{{ t('notificationRules.delayedTime') }}</span>
                 <span class="delayedIcon">≥</span>
                 <el-input
                   v-model="clampedValue"
                   class="input-with-select"
                   @input="changedeparture('Departure')"
-                  v-if="props.Title == 'Air Shipments'" 
+                  v-if="props.Title == 'Air Shipments'"
                 >
                   <template #append>
                     <el-select
                       v-model="DepartureSelect"
-                      placeholder="Select"
+                      :placeholder="t('destinationDelivery.select')"
                       class="arrivalselect"
                       @change="changedeparture('Departure')"
                     >
-                      <el-option label="Day(s)" value="Day(s)" />
-                      <el-option label="Hour(s)" value="Hour(s)" />
+                      <el-option :label="t('notificationRules.days')" value="Day(s)" />
+                      <el-option :label="t('notificationRules.hours')" value="Hour(s)" />
                     </el-select>
                   </template>
                 </el-input>
@@ -245,14 +273,19 @@ defineExpose({
                   class="input-with-select1"
                 >
                 </el-input>
-                <div
-                v-if="props.Title != 'Air Shipments'" class="Days">Day(s)</div>
+                <div v-if="props.Title != 'Air Shipments'" class="Days">
+                  {{ t('notificationRules.days') }}
+                </div>
               </div>
             </el-checkbox>
-            <el-checkbox class="delayedType" @click="handleCheckboxClick($event)"  value="Arrival Delayed (ATA-ETA)">
-              <div class="titlecheckbox">Arrival Delayed (ATA-ETA)</div>
+            <el-checkbox
+              class="delayedType"
+              @click="handleCheckboxClick($event)"
+              value="Arrival Delayed (ATA-ETA)"
+            >
+              <div class="titlecheckbox">{{ t('notificationRules.arrivalDelayedAtaEta') }}</div>
               <div v-if="isArrival" class="flex" style="margin-top: 16px">
-                <span class="delayedTitle">Delayed Time</span>
+                <span class="delayedTitle">{{ t('notificationRules.delayedTime') }}</span>
                 <span class="delayedIcon">≥</span>
                 <el-input
                   v-model="clampedArrivalValue"
@@ -263,12 +296,12 @@ defineExpose({
                   <template #append>
                     <el-select
                       v-model="ArrivalSelect"
-                      placeholder="Select"
+                      :placeholder="t('destinationDelivery.select')"
                       class="arrivalselect"
                       @change="changedeparture('Arrival')"
                     >
-                      <el-option label="Day(s)" value="Day(s)" />
-                      <el-option  label="Hour(s)" value="Hour(s)" />
+                      <el-option :label="t('notificationRules.days')" value="Day(s)" />
+                      <el-option :label="t('notificationRules.hours')" value="Hour(s)" />
                     </el-select>
                   </template>
                 </el-input>
@@ -279,7 +312,9 @@ defineExpose({
                   class="input-with-select1"
                 >
                 </el-input>
-                <div v-if="props.Title != 'Air Shipments'" class="Days">Day(s)</div>
+                <div v-if="props.Title != 'Air Shipments'" class="Days">
+                  {{ t('notificationRules.days') }}
+                </div>
               </div>
             </el-checkbox>
           </el-checkbox-group>
@@ -348,7 +383,10 @@ defineExpose({
   box-shadow: 0 0 0 1px var(--color-system-border-1) inset;
 }
 :deep(.el-input-group--append .el-input-group__append .el-select .el-select__wrapper) {
-  box-shadow: 0 1px 0 0 var(--color-system-border-1) inset,0 -1px 0 0 var(--color-system-border-1) inset,-1px 0 0 0 var(--color-system-border-1) inset;
+  box-shadow:
+    0 1px 0 0 var(--color-system-border-1) inset,
+    0 -1px 0 0 var(--color-system-border-1) inset,
+    -1px 0 0 0 var(--color-system-border-1) inset;
 }
 .Days {
   width: 84px;
@@ -369,4 +407,4 @@ defineExpose({
 .titlecheckbox {
   max-width: 198px;
 }
-</style>
+</style>

+ 16 - 14
src/components/AddRules/src/components/ETDShipments.vue

@@ -1,5 +1,7 @@
 <script lang="ts" setup>
 import { ref, computed, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 
 const props = defineProps({
   Title: String,
@@ -311,13 +313,13 @@ const clampedETAValue = computed({
         <div class="oceanCheckbox">
           <el-checkbox-group @change="CheckChange" v-model="OceanCheckedList">
             <el-checkbox @click="handleCheckboxClick($event)" class="delayedType" value="ETD">
-              <div class="titlecheckbox">ETD</div>
+              <div class="titlecheckbox">{{ t('common.etd') }}</div>
               <div v-if="isETD" style="margin-top: 16px">
                 <el-radio-group class="radiocheckbox" v-model="ETDRadio" @change="changeETDRadio">
-                  <el-radio value="1">Notify for all changes</el-radio>
+                  <el-radio value="1">{{ t('notificationRules.notifyAllChanges') }}</el-radio>
                   <el-radio value="2">
                     <div class="flex">
-                      Notify only when time difference
+                      {{ t('notificationRules.notifyOnlyWhenTimeDifference') }}
                       <span class="delayedIcon">≥</span>
                       <el-input
                       v-if="props.Title == 'Air Shipments'"
@@ -328,12 +330,12 @@ const clampedETAValue = computed({
                         <template #append>
                           <el-select
                             v-model="ETDSelect"
-                            placeholder="Select"
+                            :placeholder="t('destinationDelivery.select')"
                             class="arrivalselect"
                             @change="changedeparture('ETD')"
                           >
-                            <el-option label="Day(s)" value="Day(s)" />
-                            <el-option label="Hour(s)" value="Hour(s)" />
+                            <el-option :label="t('notificationRules.days')" value="Day(s)" />
+                            <el-option :label="t('notificationRules.hours')" value="Hour(s)" />
                           </el-select>
                         </template>
                       </el-input>
@@ -345,20 +347,20 @@ const clampedETAValue = computed({
                       >
                       </el-input>
                       <div
-                      v-if="props.Title != 'Air Shipments'" class="Days">Day(s)</div>
+                      v-if="props.Title != 'Air Shipments'" class="Days">{{ t('notificationRules.days') }}</div>
                     </div>
                   </el-radio>
                 </el-radio-group>
               </div>
             </el-checkbox>
             <el-checkbox @click="handleCheckboxClick($event)" class="delayedType" value="ETA">
-              <div class="titlecheckbox">ETA</div>
+              <div class="titlecheckbox">{{ t('common.eta') }}</div>
               <div v-if="isETA" style="margin-top: 16px">
                 <el-radio-group class="radiocheckbox" v-model="ETARadio" @change="changeETARadio">
-                  <el-radio value="1">Notify for all changes</el-radio>
+                  <el-radio value="1">{{ t('notificationRules.notifyAllChanges') }}</el-radio>
                   <el-radio value="2">
                     <div class="flex">
-                      Notify only when time difference
+                      {{ t('notificationRules.notifyOnlyWhenTimeDifference') }}
                       <span class="delayedIcon">≥</span>
                       <el-input
                       v-if="props.Title == 'Air Shipments'"
@@ -369,12 +371,12 @@ const clampedETAValue = computed({
                         <template #append>
                           <el-select
                             v-model="ETASelect"
-                            placeholder="Select"
+                            :placeholder="t('destinationDelivery.select')"
                             class="arrivalselect"
                             @change="changedeparture('ETA')"
                           >
-                            <el-option label="Day(s)" value="Day(s)" />
-                            <el-option label="Hour(s)" value="Hour(s)" />
+                            <el-option :label="t('notificationRules.days')" value="Day(s)" />
+                            <el-option :label="t('notificationRules.hours')" value="Hour(s)" />
                           </el-select>
                         </template>
                       </el-input>
@@ -386,7 +388,7 @@ const clampedETAValue = computed({
                       >
                       </el-input>
                       <div
-                      v-if="props.Title != 'Air Shipments'" class="Days">Day(s)</div>
+                      v-if="props.Title != 'Air Shipments'" class="Days">{{ t('notificationRules.days') }}</div>
                     </div>
                   </el-radio>
                 </el-radio-group>

+ 31 - 27
src/components/AddRules/src/components/NotiFrequency.vue

@@ -1,6 +1,10 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { ref, watch } from 'vue'
 import moment from 'moment-timezone'
+const WEEK_DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
 
 const props = defineProps({
   FrequencyData: Object
@@ -14,31 +18,31 @@ const WeeklyTime = ref('')
 const WeeklyDay = ref('')
 const WeekDay = ref([
   {
-    label: 'Monday',
+    label: t('notificationRules.monday'),
     value: 'Monday'
   },
   {
-    label: 'Tuesday',
+    label: t('notificationRules.tuesday'),
     value: 'Tuesday'
   },
   {
-    label: 'Wednesday',
+    label: t('notificationRules.wednesday'),
     value: 'Wednesday'
   },
   {
-    label: 'Thursday',
+    label: t('notificationRules.thursday'),
     value: 'Thursday'
   },
   {
-    label: 'Friday',
+    label: t('notificationRules.friday'),
     value: 'Friday'
   },
   {
-    label: 'Saturday',
+    label: t('notificationRules.saturday'),
     value: 'Saturday'
   },
   {
-    label: 'Sunday',
+    label: t('notificationRules.sunday'),
     value: 'Sunday'
   }
 ])
@@ -214,19 +218,19 @@ const ChangeFrequency = (val: any) => {
       FrequencyList.value.push(str)
     }
     savesubscribeobj.frequency_type = 'Weekly'
-    if (WeeklyDay.value == 'Monday') {
+    if (WeeklyDay.value == WEEK_DAYS[0]) {
       savesubscribeobj.weekly_week = 1
-    } else if (WeeklyDay.value == 'Tuesday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[1]) {
       savesubscribeobj.weekly_week = 2
-    } else if (WeeklyDay.value == 'Wednesday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[2]) {
       savesubscribeobj.weekly_week = 3
-    } else if (WeeklyDay.value == 'Thursday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[3]) {
       savesubscribeobj.weekly_week = 4
-    } else if (WeeklyDay.value == 'Friday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[4]) {
       savesubscribeobj.weekly_week = 5
-    } else if (WeeklyDay.value == 'Saturday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[5]) {
       savesubscribeobj.weekly_week = 6
-    } else if (WeeklyDay.value == 'Sunday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[6]) {
       savesubscribeobj.weekly_week = 0
     }
     savesubscribeobj.weekly_time = WeeklyTime.value
@@ -274,13 +278,13 @@ defineExpose({
   <div style="margin-top: 11px">
     <el-radio-group v-model="radio" @change="ChangeFrequency">
       <el-radio :value="1" v-if="user_type != null && user_type != 'customer'"
-        >Instant notification for each update</el-radio
+        >{{ t('notificationRules.instantNotificationEachUpdate') }}</el-radio
       >
       <el-radio :value="2">
-        <div>Daily Summary</div>
+        <div>{{ t('notificationRules.dailySummary') }}</div>
         <div class="Daily" v-if="isDaily">
           <div class="Daily_left" style="margin-right: 8px">
-            Select Time
+            {{ t('notificationRules.selectTime') }}
             <div>
               <el-time-select
                 v-model="DailyTime"
@@ -289,16 +293,16 @@ defineExpose({
                 end="23:30"
                 prefix-icon=""
                 @change="changeTime('Daily')"
-                placeholder="Select Time"
+                :placeholder="t('notificationRules.selectTime')"
               ></el-time-select>
             </div>
           </div>
           <div class="Daily_left">
-            Select Time Zone
+            {{ t('notificationRules.selectTimeZone') }}
             <div>
               <el-select
                 v-model="TimeZoneDailySelect"
-                placeholder="Select Time Zone"
+                :placeholder="t('notificationRules.selectTimeZone')"
                 @change="changeTime('Daily')"
               >
                 <el-option
@@ -313,15 +317,15 @@ defineExpose({
         </div>
       </el-radio>
       <el-radio :value="3">
-        <div>Weekly Summary</div>
+        <div>{{ t('notificationRules.weeklySummary') }}</div>
         <div class="Daily" v-if="isWeekly">
           <div class="Weekly_left">
-            Select Day
+            {{ t('notificationRules.selectDay') }}
             <div>
               <el-select
                 v-model="WeeklyDay"
                 @change="changeTime('Weekly')"
-                placeholder="Select Day"
+                :placeholder="t('notificationRules.selectDay')"
               >
                 <el-option
                   v-for="item in WeekDay"
@@ -333,7 +337,7 @@ defineExpose({
             </div>
           </div>
           <div class="Weekly_left" style="margin: 0 8px">
-            Select Time
+            {{ t('notificationRules.selectTime') }}
             <div>
               <el-time-select
                 v-model="WeeklyTime"
@@ -342,16 +346,16 @@ defineExpose({
                 step="00:30"
                 end="23:30"
                 prefix-icon=""
-                placeholder="Select time"
+                :placeholder="t('notificationRules.selectTime')"
               ></el-time-select>
             </div>
           </div>
           <div class="Weekly_left">
-            Select Time Zone
+            {{ t('notificationRules.selectTimeZone') }}
             <div>
               <el-select
                 v-model="TimeZoneWeeklySelect"
-                placeholder="Select Time Zone"
+                :placeholder="t('notificationRules.selectTimeZone')"
                 @change="changeTime('Weekly')"
               >
                 <el-option

+ 12 - 8
src/components/AddRules/src/components/NotiMethods.vue

@@ -1,10 +1,14 @@
 <script setup lang="ts">
 import { ref, watch, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
 import Light_methods from '../images/illustration_system massage@2x.png'
 import Dark_methods from '../images/illustration_system massage_darkmode@2x.png'
 import { useThemeStore } from '@/stores/modules/theme'
 
 const themeStore = useThemeStore()
+const { t } = useI18n()
+const METHOD_BY_EMAIL = 'By Email'
+const METHOD_BY_SYSTEM_MESSAGE = 'By System Message'
 
 const checkMethodList = ref()
 checkMethodList.value = []
@@ -30,13 +34,13 @@ const MethodsInit = () => {
   checkMethodList.value = []
   if (methods_data.value?.method_display != undefined) {
     if (methods_data.value?.method_display.indexOf('Email') != -1) {
-      checkMethodList.value.push('By Email')
+      checkMethodList.value.push(METHOD_BY_EMAIL)
       savesubscribeobj.method_by_email = true
     }
   }
   if (methods_data.value?.method_display != undefined) {
     if (methods_data.value?.method_display.indexOf('System Message') != -1) {
-      checkMethodList.value.push('By System Message')
+      checkMethodList.value.push(METHOD_BY_SYSTEM_MESSAGE)
       savesubscribeobj.method_by_message = true
     }
   }
@@ -45,12 +49,12 @@ const MethodsInit = () => {
 
 // 选中Method
 const changeMethod = (val: any) => {
-  if (val.indexOf('By Email') != -1) {
+  if (val.indexOf(METHOD_BY_EMAIL) != -1) {
     savesubscribeobj.method_by_email = true
   } else {
     savesubscribeobj.method_by_email = false
   }
-  if (val.indexOf('By System Message') != -1) {
+  if (val.indexOf(METHOD_BY_SYSTEM_MESSAGE) != -1) {
     savesubscribeobj.method_by_message = true
   } else {
     savesubscribeobj.method_by_message = false
@@ -77,12 +81,12 @@ defineExpose({
   <div style="margin-top: 11px">
     <div class="Method">
       <el-checkbox-group v-model="checkMethodList" @change="changeMethod">
-        <el-checkbox class="methodcheckbox" value="By Email">
-          <div>By Email</div>
+        <el-checkbox class="methodcheckbox" :value="METHOD_BY_EMAIL">
+          <div>{{ t('notificationRules.byEmail') }}</div>
           <div class="methos_image"><img src="../images/illustration_email@2x.png" /></div>
         </el-checkbox>
-        <el-checkbox class="methodcheckbox" value="By System Message">
-          <div>By System Message</div>
+        <el-checkbox class="methodcheckbox" :value="METHOD_BY_SYSTEM_MESSAGE">
+          <div>{{ t('notificationRules.bySystemMessage') }}</div>
           <div class="methos_image">
             <img :src="MethodsImg" />
           </div>

+ 23 - 20
src/components/AddRules/src/components/ShipmentRange.vue

@@ -1,5 +1,8 @@
 <script lang="ts" setup>
 import { ref, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 
 const OceanActive = ref(['TransportMode', 'Time'])
 const TransportCheckedList = ref([])
@@ -10,15 +13,15 @@ interface OceanItem {
 const TransportList = ref<OceanItem[]>([])
 TransportList.value = [
   {
-    label: 'Ocean',
+    label: t('notificationRules.ocean'),
     value: 'Ocean'
   },
   {
-    label: 'Air',
+    label: t('notificationRules.air'),
     value: 'Air'
   },
   {
-    label: 'Road',
+    label: t('notificationRules.road'),
     value: 'Road'
   }
 ]
@@ -31,9 +34,9 @@ const ETATime = ref()
 const clampedETDValue = computed({
   get: () => ETDTime.value,
   set: (newVal) => {
-    const isValid = /^[1-9]\d*$/.test(newVal);
-    ETDTime.value = isValid ? newVal : '';
-    if( ETDTime.value!='') {
+    const isValid = /^[1-9]\d*$/.test(newVal)
+    ETDTime.value = isValid ? newVal : ''
+    if (ETDTime.value != '') {
       ETDTime.value = Math.min(Math.max(parseInt(ETDTime.value, 10), 1), 365)
     }
   }
@@ -41,9 +44,9 @@ const clampedETDValue = computed({
 const clampedETAValue = computed({
   get: () => ETATime.value,
   set: (newVal) => {
-    const isValid = /^[1-9]\d*$/.test(newVal);
-    ETATime.value = isValid ? newVal : '';
-    if( ETATime.value!='') {
+    const isValid = /^[1-9]\d*$/.test(newVal)
+    ETATime.value = isValid ? newVal : ''
+    if (ETATime.value != '') {
       ETATime.value = Math.min(Math.max(parseInt(ETATime.value, 10), 1), 365)
     }
   }
@@ -54,7 +57,7 @@ let Timestr: any = ''
 const emit = defineEmits(['ChangeCheckRules', 'ChangeCheckTimeRules'])
 const CheckChange = (val: any) => {
   if (val != '') {
-    Transportstr = 'Transport Mode: ' + val
+    Transportstr = `${t('common.transportMode')}: ${val}`
   } else {
     Transportstr = ''
   }
@@ -64,15 +67,15 @@ const CheckChange = (val: any) => {
 // 输入ETD、ETA
 const changeTime = (val: any) => {
   if (val == 1) {
-    if(clampedETDValue.value != '' && clampedETDValue.value != undefined) {
-      Timestr = 'ETD within ' + clampedETDValue.value + ' Day(s)'
-    } else{
+    if (clampedETDValue.value != '' && clampedETDValue.value != undefined) {
+      Timestr = t(`notificationRules.etdTimeStr`, { days: clampedETDValue.value })
+    } else {
       Timestr = ''
     }
   } else if (val == 2) {
-    if(clampedETAValue.value != '' && clampedETAValue.value != undefined) {
-      Timestr = 'ETA within ' + clampedETAValue.value + ' Day(s)'
-    } else{
+    if (clampedETAValue.value != '' && clampedETAValue.value != undefined) {
+      Timestr = t(`notificationRules.etaTimeStr`, { days: clampedETDValue.value })
+    } else {
       Timestr = ''
     }
   } else {
@@ -100,7 +103,7 @@ defineExpose({
     <el-collapse v-model="OceanActive">
       <el-collapse-item name="TransportMode">
         <template #title>
-          <div class="Rules_Title OceanTitle">Transport Mode</div>
+          <div class="Rules_Title OceanTitle">{{ t('common.transportMode') }}</div>
         </template>
         <div class="oceanCheckbox">
           <el-checkbox-group @change="CheckChange" v-model="TransportCheckedList">
@@ -115,7 +118,7 @@ defineExpose({
       </el-collapse-item>
       <el-collapse-item name="Time">
         <template #title>
-          <div class="Rules_Title OceanTitle">Time</div>
+          <div class="Rules_Title OceanTitle">{{ t('notificationRules.time') }}</div>
         </template>
         <div class="oceanCheckbox">
           <el-radio-group v-model="TimeChecked" @change="changeTime">
@@ -203,7 +206,7 @@ defineExpose({
 :deep(.el-radio) {
   background-color: var(--color-drawer-body-bg);
 }
-:deep( .el-radio__inner) {
+:deep(.el-radio__inner) {
   border: 1px solid var(--color-system-checkbox-border);
 }
-</style>
+</style>

+ 7 - 5
src/components/AutoComplete/src/AutoComplete.vue

@@ -1,6 +1,8 @@
 <script setup lang="ts" name="SelTable">
 import _ from 'lodash'
 import { reactive, ref, onMounted, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 
 const emit = defineEmits(['check', 'input'])
 const props = defineProps({
@@ -147,7 +149,7 @@ const handleCurrentChange = (val: number) => {}
       <template #reference>
         <el-input
           v-model="searchVal"
-          placeholder="Please input country/city/uncode"
+          :placeholder="t('common.inputCountryCityUncode')"
           @input="handleSearch"
           @click="handleSearch"
         >
@@ -164,12 +166,12 @@ const handleCurrentChange = (val: number) => {}
         @row-click="handleRowClick"
         header-row-class-name="cus-header"
       >
-        <el-table-column width="120" property="Country" label="Country" />
-        <el-table-column width="120" property="City" label="City" />
-        <el-table-column min-width="120" property="Uncode" label="Uncode" />
+        <el-table-column width="120" property="Country" :label="t('common.country')" />
+        <el-table-column width="120" property="City" :label="t('common.city')" />
+        <el-table-column min-width="120" property="Uncode" :label="t('common.uncode')" />
       </el-table>
       <div class="pagination">
-        <span>Total {{ state.total }}</span>
+        <span>{{ t('common.total') }} {{ state.total }}</span>
         <el-pagination
           v-model:currentPage="state.currentPage"
           v-model:page-size="state.pageSize"

+ 4 - 1
src/components/AutoSelect/src/AutoSelect.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { useFiltersStore } from '@/stores/modules/filtersList'
 import { cloneDeep, debounce } from 'lodash'
 import { useRoute } from 'vue-router'
@@ -75,7 +78,7 @@ const remoteMethod = (query: string) => {
       .catch((err) => {
         options.value = []
         if (!axios.isCancel(err) && !newController.signal.aborted) {
-          ElMessage.error('Failed to load options')
+          ElMessage.error(t('common.failedToLoadOptions'))
         }
       })
       .finally(() => {

+ 5 - 3
src/components/ContainerStatus/src/ContainerStatus.vue

@@ -4,9 +4,11 @@ import { useThemeStore } from '@/stores/modules/theme'
 import lightPng from './image/no_data.png'
 import darkPng from './image/no_data_dark.png'
 import { useUserStore } from '@/stores/modules/user'
+import { useI18n } from 'vue-i18n'
 
 const userStore = useUserStore()
 const themeStore = useThemeStore()
+const { t } = useI18n()
 
 const emptyImg = computed(() => {
   return themeStore.theme === 'dark' ? darkPng : lightPng
@@ -49,7 +51,7 @@ watch(
       >
         <template #title>
           <div class="title">
-            Container <span>{{ containers.label }}</span>
+            {{ t('common.container') }} <span>{{ containers.label }}</span>
           </div>
         </template>
         <div class="step-item" v-for="(item, index) in containers.content" :key="item.title">
@@ -69,10 +71,10 @@ watch(
     </el-collapse>
     <div v-else class="empty-content" style="">
       <img :src="emptyImg" :class="{ 'is-dark': themeStore.theme === 'dark' }" alt="empty" />
-      <div class="empty-text" style="">No data</div>
+      <div class="empty-text" style="">{{ t('common.noData') }}</div>
     </div>
     <div class="footer" v-if="props.website && userStore.userInfo?.user_type === 'employee'">
-      Tracking on carrier website:
+      {{ t('common.trackingOnCarrierWebsite') }}:
       <a :href="props.website" target="_blank" class="link">{{ props.website }}</a>
     </div>
   </div>

+ 70 - 68
src/components/CreateAddRules/src/CreateAddRules.vue

@@ -1,5 +1,6 @@
 <script lang="ts" setup>
 import { ref, watch, onMounted } from 'vue'
+import { useI18n } from 'vue-i18n'
 import RulesShipments from './components/RulesShipments.vue'
 import AddedrluesTag from './components/AddedrluesTag.vue'
 import DelayedType from './components/DelayedType.vue'
@@ -10,6 +11,7 @@ import NotiMethods from './components/NotiMethods.vue'
 import submitsucessful from './images/icon_success_big@2x.png'
 import { useRouter } from 'vue-router'
 import moment from 'moment-timezone'
+const { t } = useI18n()
 
 const router = useRouter()
 interface CheckboxItem {
@@ -633,19 +635,19 @@ const Savesubscribe = () => {
       createObj.Timestr == ''
     ) {
       if (createObj.Transportstr == '') {
-        missingmessage.value += 'Transport Mode, '
+        missingmessage.value += t('common.transportMode') + ', '
       }
       if (createObj.Timestr == '') {
-        missingmessage.value += 'Time, '
+        missingmessage.value += t('notificationRules.time') + ', '
       }
       if (OceanCheckList.value.length == 0 && AirCheckList.value.length == 0) {
-        missingmessage.value += 'Select Milestone, '
+        missingmessage.value += t('notificationRules.selectMilestone') + ', '
       }
       if (MilFrequencyList.value.length == 0) {
-        missingmessage.value += 'Notification Frequency, '
+        missingmessage.value += t('notificationRules.notificationFrequency') + ', '
       }
       if (MilMethodsList.value.length == 0) {
-        missingmessage.value += 'Notification Method, '
+        missingmessage.value += t('notificationRules.notificationMethod') + ', '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
@@ -677,19 +679,19 @@ const Savesubscribe = () => {
       createObj.Timestr == ''
     ) {
       if (createObj.Transportstr == '') {
-        missingmessage.value += 'Transport Mode, '
+        missingmessage.value += t('common.transportMode') + ', '
       }
       if (createObj.Timestr == '') {
-        missingmessage.value += 'Time, '
+        missingmessage.value += t('notificationRules.time') + ', '
       }
       if (ContainerOceanList.value.length == 0) {
-        missingmessage.value += 'Container Status, '
+        missingmessage.value += t('notificationRules.containerStatus') + ', '
       }
       if (ConFrequencyList.value.length == 0) {
-        missingmessage.value += 'Notification Frequency, '
+        missingmessage.value += t('notificationRules.notificationFrequency') + ', '
       }
       if (ConMethodsList.value.length == 0) {
-        missingmessage.value += 'Notification Method, '
+        missingmessage.value += t('notificationRules.notificationMethod') + ', '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
@@ -710,19 +712,19 @@ const Savesubscribe = () => {
       createObj.Timestr == ''
     ) {
       if (createObj.Transportstr == '') {
-        missingmessage.value += 'Transport Mode, '
+        missingmessage.value += t('common.transportMode') + ', '
       }
       if (createObj.Timestr == '') {
-        missingmessage.value += 'Time, '
+        missingmessage.value += t('notificationRules.time') + ', '
       }
       if (DelayedDeparturedList.value.length == 0 && DelayedAirdList.value.length == 0) {
-        missingmessage.value += 'Select Delayed Shipments, '
+        missingmessage.value += t('notificationRules.selectDelayedShipments') + ', '
       }
       if (DepFrequencyList.value.length == 0) {
-        missingmessage.value += 'Notification Frequency, '
+        missingmessage.value += t('notificationRules.notificationFrequency') + ', '
       }
       if (DepMethodsList.value.length == 0) {
-        missingmessage.value += 'Notification Method, '
+        missingmessage.value += t('notificationRules.notificationMethod') + ', '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
@@ -753,19 +755,19 @@ const Savesubscribe = () => {
       createObj.Timestr == ''
     ) {
       if (createObj.Transportstr == '') {
-        missingmessage.value += 'Transport Mode, '
+        missingmessage.value += t('common.transportMode') + ', '
       }
       if (createObj.Timestr == '') {
-        missingmessage.value += 'Time, '
+        missingmessage.value += t('notificationRules.time') + ', '
       }
       if (ETDOceanList.value.length == 0 && ETDAirList.value.length == 0) {
-        missingmessage.value += 'Select Time Type, '
+        missingmessage.value += t('notificationRules.selectTimeType') + ', '
       }
       if (ETDFrequencyList.value.length == 0) {
-        missingmessage.value += 'Notification Frequency, '
+        missingmessage.value += t('notificationRules.notificationFrequency') + ', '
       }
       if (ETDMethodsList.value.length == 0) {
-        missingmessage.value += 'Notification Method, '
+        missingmessage.value += t('notificationRules.notificationMethod') + ', '
       }
       missingmessage.value = missingmessage.value.substring(0, missingmessage.value.length - 2)
       UnableSaveVisible.value = true
@@ -851,7 +853,7 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Shipment Range
+                <span class="stars_red">*</span>{{ t('notificationRules.shipmentRange') }}
               </div>
             </template>
             <div>
@@ -901,12 +903,12 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Select Milestone
+                <span class="stars_red">*</span>{{ t('notificationRules.selectMilestone') }}
               </div>
             </template>
             <div>
               <RulesShipments
-                Title="Ocean Shipments"
+                :Title="t('notificationRules.oceanShipments')"
                 ref="MilOceanref"
                 @ChangeCheckRules="ChangeCheckOceanRules"
                 :CheckboxList="MilestoneOceanListInit"
@@ -915,7 +917,7 @@ defineExpose({
             </div>
             <div>
               <RulesShipments
-                Title="Air Shipments"
+                :Title="t('notificationRules.airShipments')"
                 ref="MilAirref"
                 @ChangeCheckRules="ChangeCheckAirRules"
                 :CheckboxList="MilestoneAirListInit"
@@ -939,12 +941,12 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Select Container Status
+                <span class="stars_red">*</span>{{ t('notificationRules.selectContainerStatus') }}
               </div>
             </template>
             <div>
               <RulesShipments
-                Title="Ocean Shipments"
+                :Title="t('notificationRules.oceanShipments')"
                 ref="ContainerOcean"
                 @ChangeCheckRules="ChangeContainerRules"
                 :CheckboxList="ContainerOceanListInit"
@@ -968,12 +970,12 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Select Delayed Type
+                <span class="stars_red">*</span>{{ t('notificationRules.selectDelayedType') }}
               </div>
             </template>
             <div>
               <DelayedType
-                Title="Ocean Shipments"
+                :Title="t('notificationRules.oceanShipments')"
                 ref="OceanDelayed"
                 :DelayedData="DelayedDataInit"
                 @ChangeCheckRules="ChangeDeayedRules"
@@ -981,7 +983,7 @@ defineExpose({
             </div>
             <div>
               <DelayedType
-                Title="Air Shipments"
+                :Title="t('notificationRules.airShipments')"
                 ref="AirDelayed"
                 :DelayedData="DelayedDataInitAir"
                 @ChangeCheckRules="ChangeAirRules"
@@ -1004,12 +1006,12 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Select Time Type
+                <span class="stars_red">*</span>{{ t('notificationRules.selectTimeType') }}
               </div>
             </template>
             <div>
               <ETDShipments
-                Title="Ocean Shipments"
+                :Title="t('notificationRules.oceanShipments')"
                 ref="OceanETD"
                 :ETDData="OceanETDInit"
                 @ChangeCheckRules="ChangeETDOceanRules"
@@ -1017,7 +1019,7 @@ defineExpose({
             </div>
             <div>
               <ETDShipments
-                Title="Air Shipments"
+                :Title="t('notificationRules.airShipments')"
                 ref="AirETD"
                 :ETDData="AirETDInit"
                 @ChangeCheckRules="ChangeETDAirRules"
@@ -1040,7 +1042,7 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Notification Frequency
+                <span class="stars_red">*</span>{{ t('notificationRules.notificationFrequency') }}
               </div>
             </template>
             <NotiFrequency
@@ -1084,7 +1086,7 @@ defineExpose({
                     ></use>
                   </svg>
                 </span>
-                <span class="stars_red">*</span>Notification Method
+                <span class="stars_red">*</span>{{ t('notificationRules.notificationMethod') }}
               </div>
             </template>
             <NotiMethods
@@ -1112,122 +1114,122 @@ defineExpose({
       </div>
     </div>
     <div class="Rules_right">
-      <div class="right_Title">Added Rules</div>
+      <div class="right_Title">{{ t('notificationRules.addedRules') }}</div>
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
         :CheckedList="createListMilestone"
-        Title="Shipment Range"
+        :Title="t('notificationRules.shipmentRange')"
         @handleCloseRadio="handleCloseCreateRule"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Container'"
         :CheckedList="createListContainer"
-        Title="Shipment Range"
+        :Title="t('notificationRules.shipmentRange')"
         @handleCloseRadio="handleCloseCreateRule"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Departure'"
         :CheckedList="createListDeparture"
-        Title="Shipment Range"
+        :Title="t('notificationRules.shipmentRange')"
         @handleCloseRadio="handleCloseCreateRule"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'ETDChange'"
         :CheckedList="createListETDChange"
-        Title="Shipment Range"
+        :Title="t('notificationRules.shipmentRange')"
         @handleCloseRadio="handleCloseCreateRule"
       ></AddedrluesTag>
       <AddedrluesTag
         :CheckedList="DelayedDeparturedList"
         v-if="props.TitleType == 'Departure'"
         @handleCloseRadio="handleCloseDelayed"
-        Title="Ocean Shipments"
+        :Title="t('notificationRules.oceanShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
         @handleCloseRadio="handleCloseMilestoneOcean"
         :CheckedList="OceanCheckList"
-        Title="Ocean Shipments"
+        :Title="t('notificationRules.oceanShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Container'"
         :CheckedList="ContainerOceanList"
         @handleCloseRadio="handleCloseContainer"
-        Title="Ocean Shipments"
+        :Title="t('notificationRules.oceanShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         :CheckedList="ETDOceanList"
         v-if="props.TitleType == 'ETDChange'"
-        Title="Ocean Shipments"
+        :Title="t('notificationRules.oceanShipments')"
         @handleCloseRadio="closeOceanETD"
       ></AddedrluesTag>
       <AddedrluesTag
         :CheckedList="ETDAirList"
         v-if="props.TitleType == 'ETDChange'"
         @handleCloseRadio="closeAirETD"
-        Title="Air Shipments"
+        :Title="t('notificationRules.airShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         :CheckedList="DelayedAirdList"
         v-if="props.TitleType == 'Departure'"
         @handleCloseRadio="handleCloseAirDelayed"
-        Title="Air Shipments"
+        :Title="t('notificationRules.airShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
         :CheckedList="AirCheckList"
         @handleCloseRadio="handleCloseMilestoneAir"
-        Title="Air Shipments"
+        :Title="t('notificationRules.airShipments')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
         :CheckedList="MilFrequencyList"
-        Title="Notification Frequency"
+        :Title="t('notificationRules.notificationFrequency')"
         @handleCloseRadio="handleCloseRadio('Mil')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Container'"
         :CheckedList="ConFrequencyList"
-        Title="Notification Frequency"
+        :Title="t('notificationRules.notificationFrequency')"
         @handleCloseRadio="handleCloseRadio('Con')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Departure'"
         :CheckedList="DepFrequencyList"
-        Title="Notification Frequency"
+        :Title="t('notificationRules.notificationFrequency')"
         @handleCloseRadio="handleCloseRadio('Dep')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'ETDChange'"
         :CheckedList="ETDFrequencyList"
-        Title="Notification Frequency"
+        :Title="t('notificationRules.notificationFrequency')"
         @handleCloseRadio="handleCloseRadio('ETD')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Milestone'"
         :CheckedList="MilMethodsList"
-        Title="Notification Method"
+        :Title="t('notificationRules.notificationMethod')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Container'"
         :CheckedList="ConMethodsList"
-        Title="Notification Method"
+        :Title="t('notificationRules.notificationMethod')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'Departure'"
         :CheckedList="DepMethodsList"
-        Title="Notification Method"
+        :Title="t('notificationRules.notificationMethod')"
       ></AddedrluesTag>
       <AddedrluesTag
         v-if="props.TitleType == 'ETDChange'"
         :CheckedList="ETDMethodsList"
-        Title="Notification Method"
+        :Title="t('notificationRules.notificationMethod')"
       ></AddedrluesTag>
     </div>
   </div>
   <el-dialog v-model="UnableSaveVisible" width="480">
-    <div>{{ missingmessage }} missing.</div>
-    <div>Please complete all required fields.</div>
+    <div>{{ missingmessage }} {{ t('notificationRules.missing') }}.</div>
+    <div>{{ t('destinationDelivery.completeRequiredFields') }}</div>
     <template #footer>
       <div class="dialog-footer">
         <el-button
@@ -1235,44 +1237,44 @@ defineExpose({
           @click="UnableSaveVisible = false"
           style="width: 100px"
         >
-          OK
+          {{ t('common.ok') }}
         </el-button>
       </div>
     </template>
     <template #header>
       <div class="unable-save-header dialog-header">
         <span class="font_family icon-icon_fail_fill_b"></span>
-        Unable to Save
+        {{ t('destinationDelivery.unableToSave') }}
       </div>
     </template>
   </el-dialog>
   <!-- 保存成功 -->
   <el-dialog v-model="SaveedVisible" width="320" style="height: 212px">
     <div style="text-align: center"><el-image :src="submitsucessful" style="width: 64px" /></div>
-    <div style="text-align: center; margin-top: 20px">Saved successfully</div>
+    <div style="text-align: center; margin-top: 20px">{{ t('common.saveSuccess') }}</div>
   </el-dialog>
   <!-- 保存失败 -->
   <el-dialog v-model="SaveVisibleError" width="480">
-    <div>Duplicate Rule Error.</div>
-    <div>This rule exactly matches an existing rule.</div>
+    <div>{{ t('notificationRules.duplicateRuleError') }}</div>
+    <div>{{ t('notificationRules.duplicateRuleExactMatch') }}</div>
     <template #footer>
       <div class="dialog-footer">
         <el-button class="el-button--danger" @click="SaveVisibleError = false" style="width: 100px">
-          OK
+          {{ t('common.ok') }}
         </el-button>
       </div>
     </template>
     <template #header>
       <div class="unable-save-header dialog-header">
         <span class="font_family icon-icon_fail_fill_b"></span>
-        Unable to Save
+        {{ t('destinationDelivery.unableToSave') }}
       </div>
     </template>
   </el-dialog>
   <!-- 三项重合提示 -->
   <el-dialog v-model="SaveVisibleDetected" width="480">
-    <div>A similar configuration rule already exists.</div>
-    <div>Would you like to proceed with creating this rule?</div>
+    <div>{{ t('notificationRules.similarRuleExists') }}</div>
+    <div>{{ t('notificationRules.proceedCreateRule') }}</div>
     <template #footer>
       <div class="dialog-footer">
         <el-button
@@ -1280,21 +1282,21 @@ defineExpose({
           @click="SaveVisibleDetected = false"
           style="width: 100px"
         >
-          Cancel
+          {{ t('common.cancel') }}
         </el-button>
         <el-button
           class="el-button--warning"
           @click="HandelSaveVisibleDetected"
           style="width: 100px"
         >
-          Save
+          {{ t('common.save') }}
         </el-button>
       </div>
     </template>
     <template #header>
       <div class="warning-header dialog-header">
         <span class="font_family icon-icon_warning_fill_b"></span>
-        Similar Rule Detected
+        {{ t('notificationRules.similarRuleDetected') }}
       </div>
     </template>
   </el-dialog>

+ 14 - 12
src/components/CreateAddRules/src/components/DelayedType.vue

@@ -1,5 +1,7 @@
 <script lang="ts" setup>
 import { ref, computed, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 
 const props = defineProps({
   Title: String,
@@ -217,9 +219,9 @@ defineExpose({
         <div class="oceanCheckbox">
           <el-checkbox-group @change="CheckChange" v-model="OceanCheckedList">
             <el-checkbox @click="handleCheckboxClick($event)"  class="delayedType" value="Departure Delayed">
-              <div class="titlecheckbox">Departure Delayed (ATD-ETD)</div>
+              <div class="titlecheckbox">{{ t('notificationRules.departureDelayedAtdEtd') }}</div>
               <div v-if="isDeparture" class="flex" style="margin-top: 16px">
-                <span class="delayedTitle">Delayed Time</span>
+                <span class="delayedTitle">{{ t('notificationRules.delayedTime') }}</span>
                 <span class="delayedIcon">≥</span>
                 <el-input
                   v-model="clampedValue"
@@ -230,12 +232,12 @@ defineExpose({
                   <template #append>
                     <el-select
                       v-model="DepartureSelect"
-                      placeholder="Select"
+                      :placeholder="t('destinationDelivery.select')"
                       class="arrivalselect"
                       @change="changedeparture('Departure')"
                     >
-                      <el-option label="Day(s)" value="Day(s)" />
-                      <el-option label="Hour(s)" value="Hour(s)" />
+                      <el-option :label="t('notificationRules.days')" value="Day(s)" />
+                      <el-option :label="t('notificationRules.hours')" value="Hour(s)" />
                     </el-select>
                   </template>
                 </el-input>
@@ -247,13 +249,13 @@ defineExpose({
                 >
                 </el-input>
                 <div
-                v-if="props.Title != 'Air Shipments'" class="Days">Day(s)</div>
+                v-if="props.Title != 'Air Shipments'" class="Days">{{ t('notificationRules.days') }}</div>
               </div>
             </el-checkbox>
             <el-checkbox @click="handleCheckboxClick($event)"  class="delayedType" value="Arrival Delayed (ATA-ETA)">
-              <div class="titlecheckbox">Arrival Delayed (ATA-ETA)</div>
+              <div class="titlecheckbox">{{ t('notificationRules.arrivalDelayedAtaEta') }}</div>
               <div v-if="isArrival" class="flex" style="margin-top: 16px">
-                <span class="delayedTitle">Delayed Time</span>
+                <span class="delayedTitle">{{ t('notificationRules.delayedTime') }}</span>
                 <span class="delayedIcon">≥</span>
                 <el-input
                   v-model="clampedArrivalValue"
@@ -264,12 +266,12 @@ defineExpose({
                   <template #append>
                     <el-select
                       v-model="ArrivalSelect"
-                      placeholder="Select"
+                      :placeholder="t('destinationDelivery.select')"
                       class="arrivalselect"
                       @change="changedeparture('Arrival')"
                     >
-                      <el-option label="Day(s)" value="Day(s)" />
-                      <el-option  label="Hour(s)" value="Hour(s)" />
+                      <el-option :label="t('notificationRules.days')" value="Day(s)" />
+                      <el-option :label="t('notificationRules.hours')" value="Hour(s)" />
                     </el-select>
                   </template>
                 </el-input>
@@ -280,7 +282,7 @@ defineExpose({
                   class="input-with-select1"
                 >
                 </el-input>
-                <div v-if="props.Title != 'Air Shipments'" class="Days">Day(s)</div>
+                <div v-if="props.Title != 'Air Shipments'" class="Days">{{ t('notificationRules.days') }}</div>
               </div>
             </el-checkbox>
           </el-checkbox-group>

+ 16 - 14
src/components/CreateAddRules/src/components/ETDShipments.vue

@@ -1,5 +1,7 @@
 <script lang="ts" setup>
 import { ref, computed, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 
 const props = defineProps({
   Title: String,
@@ -297,13 +299,13 @@ const clampedETAValue = computed({
         <div class="oceanCheckbox">
           <el-checkbox-group @change="CheckChange" v-model="OceanCheckedList">
             <el-checkbox @click="handleCheckboxClick($event)" class="delayedType" value="ETD">
-              <div class="titlecheckbox">ETD</div>
+              <div class="titlecheckbox">{{ t('common.etd') }}</div>
               <div v-if="isETD" style="margin-top: 16px">
                 <el-radio-group class="radiocheckbox" v-model="ETDRadio" @change="changeETDRadio">
-                  <el-radio value="1">Notify for all changes</el-radio>
+                  <el-radio value="1">{{ t('notificationRules.notifyAllChanges') }}</el-radio>
                   <el-radio value="2">
                     <div class="flex">
-                      Notify only when time difference
+                      {{ t('notificationRules.notifyOnlyWhenTimeDifference') }}
                       <span class="delayedIcon">≥</span>
                       <el-input
                         v-if="props.Title == 'Air Shipments'"
@@ -314,12 +316,12 @@ const clampedETAValue = computed({
                         <template #append>
                           <el-select
                             v-model="ETDSelect"
-                            placeholder="Select"
+                            :placeholder="t('destinationDelivery.select')"
                             class="arrivalselect"
                             @change="changedeparture('ETD')"
                           >
-                            <el-option label="Day(s)" value="Day(s)" />
-                            <el-option label="Hour(s)" value="Hour(s)" />
+                            <el-option :label="t('notificationRules.days')" value="Day(s)" />
+                            <el-option :label="t('notificationRules.hours')" value="Hour(s)" />
                           </el-select>
                         </template>
                       </el-input>
@@ -331,20 +333,20 @@ const clampedETAValue = computed({
                       >
                       </el-input>
                       <div
-                      v-if="props.Title != 'Air Shipments'" class="Days">Day(s)</div>
+                      v-if="props.Title != 'Air Shipments'" class="Days">{{ t('notificationRules.days') }}</div>
                     </div>
                   </el-radio>
                 </el-radio-group>
               </div>
             </el-checkbox>
             <el-checkbox class="delayedType" @click="handleCheckboxClick($event)"  value="ETA">
-              <div class="titlecheckbox">ETA</div>
+              <div class="titlecheckbox">{{ t('common.eta') }}</div>
               <div v-if="isETA" style="margin-top: 16px">
                 <el-radio-group class="radiocheckbox" v-model="ETARadio" @change="changeETARadio">
-                  <el-radio value="1">Notify for all changes</el-radio>
+                  <el-radio value="1">{{ t('notificationRules.notifyAllChanges') }}</el-radio>
                   <el-radio value="2">
                     <div class="flex">
-                      Notify only when time difference
+                      {{ t('notificationRules.notifyOnlyWhenTimeDifference') }}
                       <span class="delayedIcon">≥</span>
                       <el-input
                       v-if="props.Title == 'Air Shipments'"
@@ -355,12 +357,12 @@ const clampedETAValue = computed({
                         <template #append>
                           <el-select
                             v-model="ETASelect"
-                            placeholder="Select"
+                            :placeholder="t('destinationDelivery.select')"
                             class="arrivalselect"
                             @change="changedeparture('ETA')"
                           >
-                            <el-option label="Day(s)" value="Day(s)" />
-                            <el-option label="Hour(s)" value="Hour(s)" />
+                            <el-option :label="t('notificationRules.days')" value="Day(s)" />
+                            <el-option :label="t('notificationRules.hours')" value="Hour(s)" />
                           </el-select>
                         </template>
                       </el-input>
@@ -372,7 +374,7 @@ const clampedETAValue = computed({
                       >
                       </el-input>
                       <div
-                      v-if="props.Title != 'Air Shipments'" class="Days">Day(s)</div>
+                      v-if="props.Title != 'Air Shipments'" class="Days">{{ t('notificationRules.days') }}</div>
                     </div>
                   </el-radio>
                 </el-radio-group>

+ 30 - 26
src/components/CreateAddRules/src/components/NotiFrequency.vue

@@ -1,7 +1,11 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { ref, watch } from 'vue'
 import moment from 'moment-timezone'
 import { defaultTimeZone } from '@/utils/timezone'
+const WEEK_DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
 
 const props = defineProps({
   FrequencyData: Object
@@ -15,31 +19,31 @@ const WeeklyTime = ref('')
 const WeeklyDay = ref('')
 const WeekDay = ref([
   {
-    label: 'Monday',
+    label: t('notificationRules.monday'),
     value: 'Monday'
   },
   {
-    label: 'Tuesday',
+    label: t('notificationRules.tuesday'),
     value: 'Tuesday'
   },
   {
-    label: 'Wednesday',
+    label: t('notificationRules.wednesday'),
     value: 'Wednesday'
   },
   {
-    label: 'Thursday',
+    label: t('notificationRules.thursday'),
     value: 'Thursday'
   },
   {
-    label: 'Friday',
+    label: t('notificationRules.friday'),
     value: 'Friday'
   },
   {
-    label: 'Saturday',
+    label: t('notificationRules.saturday'),
     value: 'Saturday'
   },
   {
-    label: 'Sunday',
+    label: t('notificationRules.sunday'),
     value: 'Sunday'
   }
 ])
@@ -215,19 +219,19 @@ const ChangeFrequency = (val: any) => {
       FrequencyList.value.push(str)
     }
     savesubscribeobj.frequency_type = 'Weekly'
-    if (WeeklyDay.value == 'Monday') {
+    if (WeeklyDay.value == WEEK_DAYS[0]) {
       savesubscribeobj.weekly_week = 1
-    } else if (WeeklyDay.value == 'Tuesday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[1]) {
       savesubscribeobj.weekly_week = 2
-    } else if (WeeklyDay.value == 'Wednesday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[2]) {
       savesubscribeobj.weekly_week = 3
-    } else if (WeeklyDay.value == 'Thursday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[3]) {
       savesubscribeobj.weekly_week = 4
-    } else if (WeeklyDay.value == 'Friday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[4]) {
       savesubscribeobj.weekly_week = 5
-    } else if (WeeklyDay.value == 'Saturday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[5]) {
       savesubscribeobj.weekly_week = 6
-    } else if (WeeklyDay.value == 'Sunday') {
+    } else if (WeeklyDay.value == WEEK_DAYS[6]) {
       savesubscribeobj.weekly_week = 0
     }
     savesubscribeobj.weekly_time = WeeklyTime.value
@@ -278,10 +282,10 @@ defineExpose({
         >Instant notification for each update</el-radio
       > -->
       <el-radio :value="2">
-        <div>Daily Summary</div>
+        <div>{{ t('notificationRules.dailySummary') }}</div>
         <div class="Daily" v-if="isDaily">
           <div class="Daily_left" style="margin-right: 8px">
-            Select Time
+            {{ t('notificationRules.selectTime') }}
             <div>
               <el-time-select
                 v-model="DailyTime"
@@ -290,16 +294,16 @@ defineExpose({
                 end="23:30"
                 prefix-icon=""
                 @change="changeTime('Daily')"
-                placeholder="Select Time"
+                :placeholder="t('notificationRules.selectTime')"
               ></el-time-select>
             </div>
           </div>
           <div class="Daily_left">
-            Select Time Zone
+            {{ t('notificationRules.selectTimeZone') }}
             <div>
               <el-select
                 v-model="TimeZoneDailySelect"
-                placeholder="Select Time Zone"
+                :placeholder="t('notificationRules.selectTimeZone')"
                 @change="changeTime('Daily')"
               >
                 <el-option
@@ -314,15 +318,15 @@ defineExpose({
         </div>
       </el-radio>
       <el-radio :value="3">
-        <div>Weekly Summary</div>
+        <div>{{ t('notificationRules.weeklySummary') }}</div>
         <div class="Daily" v-if="isWeekly">
           <div class="Weekly_left">
-            Select Day
+            {{ t('notificationRules.selectDay') }}
             <div>
               <el-select
                 v-model="WeeklyDay"
                 @change="changeTime('Weekly')"
-                placeholder="Select Day"
+                :placeholder="t('notificationRules.selectDay')"
               >
                 <el-option
                   v-for="item in WeekDay"
@@ -334,7 +338,7 @@ defineExpose({
             </div>
           </div>
           <div class="Weekly_left" style="margin: 0 8px">
-            Select Time
+            {{ t('notificationRules.selectTime') }}
             <div>
               <el-time-select
                 v-model="WeeklyTime"
@@ -343,16 +347,16 @@ defineExpose({
                 step="00:30"
                 end="23:30"
                 prefix-icon=""
-                placeholder="Select time"
+                :placeholder="t('notificationRules.selectTime')"
               ></el-time-select>
             </div>
           </div>
           <div class="Weekly_left">
-            Select Time Zone
+            {{ t('notificationRules.selectTimeZone') }}
             <div>
               <el-select
                 v-model="TimeZoneWeeklySelect"
-                placeholder="Select Time Zone"
+                :placeholder="t('notificationRules.selectTimeZone')"
                 @change="changeTime('Weekly')"
               >
                 <el-option

+ 13 - 9
src/components/CreateAddRules/src/components/NotiMethods.vue

@@ -1,10 +1,14 @@
 <script setup lang="ts">
-import { ref, watch } from 'vue'
+import { ref, watch, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
 import Light_methods from '../images/illustration_system massage@2x.png'
 import Dark_methods from '../images/illustration_system massage_darkmode@2x.png'
 import { useThemeStore } from '@/stores/modules/theme'
 
 const themeStore = useThemeStore()
+const { t } = useI18n()
+const METHOD_BY_EMAIL = 'By Email'
+const METHOD_BY_SYSTEM_MESSAGE = 'By System Message'
 
 const checkMethodList = ref()
 checkMethodList.value = []
@@ -29,12 +33,12 @@ const emits = defineEmits(['ChangeMethodsAdd'])
 const MethodsInit = () => {
   if (methods_data.value?.method_display != undefined) {
     if (methods_data.value?.method_display.indexOf('Email') != -1) {
-      checkMethodList.value.push('By Email')
+      checkMethodList.value.push(METHOD_BY_EMAIL)
     }
   }
   if (methods_data.value?.method_display != undefined) {
     if (methods_data.value?.method_display.indexOf('System Message') != -1) {
-      checkMethodList.value.push('By System Message')
+      checkMethodList.value.push(METHOD_BY_SYSTEM_MESSAGE)
     }
   }
   changeMethod(checkMethodList.value)
@@ -42,12 +46,12 @@ const MethodsInit = () => {
 
 // 选中Method
 const changeMethod = (val: any) => {
-  if (val.indexOf('By Email') != -1) {
+  if (val.indexOf(METHOD_BY_EMAIL) != -1) {
     savesubscribeobj.method_by_email = true
   } else {
     savesubscribeobj.method_by_email = false
   }
-  if (val.indexOf('By System Message') != -1) {
+  if (val.indexOf(METHOD_BY_SYSTEM_MESSAGE) != -1) {
     savesubscribeobj.method_by_message = true
   } else {
     savesubscribeobj.method_by_message = false
@@ -65,12 +69,12 @@ const user_type = localStorage.getItem('user_type')
   <div style="margin-top: 11px">
     <div class="Method">
       <el-checkbox-group v-model="checkMethodList" @change="changeMethod">
-        <el-checkbox class="methodcheckbox" value="By Email">
-          <div>By Email</div>
+        <el-checkbox class="methodcheckbox" :value="METHOD_BY_EMAIL">
+          <div>{{ t('notificationRules.byEmail') }}</div>
           <div class="methos_image"><img src="../images/illustration_email@2x.png" /></div>
         </el-checkbox>
-        <el-checkbox class="methodcheckbox" value="By System Message">
-          <div>By System Message</div>
+        <el-checkbox class="methodcheckbox" :value="METHOD_BY_SYSTEM_MESSAGE">
+          <div>{{ t('notificationRules.bySystemMessage') }}</div>
           <div class="methos_image">
             <img :src="MethodsImg" />
           </div>

+ 72 - 64
src/components/CreateAddRules/src/components/ShipmentRange.vue

@@ -1,5 +1,13 @@
 <script lang="ts" setup>
 import { ref, computed, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
+const RANGE_NEXT_30_DAYS = 'Next 30 days'
+const RANGE_NEXT_60_DAYS = 'Next 60 days'
+const RANGE_PAST_10_TO_NEXT_60_DAYS = 'Past 10 days to next 60 days'
+const RANGE_PAST_30_DAYS = 'Past 30 days'
+const RANGE_CUSTOMIZE = 'Customize'
 
 const OceanActive = ref(['TransportMode', 'Time'])
 const TransportCheckedList = ref([])
@@ -14,15 +22,15 @@ const props = defineProps({
 const ShipmentRange_data = ref(props.ShipmentRangeData)
 TransportList.value = [
   {
-    label: 'Ocean',
+    label: t('notificationRules.ocean'),
     value: 'Ocean'
   },
   {
-    label: 'Air',
+    label: t('notificationRules.air'),
     value: 'Air'
   },
   {
-    label: 'Road',
+    label: t('notificationRules.road'),
     value: 'Road'
   }
 ]
@@ -131,7 +139,7 @@ let Timestr: any = ''
 const emit = defineEmits(['ChangeCheckRules', 'ChangeCheckTimeRules'])
 const CheckChange = (val: any) => {
   if (val != '') {
-    Transportstr = 'Transport Mode: ' + val
+    Transportstr = `${t('common.transportMode')}: ${val}`
   } else {
     Transportstr = ''
   }
@@ -154,25 +162,25 @@ const changeTime = (val: any) => {
       typeof clampedETDValueEnd.value == 'number'
     ) {
       if (clampedETDValueStart.value == 0 && clampedETDValueEnd.value == 30) {
-        defaultradio.value = 'Next 30 days'
+        defaultradio.value = RANGE_NEXT_30_DAYS
       } else if (clampedETDValueStart.value == 0 && clampedETDValueEnd.value == 60) {
-        defaultradio.value = 'Next 60 days'
+        defaultradio.value = RANGE_NEXT_60_DAYS
       } else if (clampedETDValueStart.value == 10 && clampedETDValueEnd.value == 60) {
-        defaultradio.value = 'Past 10 days to next 60 day'
+        defaultradio.value = RANGE_PAST_10_TO_NEXT_60_DAYS
       } else if (clampedETDValueStart.value == 30 && clampedETDValueEnd.value == 0) {
-        defaultradio.value = 'Past 30 days'
+        defaultradio.value = RANGE_PAST_30_DAYS
       } else {
-        defaultradio.value = 'Customize'
+        defaultradio.value = RANGE_CUSTOMIZE
       }
       if (clampedETDValueStart.value == 0 && clampedETDValueEnd.value == 0) {
         Timestr = ''
       } else {
         Timestr =
-          'ETD: minus ' +
+          'ETD: ' + t('notificationRules.minus') + ' ' +
           clampedETDValueStart.value +
-          ' Day(s) to Plus ' +
+          ' ' + t('notificationRules.days') + ' ' + t('report.to') + ' ' + t('notificationRules.plus') + ' ' +
           clampedETDValueEnd.value +
-          ' Day(s)'
+          ' ' + t('notificationRules.days')
       }
     } else {
       Timestr = ''
@@ -190,25 +198,25 @@ const changeTime = (val: any) => {
       typeof clampedETAValueEnd.value == 'number'
     ) {
       if (clampedETAValueStart.value == 0 && clampedETAValueEnd.value == 30) {
-        defaultradio2.value = 'Next 30 days'
+        defaultradio2.value = RANGE_NEXT_30_DAYS
       } else if (clampedETAValueStart.value == 0 && clampedETAValueEnd.value == 60) {
-        defaultradio2.value = 'Next 60 days'
+        defaultradio2.value = RANGE_NEXT_60_DAYS
       } else if (clampedETAValueStart.value == 10 && clampedETAValueEnd.value == 60) {
-        defaultradio2.value = 'Past 10 days to next 60 day'
+        defaultradio2.value = RANGE_PAST_10_TO_NEXT_60_DAYS
       } else if (clampedETAValueStart.value == 30 && clampedETAValueEnd.value == 0) {
-        defaultradio2.value = 'Past 30 days'
+        defaultradio2.value = RANGE_PAST_30_DAYS
       } else {
-        defaultradio2.value = 'Customize'
+        defaultradio2.value = RANGE_CUSTOMIZE
       }
       if (clampedETAValueStart.value == 0 && clampedETAValueEnd.value == 0) {
         Timestr = ''
       } else {
         Timestr =
-          'ETA: minus ' +
+          'ETA: ' + t('notificationRules.minus') + ' ' +
           clampedETAValueStart.value +
-          ' Day(s) to Plus ' +
+          ' ' + t('notificationRules.days') + ' ' + t('report.to') + ' ' + t('notificationRules.plus') + ' ' +
           clampedETAValueEnd.value +
-          ' Day(s)'
+          ' ' + t('notificationRules.days')
       }
     } else {
       Timestr = ''
@@ -221,16 +229,16 @@ const changeTime = (val: any) => {
 
 //切换默认值
 const changedefaultradio = (val: any) => {
-  if (val == 'Next 30 days') {
+  if (val == RANGE_NEXT_30_DAYS) {
     clampedETDValueStart.value = 0
     clampedETDValueEnd.value = 30
-  } else if (val == 'Next 60 days') {
+  } else if (val == RANGE_NEXT_60_DAYS) {
     clampedETDValueStart.value = 0
     clampedETDValueEnd.value = 60
-  } else if (val == 'Past 30 days') {
+  } else if (val == RANGE_PAST_30_DAYS) {
     clampedETDValueStart.value = 30
     clampedETDValueEnd.value = 0
-  } else if (val == 'Past 10 days to next 60 days') {
+  } else if (val == RANGE_PAST_10_TO_NEXT_60_DAYS) {
     clampedETDValueStart.value = 10
     clampedETDValueEnd.value = 60
   } else {
@@ -241,26 +249,26 @@ const changedefaultradio = (val: any) => {
     Timestr = ''
   } else {
     Timestr =
-      'ETD: minus ' +
+      'ETD: ' + t('notificationRules.minus') + ' ' +
       clampedETDValueStart.value +
-      ' Day(s) to Plus ' +
+      ' ' + t('notificationRules.days') + ' ' + t('report.to') + ' ' + t('notificationRules.plus') + ' ' +
       clampedETDValueEnd.value +
-      ' Day(s)'
+      ' ' + t('notificationRules.days')
   }
   emit('ChangeCheckTimeRules', Timestr, clampedETDValueStart.value, clampedETDValueEnd.value)
 }
 //切换默认值
 const changedefaultradioETA = (val: any) => {
-  if (val == 'Next 30 days') {
+  if (val == RANGE_NEXT_30_DAYS) {
     clampedETAValueStart.value = 0
     clampedETAValueEnd.value = 30
-  } else if (val == 'Next 60 days') {
+  } else if (val == RANGE_NEXT_60_DAYS) {
     clampedETAValueStart.value = 0
     clampedETAValueEnd.value = 60
-  } else if (val == 'Past 30 days') {
+  } else if (val == RANGE_PAST_30_DAYS) {
     clampedETAValueStart.value = 30
     clampedETAValueEnd.value = 0
-  } else if (val == 'Past 10 days to next 60 days') {
+  } else if (val == RANGE_PAST_10_TO_NEXT_60_DAYS) {
     clampedETAValueStart.value = 10
     clampedETAValueEnd.value = 60
   } else {
@@ -271,11 +279,11 @@ const changedefaultradioETA = (val: any) => {
     Timestr = ''
   } else {
     Timestr =
-      'ETA: minus ' +
+      'ETA: ' + t('notificationRules.minus') + ' ' +
       clampedETAValueStart.value +
-      ' Day(s) to Plus ' +
+      ' ' + t('notificationRules.days') + ' ' + t('report.to') + ' ' + t('notificationRules.plus') + ' ' +
       clampedETAValueEnd.value +
-      ' Day(s)'
+      ' ' + t('notificationRules.days')
   }
   emit('ChangeCheckTimeRules', Timestr, clampedETAValueStart.value, clampedETAValueEnd.value)
 }
@@ -306,7 +314,7 @@ defineExpose({
         <template #title>
           <div class="Rules_Title_flex">
             <span class="stars_red">*</span>
-            <div class="Rules_Title OceanTitle">Transport Mode</div>
+            <div class="Rules_Title OceanTitle">{{ t('common.transportMode') }}</div>
           </div>
         </template>
         <div class="oceanCheckbox">
@@ -324,94 +332,94 @@ defineExpose({
         <template #title>
           <div class="Rules_Title_flex">
             <span class="stars_red">*</span>
-            <div class="Rules_Title OceanTitle">Time</div>
+            <div class="Rules_Title OceanTitle">{{ t('notificationRules.time') }}</div>
           </div>
         </template>
         <div class="oceanCheckbox">
           <el-radio-group v-model="TimeChecked" @change="changeTime">
             <el-radio :value="1">
-              <div>ETD</div>
+              <div>{{ t('common.etd') }}</div>
               <div v-if="isETDVisible" class="oceanCheckbox2">
                 <el-radio-group v-model="defaultradio" @change="changedefaultradio">
-                  <el-radio-button label="Next 30 days" value="Next 30 days" />
-                  <el-radio-button label="Next 60 days" value="Next 60 days" />
+                  <el-radio-button :label="t('notificationRules.next30Days')" :value="RANGE_NEXT_30_DAYS" />
+                  <el-radio-button :label="t('notificationRules.next60Days')" :value="RANGE_NEXT_60_DAYS" />
                   <el-radio-button
-                    label="Past 10 days to next 60 days"
-                    value="Past 10 days to next 60 days"
+                    :label="t('notificationRules.past10DaysToNext60Days')"
+                    :value="RANGE_PAST_10_TO_NEXT_60_DAYS"
                   />
-                  <el-radio-button label="Past 30 days" value="Past 30 days" />
-                  <el-radio-button label="Customize" value="Customize" />
+                  <el-radio-button :label="t('notificationRules.past30Days')" :value="RANGE_PAST_30_DAYS" />
+                  <el-radio-button :label="t('notificationRules.customize')" :value="RANGE_CUSTOMIZE" />
                 </el-radio-group>
                 <div class="flex" style="align-items: end; margin: 0 8px 8px 0; flex-wrap: wrap">
                   <div class="date_flex">
-                    <div class="time_title">Start Date</div>
+                    <div class="time_title">{{ t('report.startDate') }}</div>
                     <div class="flex">
-                      <div class="currentTime">Current time minus</div>
+                      <div class="currentTime">{{ t('notificationRules.currentTimeMinus') }}</div>
                       <el-input
                         @input="changeTime('1')"
                         v-model="clampedETDValueStart"
                         class="input-with-select"
                       >
                       </el-input>
-                      <div class="Days">Day(s)</div>
+                      <div class="Days">{{ t('notificationRules.days') }}</div>
                     </div>
                   </div>
-                  <div class="To">To</div>
+                  <div class="To">{{ t('report.to') }}</div>
                   <div class="date_flex">
-                    <div class="time_title">End Date</div>
+                    <div class="time_title">{{ t('report.endDate') }}</div>
                     <div class="flex">
-                      <div class="currentTime">Current time plus</div>
+                      <div class="currentTime">{{ t('notificationRules.currentTimePlus') }}</div>
                       <el-input
                         @input="changeTime('1')"
                         v-model="clampedETDValueEnd"
                         class="input-with-select"
                       >
                       </el-input>
-                      <div class="Days">Day(s)</div>
+                      <div class="Days">{{ t('notificationRules.days') }}</div>
                     </div>
                   </div>
                 </div>
               </div>
             </el-radio>
             <el-radio :value="2">
-              <div>ETA</div>
+              <div>{{ t('common.eta') }}</div>
               <div v-if="isETAVisible" class="oceanCheckbox2">
                 <el-radio-group v-model="defaultradio2" @change="changedefaultradioETA">
-                  <el-radio-button label="Next 30 days" value="Next 30 days" />
-                  <el-radio-button label="Next 60 days" value="Next 60 days" />
+                  <el-radio-button :label="t('notificationRules.next30Days')" :value="RANGE_NEXT_30_DAYS" />
+                  <el-radio-button :label="t('notificationRules.next60Days')" :value="RANGE_NEXT_60_DAYS" />
                   <el-radio-button
-                    label="Past 10 days to next 60 days"
-                    value="Past 10 days to next 60 days"
+                    :label="t('notificationRules.past10DaysToNext60Days')"
+                    :value="RANGE_PAST_10_TO_NEXT_60_DAYS"
                   />
-                  <el-radio-button label="Past 30 days" value="Past 30 days" />
-                  <el-radio-button label="Customize" value="Customize" />
+                  <el-radio-button :label="t('notificationRules.past30Days')" :value="RANGE_PAST_30_DAYS" />
+                  <el-radio-button :label="t('notificationRules.customize')" :value="RANGE_CUSTOMIZE" />
                 </el-radio-group>
                 <div class="flex" style="align-items: end; margin: 0 8px 8px 0; flex-wrap: wrap">
                   <div class="date_flex">
-                    <div class="time_title">Start Date</div>
+                    <div class="time_title">{{ t('report.startDate') }}</div>
                     <div class="flex">
-                      <div class="currentTime">Current time minus</div>
+                      <div class="currentTime">{{ t('notificationRules.currentTimeMinus') }}</div>
                       <el-input
                         @input="changeTime('2')"
                         v-model="clampedETAValueStart"
                         class="input-with-select"
                       >
                       </el-input>
-                      <div class="Days">Day(s)</div>
+                      <div class="Days">{{ t('notificationRules.days') }}</div>
                     </div>
                   </div>
-                  <div class="To">To</div>
+                  <div class="To">{{ t('report.to') }}</div>
                   <div class="date_flex">
-                    <div class="time_title">End Date</div>
+                    <div class="time_title">{{ t('report.endDate') }}</div>
                     <div class="flex">
-                      <div class="currentTime">Current time plus</div>
+                      <div class="currentTime">{{ t('notificationRules.currentTimePlus') }}</div>
                       <el-input
                         @input="changeTime('2')"
                         v-model="clampedETAValueEnd"
                         class="input-with-select"
                       >
                       </el-input>
-                      <div class="Days">Day(s)</div>
+                      <div class="Days">{{ t('notificationRules.days') }}</div>
                     </div>
                   </div>
                 </div>

+ 48 - 24
src/components/CustomizeColumns/src/CustomizeColumns.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { VueDraggable } from 'vue-draggable-plus'
 import { useRoute } from 'vue-router'
 
@@ -342,7 +345,7 @@ defineExpose({
     class="customize-columns"
     v-model="dialogVisible"
     :width="1000"
-    title="Customize Columns"
+    :title="t('customizeColumns.title')"
     @close="clearData"
   >
     <div class="search-header">
@@ -351,7 +354,7 @@ defineExpose({
           v-model="searchColumn"
           @change="scrollToItem"
           filterable
-          placeholder="Search columns you preffered"
+          :placeholder="t('customizeColumns.searchPlaceholder')"
         >
           <template #prefix>
             <span class="font_family icon-icon_search_b"></span>
@@ -359,7 +362,7 @@ defineExpose({
           <el-option
             v-for="item in searchOptions"
             :key="item.field"
-            :label="item.label"
+            :label="t(item.cleaned_field_name)"
             :value="item.field"
           />
         </el-select>
@@ -376,7 +379,7 @@ defineExpose({
             <el-tab-pane
               v-for="groupItem in groupColumns"
               :key="groupItem.name"
-              :label="groupItem.name"
+              :label="t(groupItem.cleaned_field_name)"
               :name="groupItem.name"
             >
               <VueDraggable
@@ -397,7 +400,7 @@ defineExpose({
                     @mouseleave="hoverAllIcon = ''"
                   >
                     <span class="font_family icon-icon_dragsort__b draggable-icon"></span>
-                    <span class="title">{{ item.label }}</span>
+                    <span class="title">{{ t(item.cleaned_field_name) }}</span>
                     <span
                       ref="step1"
                       v-if="hoverAllIcon === item.field || (index === 0 && isShowStep1)"
@@ -413,8 +416,13 @@ defineExpose({
       </div>
       <div class="right-select-columns">
         <div class="title">
-          Selected columns on your
-          {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list
+          {{
+            t('customizeColumns.selectedColumnsOnList', {
+              type: route.path.includes('booking')
+                ? t('customizeColumns.booking')
+                : t('customizeColumns.shipment')
+            })
+          }}
         </div>
         <VueDraggable
           v-vloading="loading"
@@ -437,7 +445,7 @@ defineExpose({
                 class="font_family icon-icon_dragsort__b draggable-icon"
                 style="font-size: 16px"
               ></span>
-              <span class="title">{{ item.label }}</span>
+              <span class="title">{{ t(item.cleaned_field_name) }}</span>
               <span
                 v-if="hoverSelectIcon === item.field || (index === 0 && isShowStep2)"
                 class="font_family icon-icon_moveup_b move-icon"
@@ -464,17 +472,17 @@ defineExpose({
         type="default"
         style="height: 40px; padding: 8px 40px"
         @click="dialogVisible = false"
-        >Cancel</el-button
-      >
-      <el-button type="default" style="height: 40px; padding: 8px 20px" @click="handleReset"
-        >Reset to default</el-button
+        >{{ t('common.cancel') }}</el-button
       >
+      <el-button type="default" style="height: 40px; padding: 8px 20px" @click="handleReset">{{
+        t('customizeColumns.resetToDefault')
+      }}</el-button>
       <el-button
         class="el-button--dark"
         style="height: 40px; padding: 8px 40px"
         @click="handleApply"
       >
-        Apply
+        {{ t('report.apply') }}
       </el-button>
     </template>
     <el-tour
@@ -488,10 +496,17 @@ defineExpose({
       <el-tour-step :show-close="false" :target="step1?.[0]">
         <template #default>
           <div class="description">
-            <span>Drag</span> items to the right group or click the "<span>Add</span>" icon to add
-            columns to the {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list.
+            {{
+              t('customizeColumns.tourStep1', {
+                type: route.path.includes('booking')
+                  ? t('customizeColumns.booking')
+                  : t('customizeColumns.shipment')
+              })
+            }}
+          </div>
+          <div class="got-it-text" @click="handleCloseTour('step1')">
+            {{ t('customizeColumns.gotIt') }}
           </div>
-          <div class="got-it-text" @click="handleCloseTour('step1')">Got it</div>
         </template>
       </el-tour-step>
     </el-tour>
@@ -506,17 +521,26 @@ defineExpose({
       <el-tour-step :show-close="false" :target="step2?.[0]">
         <template #default>
           <div class="description">
-            <span>Drag</span> items to the left group or click the "<span>Remove</span>" icon to
-            delete columns from the
-            {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list.
+            {{
+              t('customizeColumns.tourStep2Line1', {
+                type: route.path.includes('booking')
+                  ? t('customizeColumns.booking')
+                  : t('customizeColumns.shipment')
+              })
+            }}
           </div>
           <div class="description">
-            <span>Drag</span> items up or down to reorder the
-            {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list, or use the "<span
-              >Move up</span
-            >" and "<span>Move down</span>" icons.
+            {{
+              t('customizeColumns.tourStep2Line2', {
+                type: route.path.includes('booking')
+                  ? t('customizeColumns.booking')
+                  : t('customizeColumns.shipment')
+              })
+            }}
+          </div>
+          <div class="got-it-text" @click="handleCloseTour('step2')">
+            {{ t('customizeColumns.gotIt') }}
           </div>
-          <div class="got-it-text" @click="handleCloseTour('step2')">Got it</div>
         </template>
       </el-tour-step>
     </el-tour>

+ 25 - 14
src/components/DateRange/src/DateRange.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { ref, watch, onMounted, onBeforeMount } from 'vue'
 import IconDropDown from '@/components/IconDropDown'
 import VCalendarDate from './components/VCalendarDate.vue'
@@ -73,7 +76,7 @@ const DateRangeSearch = () => {
       key: etaKey
     })
   } else {
-    filtersStore.deleteFilterByTitle('ETA')
+    filtersStore.deleteFilterByKey(etaKey)
   }
 
   if (hasValidDate(etdDateRange.value)) {
@@ -84,7 +87,7 @@ const DateRangeSearch = () => {
       key: etdKey
     })
   } else {
-    filtersStore.deleteFilterByTitle('ETD')
+    filtersStore.deleteFilterByKey(etdKey)
   }
 
   allOtherType.value.forEach((item) => {
@@ -97,7 +100,7 @@ const DateRangeSearch = () => {
         key: item.key
       })
     } else {
-      filtersStore.deleteFilterByTitle(item.title)
+      filtersStore.deleteFilterByKey(item.key)
     }
   })
   emit('DateRangeSearch')
@@ -109,10 +112,10 @@ const clearRest = () => {
   etdDateRange.value = []
   etaDateRange.value = []
   otherDateType.value = []
-  filtersStore.deleteFilterByTitle('ETA')
-  filtersStore.deleteFilterByTitle('ETD')
+  filtersStore.deleteFilterByKey(etaKey)
+  filtersStore.deleteFilterByKey(etdKey)
   allOtherType.value.forEach((item) => {
-    filtersStore.deleteFilterByTitle(item.title)
+    filtersStore.deleteFilterByKey(item.key)
   })
   emit('clearDaterangeTags')
 }
@@ -139,7 +142,7 @@ const closeRset = () => {
           @blur="popoverVisible = false"
           @click="popoverVisible = !popoverVisible"
         >
-          <div class="select_title">Date Range</div>
+          <div class="select_title">{{ t('common.dateRange') }}</div>
           <span class="iconfont_icon">
             <svg class="iconfont icon_dark" aria-hidden="true">
               <use xlink:href="#icon-icon_dropdown_b"></use>
@@ -148,7 +151,7 @@ const closeRset = () => {
         </div>
       </template>
       <div class="date_header">
-        <div class="title">Date Range</div>
+        <div class="title">{{ t('common.dateRange') }}</div>
         <div class="ETD">
           <VCalendarDate CalendarTitle="ETD" v-model:date="etdDateRange"></VCalendarDate>
         </div>
@@ -158,7 +161,7 @@ const closeRset = () => {
         <div class="addType" v-for="(item, index) in otherDateType" :key="item.title">
           <div>
             <div class="ETD_title Date_Title">
-              <div class="Date_type">Date Type</div>
+              <div class="Date_type">{{ t('common.dateType') }}</div>
               <span class="iconfont_icon" @click="deleteType(index)">
                 <svg class="iconfont icon_delete" aria-hidden="true">
                   <use xlink:href="#icon-icon_delete_b"></use>
@@ -167,7 +170,7 @@ const closeRset = () => {
             </div>
             <el-select
               :suffix-icon="IconDropDown"
-              placeholder="Please Select Date Type"
+              :placeholder="t('common.pleaseSelectDateType')"
               v-model="item.title"
             >
               <el-option
@@ -181,7 +184,7 @@ const closeRset = () => {
           </div>
           <div style="margin-top: 16px">
             <VCalendarDate
-              :CalendarTitle="item.label || 'Date Range'"
+              :CalendarTitle="item.label || t('common.dateRange')"
               CalendarWidth="352px"
               v-model:date="item.value"
               :isType="true"
@@ -189,12 +192,20 @@ const closeRset = () => {
           </div>
         </div>
         <div class="MoreType" @click="addType" v-if="otherDateType.length != allOtherType.length">
-          <el-button class="el-button--noborder moretype">+ More Date Type</el-button>
+          <el-button class="el-button--noborder moretype"
+            >+ {{ t('common.moreDateType') }}</el-button
+          >
         </div>
         <div class="daterange_bottom">
-          <div><el-button type="default" @click="clearRest" class="Clear">Reset</el-button></div>
           <div>
-            <el-button class="search el-button--dark" @click="DateRangeSearch">Search</el-button>
+            <el-button type="default" @click="clearRest" class="Clear">{{
+              t('common.reset')
+            }}</el-button>
+          </div>
+          <div>
+            <el-button class="search el-button--dark" @click="DateRangeSearch">{{
+              t('common.search')
+            }}</el-button>
           </div>
         </div>
       </div>

+ 10 - 8
src/components/DateRange/src/components/CalendarDate.vue

@@ -2,8 +2,10 @@
 import dayjs, { Dayjs } from 'dayjs'
 import { ref, watch } from 'vue'
 import { useUserStore } from '@/stores/modules/user'
+import { useI18n } from 'vue-i18n'
 
 const userStore = useUserStore()
+const { t } = useI18n()
 const formatDate = userStore.dateFormat
 const valueFormatDate = 'MM/DD/YYYY'
 // type RangeValue = [Dayjs, Dayjs]
@@ -39,9 +41,9 @@ const props = defineProps({
 const ETDDate = ref([])
 const CreatePlaceholder = computed(() => {
   if (props.isETA) {
-    return ['Start ETA Time', 'End ETA Time']
+    return [t('dateRange.startEtaTime'), t('dateRange.endEtaTime')]
   } else {
-    return ['Start ATA Time', 'End ATA Time']
+    return [t('dateRange.startAtaTime'), t('dateRange.endAtaTime')]
   }
 })
 watch(
@@ -125,7 +127,7 @@ const handlePanelChange = (value: any, mode: any) => {
   <div>
     <div class="ETD_title" v-if="props.CalendarTitle">{{ props.CalendarTitle }}</div>
     <a-range-picker
-      separator="To"
+      :separator="t('report.to')"
       :showToday="false"
       :style="{
         width: props.CalendarWidth,
@@ -135,7 +137,7 @@ const handlePanelChange = (value: any, mode: any) => {
       :open="open"
       :disabled="Disabled"
       @change="changeRangeData"
-      :placeholder="isNeedFooter ? ['Start Time', 'End Time'] : CreatePlaceholder"
+      :placeholder="isNeedFooter ? [t('dateRange.startTime'), t('dateRange.endTime')] : CreatePlaceholder"
       :format="userStore.dateFormat"
       valueFormat="MM/DD/YYYY"
       @openChange="handleCalendarOpen(ETDDate)"
@@ -152,14 +154,14 @@ const handlePanelChange = (value: any, mode: any) => {
       <template #renderExtraFooter v-if="isShowExtra && isNeedFooter">
         <div class="calender_flex">
           <div class="footer_left">
-            <el-button class="el-button--noborder" @click="Earliest">Earliest Time</el-button>
+            <el-button class="el-button--noborder" @click="Earliest">{{ t('dateRange.earliestTime') }}</el-button>
             <el-button class="el-button--noborder" @click="ChangeToday('Earliest')"
-              >Today</el-button
+              >{{ t('dateRange.today') }}</el-button
             >
           </div>
           <div class="footer_left footer_right">
-            <el-button @click="Latest" class="el-button--noborder">Latest Time</el-button>
-            <el-button class="el-button--noborder" @click="ChangeToday('Latest')">Today</el-button>
+            <el-button @click="Latest" class="el-button--noborder">{{ t('dateRange.latestTime') }}</el-button>
+            <el-button class="el-button--noborder" @click="ChangeToday('Latest')">{{ t('dateRange.today') }}</el-button>
           </div>
         </div>
       </template>

+ 12 - 10
src/components/DateRange/src/components/QuickCalendarDate.vue

@@ -3,8 +3,10 @@ import dayjs from 'dayjs'
 import { ref, watch, computed } from 'vue'
 import { formatTimezone } from '@/utils/tools'
 import { useUserStore } from '@/stores/modules/user'
+import { useI18n } from 'vue-i18n'
 
 const userStore = useUserStore()
+const { t } = useI18n()
 const props = defineProps({
   CalendarWidth: {
     type: String,
@@ -95,28 +97,28 @@ const handlePanelChange = (value: any, mode: any) => {
 }
 //预设范围
 const rangePresets = ref([
-  { label: 'Current Month', value: [dayjs().startOf('month'), dayjs().endOf('month')] },
+  { label: t('dateRange.currentMonth'), value: [dayjs().startOf('month'), dayjs().endOf('month')] },
   {
-    label: 'Last Month',
+    label: t('dateRange.lastMonth'),
     value: [
       dayjs().startOf('month').subtract(1, 'month'),
       dayjs().endOf('month').subtract(1, 'month')
     ]
   },
-  { label: 'This Year', value: [dayjs().startOf('year'), dayjs().endOf('year')] }
+  { label: t('dateRange.thisYear'), value: [dayjs().startOf('year'), dayjs().endOf('year')] }
 ])
 const placeholder = computed(() => {
   if (props.isDisabled) {
-    return ['No Start Limit', 'No End Limit']
+    return [t('dateRange.noStartLimit'), t('dateRange.noEndLimit')]
   } else {
-    return ['Start Time', 'End Time']
+    return [t('dateRange.startTime'), t('dateRange.endTime')]
   }
 })
 </script>
 <template>
   <div>
     <a-range-picker
-      separator="To"
+      :separator="t('report.to')"
       :showToday="false"
       :allowClear="false"
       :style="{ width: props.CalendarWidth }"
@@ -142,14 +144,14 @@ const placeholder = computed(() => {
       <template #renderExtraFooter v-if="isShowExtra">
         <div class="calender_flex">
           <div class="footer_left">
-            <el-button class="el-button--noborder" @click="Earliest">Earliest Time</el-button>
+            <el-button class="el-button--noborder" @click="Earliest">{{ t('dateRange.earliestTime') }}</el-button>
             <el-button class="el-button--noborder" @click="ChangeToday('Earliest')"
-              >Today</el-button
+              >{{ t('dateRange.today') }}</el-button
             >
           </div>
           <div class="footer_left footer_right">
-            <el-button @click="Latest" class="el-button--noborder">Latest Time</el-button>
-            <el-button class="el-button--noborder" @click="ChangeToday('Latest')">Today</el-button>
+            <el-button @click="Latest" class="el-button--noborder">{{ t('dateRange.latestTime') }}</el-button>
+            <el-button class="el-button--noborder" @click="ChangeToday('Latest')">{{ t('dateRange.today') }}</el-button>
           </div>
         </div>
       </template>

+ 4 - 2
src/components/DateRange/src/components/QuickMonth.vue

@@ -1,6 +1,8 @@
 <script lang="ts" setup>
 import { ref, watch } from 'vue'
 import moment from 'moment'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 
 const props = defineProps({
   MonthStartDate: {
@@ -64,7 +66,7 @@ const EndChange = (val: any) => {
       type="month"
       @change="StartChange"
       :clearable="false"
-      placeholder="Start month"
+      :placeholder="t('dateRange.startMonth')"
       placement="bottom"
     />
     <el-date-picker
@@ -74,7 +76,7 @@ const EndChange = (val: any) => {
       type="month"
       @change="EndChange"
       :clearable="false"
-      placeholder="End month"
+      :placeholder="t('dateRange.endMonth')"
       placement="bottom"
     />
   </div>

+ 10 - 8
src/components/DateRange/src/components/VCalendarDate.vue

@@ -2,8 +2,10 @@
 import dayjs, { Dayjs } from 'dayjs'
 import { ref, watch } from 'vue'
 import { useUserStore } from '@/stores/modules/user'
+import { useI18n } from 'vue-i18n'
 
 const userStore = useUserStore()
+const { t } = useI18n()
 const formatDate = userStore.dateFormat
 const valueFormatDate = 'MM/DD/YYYY'
 const props = defineProps({
@@ -37,9 +39,9 @@ const props = defineProps({
 const ETDDate = ref([])
 const CreatePlaceholder = computed(() => {
   if (props.isETA) {
-    return ['Start ETA Time', 'End ETA Time']
+    return [t('dateRange.startEtaTime'), t('dateRange.endEtaTime')]
   } else {
-    return ['Start ATA Time', 'End ATA Time']
+    return [t('dateRange.startAtaTime'), t('dateRange.endAtaTime')]
   }
 })
 
@@ -94,7 +96,7 @@ const handleCalendarOpen = (date: any) => {
   <div>
     <div class="ETD_title" v-if="props.CalendarTitle">{{ props.CalendarTitle }}</div>
     <a-range-picker
-      separator="To"
+      :separator="t('report.to')"
       :showToday="false"
       :style="{
         width: props.CalendarWidth,
@@ -103,7 +105,7 @@ const handleCalendarOpen = (date: any) => {
       :popupClassName="props.isShowPopupClass ? 'th-color' : ''"
       :open="open"
       @change="changeRangeData"
-      :placeholder="isNeedFooter ? ['Start Time', 'End Time'] : CreatePlaceholder"
+      :placeholder="isNeedFooter ? [t('dateRange.startTime'), t('dateRange.endTime')] : CreatePlaceholder"
       :format="formatDate"
       @openChange="handleCalendarOpen(ETDDate)"
       :valueFormat="formatDate"
@@ -119,14 +121,14 @@ const handleCalendarOpen = (date: any) => {
       <template #renderExtraFooter v-if="isShowExtra && isNeedFooter">
         <div class="calender_flex">
           <div class="footer_left">
-            <el-button class="el-button--noborder" @click="Earliest">Earliest Time</el-button>
+            <el-button class="el-button--noborder" @click="Earliest">{{ t('dateRange.earliestTime') }}</el-button>
             <el-button class="el-button--noborder" @click="ChangeToday('Earliest')"
-              >Today</el-button
+              >{{ t('dateRange.today') }}</el-button
             >
           </div>
           <div class="footer_left footer_right">
-            <el-button @click="Latest" class="el-button--noborder">Latest Time</el-button>
-            <el-button class="el-button--noborder" @click="ChangeToday('Latest')">Today</el-button>
+            <el-button @click="Latest" class="el-button--noborder">{{ t('dateRange.latestTime') }}</el-button>
+            <el-button class="el-button--noborder" @click="ChangeToday('Latest')">{{ t('dateRange.today') }}</el-button>
           </div>
         </div>
       </template>

+ 0 - 1
src/components/FliterTags/src/FilterTags.vue

@@ -13,7 +13,6 @@ interface Props {
 const props = withDefaults(defineProps<Props>(), {})
 
 const emits = defineEmits(['tabChange'])
-
 const getCheckedTabs = (tagsList: ListItem[]) => {
   const checkedList = tagsList.filter((item) => item.checked)
   return checkedList.map((item) => item.name)

+ 31 - 14
src/components/MoreFilters/src/MoreFilters.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import AutoSelect from '@/components/AutoSelect'
 import { useFiltersStore } from '@/stores/modules/filtersList'
 import { useRoute } from 'vue-router'
@@ -33,6 +36,20 @@ const transportaionData = ref([
     placeholder: 'Please input Voyage or flight no.'
   }
 ])
+const getTransportationLabel = (title: string) => {
+  const map = {
+    Vessel: t('moreFilters.vessel'),
+    'Voyage/Flight': t('moreFilters.voyageFlight')
+  }
+  return map[title] || title
+}
+const getTransportationPlaceholder = (title: string) => {
+  const map = {
+    Vessel: t('moreFilters.inputVesselNameOrCode'),
+    'Voyage/Flight': t('moreFilters.inputVoyageOrFlightNo')
+  }
+  return map[title] || ''
+}
 
 const partiesViewRef = ref()
 const placesViewRef = ref()
@@ -218,15 +235,15 @@ const moreFiltersGuideImg = computed(() => {
           <use xlink:href="#icon-icon_filter_b"></use>
         </svg>
       </span>
-      <div class="Filters_title">More Filters</div>
+      <div class="Filters_title">{{ t('common.moreFilters') }}</div>
     </el-button>
-    <el-drawer @open="initData" size="400px" title="More Filters" v-model="drawer">
+    <el-drawer @open="initData" size="400px" :title="t('common.moreFilters')" v-model="drawer">
       <el-collapse v-model="collapseValue"
         ><!-- General -->
         <el-collapse-item class="collapse_item" name="General" v-if="searchMode === 'tracking'">
           <template #title>
             <span class="collapse-title"
-              >General
+              >{{ t('common.general') }}
               <el-badge
                 v-if="generalBadgeCount > 0 && !collapseValue?.includes('General')"
                 class="mark"
@@ -235,18 +252,18 @@ const moreFiltersGuideImg = computed(() => {
             /></span>
           </template>
           <div class="ETD">
-            <div class="ETD_title">Incoterms</div>
+            <div class="ETD_title">{{ t('common.incoterms') }}</div>
             <SelectValue
-              title="Incoterms"
+              :title="t('common.incoterms')"
               keyValue="incoterms"
               ref="incotermsRef"
               :transportListData="props.incotermsList"
             ></SelectValue>
           </div>
           <div class="ETA">
-            <div class="ETD_title">Service</div>
+            <div class="ETD_title">{{ t('common.service') }}</div>
             <SelectValue
-              title="Service"
+              :title="t('common.service')"
               keyValue="service"
               ref="serviceRef"
               :transportListData="props.serviceList"
@@ -257,7 +274,7 @@ const moreFiltersGuideImg = computed(() => {
         <el-collapse-item class="collapse_item" name="Parties">
           <template #title>
             <span class="collapse-title"
-              >Parties
+              >{{ t('common.parties') }}
               <el-badge
                 v-if="partiesBadgeCount && !collapseValue?.includes('Parties')"
                 :value="partiesBadgeCount"
@@ -271,7 +288,7 @@ const moreFiltersGuideImg = computed(() => {
         <el-collapse-item class="collapse_item" name="Places">
           <template #title
             ><span class="collapse-title"
-              >Places<el-badge
+              >{{ t('common.places') }}<el-badge
                 class="mark"
                 v-if="placesBadgeCount && !collapseValue?.includes('Places')"
                 :value="placesBadgeCount"
@@ -283,7 +300,7 @@ const moreFiltersGuideImg = computed(() => {
         <el-collapse-item class="collapse_item" name="Transportation">
           <template #title
             ><span class="collapse-title"
-              >Transportation
+              >{{ t('common.transportation') }}
               <el-badge
                 class="mark"
                 v-if="transportationBadgeCount && !collapseValue?.includes('Transportation')"
@@ -291,13 +308,13 @@ const moreFiltersGuideImg = computed(() => {
                 type="warning" /></span
           ></template>
           <div class="ETD" v-for="item in transportaionData" :key="item.title">
-            <div class="ETD_title">{{ item.title }}</div>
+            <div class="ETD_title">{{ getTransportationLabel(item.title) }}</div>
             <AutoSelect
               :type="item.type"
               :title="item.title"
               @changeAutoSelect="changeTransportationData"
               :data="item.value"
-              :placeholder="item.placeholder"
+              :placeholder="getTransportationPlaceholder(item.title)"
             >
             </AutoSelect>
           </div>
@@ -306,11 +323,11 @@ const moreFiltersGuideImg = computed(() => {
       <div class="more_bottom">
         <el-button class="reset" type="default" @click="clearrest">
           <span class="font_family icon-icon_reset_b" style="padding-right: 4px"></span>
-          Reset
+          {{ t('common.reset') }}
         </el-button>
         <el-button class="reset el-button--dark" style="margin-left: 8px" @click="handleSearch">
           <span class="font_family icon-icon_search_b" style="padding-right: 4px"></span>
-          Search
+          {{ t('common.search') }}
         </el-button>
       </div>
     </el-drawer>

+ 28 - 4
src/components/MoreFilters/src/components/PartiesView.vue

@@ -1,9 +1,11 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
 import { useFiltersStore } from '@/stores/modules/filtersList'
 import { useRoute } from 'vue-router'
 
 const route = useRoute()
 const searchMode = route.path.includes('booking') ? 'booking' : 'tracking'
+const { t } = useI18n()
 
 const filtersStore = useFiltersStore()
 const filtersList = computed(() => filtersStore.filtersList)
@@ -24,6 +26,20 @@ const pageData = ref([
     placeholder: 'Please input consignee name'
   }
 ])
+const getPartyLabel = (title: string) => {
+  const map = {
+    'Shipper Name': t('moreFilters.shipperName'),
+    'Consignee Name': t('moreFilters.consigneeName'),
+    'Origin Agent': t('moreFilters.originAgent'),
+    'Destination Agent': t('moreFilters.destinationAgent'),
+    Sales: t('moreFilters.sales'),
+    'Notify Party': t('moreFilters.notifyParty'),
+    'Bill to': t('moreFilters.billTo'),
+    'Destination Operator': t('moreFilters.destinationOperator'),
+    'Controlling Customer': t('moreFilters.controllingCustomer')
+  }
+  return map[title] || title
+}
 const changeAutoSelect = (data: any, title: string) => {
   const index = pageData.value.findIndex((item: any) => item.title === title)
   pageData.value[index].value = data
@@ -188,12 +204,18 @@ defineExpose({
 <template>
   <div>
     <div class="ETD" v-for="item in pageData" :key="item.title">
-      <div class="ETD_title">{{ item.title }}</div>
+      <div class="ETD_title">{{ getPartyLabel(item.title) }}</div>
       <AutoSelect
         :type="item.type"
         :title="item.title"
         :data="item.value"
-        :placeholder="item.placeholder"
+        :placeholder="
+          t(
+            item.title === 'Shipper Name'
+              ? 'moreFilters.inputShipperName'
+              : 'moreFilters.inputConsigneeName'
+          )
+        "
         @changeAutoSelect="changeAutoSelect"
       >
       </AutoSelect>
@@ -205,11 +227,13 @@ defineExpose({
       @deleteItem="deleteItem"
       @changeTitle="changeTitle"
       @changeValue="changeValue"
-      placeholder="Please input party name"
+      :placeholder="t('moreFilters.inputPartyName')"
     >
     </SelectAutoSelect>
     <div class="MoreType" @click="addType" v-if="partyTypeOptions.length != moreList.length">
-      <el-button class="el-button--noborder moretype">+ More Party Type</el-button>
+      <el-button class="el-button--noborder moretype"
+        >+ {{ t('moreFilters.morePartyType') }}</el-button
+      >
     </div>
   </div>
 </template>

+ 18 - 3
src/components/MoreFilters/src/components/PlacesView.vue

@@ -1,10 +1,12 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
 import { useFiltersStore } from '@/stores/modules/filtersList'
 import { cloneDeep } from 'lodash'
 import { useRoute } from 'vue-router'
 
 const route = useRoute()
 const searchMode = route.path.includes('booking') ? 'booking' : 'tracking'
+const { t } = useI18n()
 
 const filtersStore = useFiltersStore()
 const filtersList = computed(() => filtersStore.filtersList)
@@ -41,6 +43,17 @@ const placeTypeOptions = ref([
     type: 'uncode'
   }
 ])
+const getPlaceLabel = (title: string) => {
+  const map = {
+    Origin: t('moreFilters.origin'),
+    Destination: t('moreFilters.destination'),
+    'Place of delivery': t('moreFilters.placeOfDelivery'),
+    ' Place of Receipt': t('moreFilters.placeOfReceipt'),
+    'Port of Loading': t('moreFilters.portOfLoading'),
+    'Place of Discharge': t('moreFilters.placeOfDischarge')
+  }
+  return map[title] || title
+}
 if (searchMode === 'tracking') {
   placeTypeOptions.value.push({
     title: 'Place of Discharge',
@@ -179,7 +192,7 @@ defineExpose({
 
 <template>
   <div class="ETD" v-for="item in pageData">
-    <div class="ETD_title">{{ item.title }}</div>
+    <div class="ETD_title">{{ getPlaceLabel(item.title) }}</div>
     <SelectTable :title="item.title" :data="item.value" @input="changePageData" />
   </div>
   <SelectTableSelect
@@ -188,12 +201,14 @@ defineExpose({
     @changeData="changeData"
     @changeTitle="changeTitle"
     @deleteType="deleteType"
-    placeholder="Please input places name"
+    :placeholder="t('moreFilters.inputPlacesName')"
   >
   </SelectTableSelect>
   <!-- More Place Type -->
   <div class="MoreType" @click="addType()" v-if="moreList.length !== placeTypeOptions.length">
-    <el-button class="el-button--noborder moretype">+ More Place Type</el-button>
+    <el-button class="el-button--noborder moretype"
+      >+ {{ t('moreFilters.morePlaceType') }}</el-button
+    >
   </div>
 </template>
 

+ 23 - 8
src/components/MoreFilters/src/components/SelectValue.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import type { DropdownInstance } from 'element-plus'
 import { cloneDeep } from 'lodash'
 import { useFiltersStore } from '@/stores/modules/filtersList'
@@ -33,6 +36,18 @@ watch(
 )
 
 const dropdown1 = ref<DropdownInstance>()
+const displayTitle = computed(() => {
+  const map = {
+    Incoterms: t('common.incoterms'),
+    Service: t('common.service'),
+    Vessel: t('moreFilters.vessel'),
+    'Voyage/Flight': t('moreFilters.voyageFlight')
+  }
+  return map[props.title] || props.title
+})
+const displaySelectData = computed(() => {
+  return selectData.value === 'All' ? t('common.all') : selectData.value
+})
 
 const handleCheckAll = (val: any) => {
   transportList.value.forEach((item: any) => {
@@ -153,12 +168,12 @@ defineExpose({
     >
       <div class="el-dropdown-link">
         <div v-if="props.title == 'Incoterms' && !selectData" class="select_title">
-          Please Select Date Range
+          {{ t('common.pleaseSelectDateRange') }}
         </div>
         <div v-else-if="props.title == 'Service' && !selectData" class="select_title">
-          Please Select Service
+          {{ t('common.pleaseSelectService') }}
         </div>
-        <div v-else class="select_title_2">{{ selectData }}</div>
+        <div v-else class="select_title_2">{{ displaySelectData }}</div>
         <span class="iconfont_icon">
           <svg class="iconfont icon_dark" aria-hidden="true">
             <use xlink:href="#icon-icon_dropdown_b"></use>
@@ -167,11 +182,11 @@ defineExpose({
       </div>
       <template #dropdown>
         <div class="dropdownwidth">
-          <div class="title" style="margin-bottom: 3px">{{ props.title }}</div>
+          <div class="title" style="margin-bottom: 3px">{{ displayTitle }}</div>
           <el-dropdown-menu>
             <el-dropdown-item>
               <el-checkbox v-model="checkAll" class="checkbox" @change="handleCheckAll">
-                <div class="checkbox_title">Select All</div>
+                <div class="checkbox_title">{{ t('common.selectAll') }}</div>
               </el-checkbox>
             </el-dropdown-item>
             <el-divider style="border-color: var(--input-border)"></el-divider>
@@ -195,14 +210,14 @@ defineExpose({
                   <use xlink:href="#icon-icon_dropdown__line_b"></use>
                 </svg>
               </span>
-              See All
+              {{ t('common.seeAll') }}
             </div>
             <div class="transport_bottom">
               <div>
-                <el-button class="clear" type="default" @click="clearList">Reset</el-button>
+                <el-button class="clear" type="default" @click="clearList">{{ t('common.reset') }}</el-button>
               </div>
               <div>
-                <el-button class="search el-button--dark" @click="TransportSearch">OK</el-button>
+                <el-button class="search el-button--dark" @click="TransportSearch">{{ t('common.ok') }}</el-button>
               </div>
             </div>
           </el-dropdown-menu>

+ 5 - 2
src/components/NotificationMessageCard/src/NotificationMessageCard.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import EventCard from './components/EventCard.vue'
 import PasswordCard from './components/PasswordCard.vue'
 import FeatureUpdateCard from './components/FeatureUpdateCard.vue'
@@ -280,9 +283,9 @@ defineExpose({
         (pageData?.[0]?.notificationType !== 'feature' && pageData?.length > 0) || props.isDrawer
       "
     >
-      <el-divider v-if="loading"> loading... </el-divider>
+      <el-divider v-if="loading"> {{ t('common.loading') }} </el-divider>
       <el-divider v-if="finished && pageData.length > 0">
-        Only display the message data within three months
+        {{ t('systemMessage.onlyDisplayWithinThreeMonths') }}
       </el-divider>
     </div>
   </div>

+ 19 - 12
src/components/NotificationMessageCard/src/components/EventCard.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { transportationMode } from '@/components/transportationMode'
 import { useRouter } from 'vue-router'
 import { getTimezone } from '@/utils/tools'
@@ -80,7 +83,7 @@ const jumpTracking = (data: EventCardPropsData) => {
         class="more-tips"
         v-if="(data.type === 'milestone' || data.type === 'container') && data.numericRecords"
       >
-        <span>Latest Status Updates ({{ data.numericRecords }}) </span>
+        <span>{{ t('systemMessage.latestStatusUpdates', { count: data.numericRecords }) }} </span>
         <el-button
           @click="
             handleSeeAll(data, {
@@ -89,24 +92,28 @@ const jumpTracking = (data: EventCardPropsData) => {
           "
           class="see-all-icon el-button--text"
         >
-          See All
+          {{ t('common.seeAll') }}
           <span class="font_family icon-icon_next_b"></span>
         </el-button>
       </div>
       <div class="more-tips" v-if="data.info?.etdOrdeparturNum || data.info?.etaOrarrivalNum">
         <div>
           <span v-if="data.info?.etdOrdeparturNum"
-            >{{ data.type === 'delay' ? 'Departure Delay' : 'ETD Change' }} ({{
-              data.info?.etdOrdeparturNum
-            }})</span
+            >{{
+              data.type === 'delay'
+                ? t('systemMessage.departureDelay')
+                : t('systemMessage.etdChange')
+            }}
+            ({{ data.info?.etdOrdeparturNum }})</span
           >
           <span v-if="data.info?.etdOrdeparturNum && data.info?.etaOrarrivalNum">
             &nbsp;&nbsp;|&nbsp;&nbsp;</span
           >
           <span v-if="data.info?.etaOrarrivalNum">
-            {{ data.type === 'delay' ? 'Arrival Delay' : 'ETA Change' }} ({{
-              data.info?.etaOrarrivalNum
-            }})
+            {{
+              data.type === 'delay' ? t('systemMessage.arrivalDelay') : t('systemMessage.etaChange')
+            }}
+            ({{ data.info?.etaOrarrivalNum }})
           </span>
         </div>
         <el-button
@@ -118,7 +125,7 @@ const jumpTracking = (data: EventCardPropsData) => {
           "
           class="see-all-icon el-button--text"
         >
-          See All
+          {{ t('common.seeAll') }}
           <span class="font_family icon-icon_next_b"></span>
         </el-button>
       </div>
@@ -134,7 +141,7 @@ const jumpTracking = (data: EventCardPropsData) => {
         <!-- container类型显示图标 -->
         <div v-else>
           <span class="font_family icon-icon_container__filled_b"></span>
-          <span class="no">Container: {{ data.no }}</span>
+          <span class="no">{{ t('common.container') }}: {{ data.no }}</span>
         </div>
         <div class="tag" :class="{ delay: data.type === 'delay', change: data.type === 'change' }">
           <span class="dot"></span>
@@ -143,7 +150,7 @@ const jumpTracking = (data: EventCardPropsData) => {
       </div>
       <div class="route" v-if="data?.info?.route?.length > 0">
         <span class="font_family icon-icon_route_b"></span>
-        <span>Route:&nbsp;</span>
+        <span>{{ t('systemMessage.route') }}:&nbsp;</span>
         <template v-for="(item, index) in data.info.route" :key="index">
           <span>{{ item }}</span
           ><span style="margin: 0 3px" v-if="index !== data.info.route.length - 1">→</span>
@@ -152,7 +159,7 @@ const jumpTracking = (data: EventCardPropsData) => {
       <!-- change多程情况中的Leg-->
       <div class="location" v-if="data?.info?.leg?.length > 0">
         <span class="font_family icon-icon_location_b"></span>
-        <span>Current Leg:&nbsp;</span>
+        <span>{{ t('systemMessage.currentLeg') }}:&nbsp;</span>
         <template v-for="(item, index) in data.info.leg" :key="index">
           <span>{{ item }}</span
           ><span style="margin: 0 3px" v-if="index !== data.info.leg.length - 1">→</span>

+ 6 - 3
src/components/NotificationMessageCard/src/components/FeatureUpdateCard.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { useRouter } from 'vue-router'
 
 const router = useRouter()
@@ -34,9 +37,9 @@ const handleViewMore = () => {
 
 const handleContent = (header) => {
   if (header === 'New Feature: AI Smart Assistant') {
-    return 'Smart Assistant is live! Ask in natural language, get real-time shipment data with multi-language support.'
-  }else {
-    return 'Smart Notification is here! Four key event alerts with customizable rules and multi-channel delivery.'
+    return t('systemMessage.smartAssistantContent')
+  } else {
+    return t('systemMessage.smartNotificationContent')
   }
 }
 </script>

+ 3 - 1
src/components/NotificationMessageCard/src/components/PasswordCard.vue

@@ -1,5 +1,7 @@
 <script setup lang="ts">
 import ChangePasswordDialog from '@/views/Layout/src/components/Header/components/ChangePasswordDialog.vue'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 
 interface PasswordCardPropsData {
   title: string
@@ -35,7 +37,7 @@ const handleChangePassword = () => {
       <div class="change-btn" style="text-align: center">
         <el-button @click="handleChangePassword" class="el-button--main" style="height: 40px">
           <span class="font_family icon-icon_edit_b" style="margin-right: 4px"></span>
-          <span>Change Password</span></el-button
+          <span>{{ t('login.changePassword') }}</span></el-button
         >
       </div>
     </div>

+ 4 - 2
src/components/ScoringGrade/components/DialogColorful.vue

@@ -1,8 +1,10 @@
 <script setup lang="ts">
 import { ref } from 'vue'
 import { useThemeStore } from '@/stores/modules/theme'
+import { useI18n } from 'vue-i18n'
 import BubbleLight from '../image/bubble_corner_colorful.png'
 import BubbleDark from '../image/bubble_corner_colorful_darkmode.png'
+const { t } = useI18n()
 const props = defineProps({
   colorfulSrc: String,
   isshowexpression: Boolean,
@@ -33,11 +35,11 @@ const themeStore = useThemeStore()
           style="width: 554px"
           :rows="4"
           type="textarea"
-          placeholder="We look forward to hearing from you. Thank you for helping to build a better customer system."
+          :placeholder="t('scoringGrade.feedbackPlaceholder')"
         />
         <div class="button_submit">
           <el-button class="submit_button el-button--dark" @click="submitDetails(inputdetails)"
-            >Submit</el-button
+            >{{ t('common.submit') }}</el-button
           >
         </div>
       </div>

+ 21 - 4
src/components/ScoringGrade/components/DialogUe.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import { ref } from 'vue'
+import { useI18n } from 'vue-i18n'
 import { useThemeStore } from '@/stores/modules/theme'
 import BubbleLight from '../image/bubble_corner.png'
 import BubbleDark from '../image/bubble_corner_darkmode.png'
@@ -13,6 +14,7 @@ const props = defineProps({
 })
 const checkboxGroup1 = ref([])
 const checkboxGroup2 = ref([])
+const { t } = useI18n()
 const evaluate = [
   'Functionality',
   'Data accuracy',
@@ -20,6 +22,21 @@ const evaluate = [
   'Ease of use',
   'Website performance'
 ]
+const getAspectLabel = (key: string) => {
+  const map = {
+    Functionality: t('scoringGrade.aspectFunctionality'),
+    'Data accuracy': t('scoringGrade.aspectDataAccuracy'),
+    'Look and feel': t('scoringGrade.aspectLookAndFeel'),
+    'Ease of use': t('scoringGrade.aspectEaseOfUse'),
+    'Website performance': t('scoringGrade.aspectWebsitePerformance'),
+    'Highly Dissatisfied': t('scoringGrade.highlyDissatisfied'),
+    Dissatisfied: t('scoringGrade.dissatisfied'),
+    Neutral: t('scoringGrade.neutral'),
+    Satisfied: t('scoringGrade.satisfied'),
+    'Highly Satisfied': t('scoringGrade.highlySatisfied')
+  }
+  return map[key] || key
+}
 const happyevaluate = [
   'Functionality',
   'Data accuracy',
@@ -94,7 +111,7 @@ const themeStore = useThemeStore()
             :key="item"
             :value="item"
           >
-            {{ item }}
+            {{ getAspectLabel(item) }}
           </el-checkbox-button>
         </el-checkbox-group>
       </div>
@@ -106,7 +123,7 @@ const themeStore = useThemeStore()
             :key="item"
             :value="item"
           >
-            {{ item }}
+            {{ getAspectLabel(item) }}
           </el-checkbox-button>
         </el-checkbox-group>
       </div>
@@ -115,12 +132,12 @@ const themeStore = useThemeStore()
           <div class="smile_title_left"></div>
           <div class="smile_title_right">
             <div class="smile_title" v-for="item in Aspects" :key="item">
-              {{ item }}
+              {{ getAspectLabel(item) }}
             </div>
           </div>
         </div>
         <div class="smile_content_flex" v-for="(item, index) in smileAspects" :key="index">
-          <div class="smile_title_left content_left">{{ item.title }}</div>
+          <div class="smile_title_left content_left">{{ getAspectLabel(item.title) }}</div>
           <div class="smile_title_right content_right">
             <el-radio-group v-model="item.radio" @change="changeSmileRadio(item.title, item.radio)">
               <el-radio

+ 23 - 21
src/components/ScoringGrade/src/ScoringGrade.vue

@@ -16,8 +16,10 @@ import normalPng from '../image/score_normal.png'
 import submitsucessful from '../image/submit_successful.png'
 import { useUserStore } from '@/stores/modules/user'
 import emitter from '@/utils/bus'
+import { useI18n } from 'vue-i18n'
 
 const userStore = useUserStore()
+const { t } = useI18n()
 
 // const isShow = ref(true)
 const visible = ref(false)
@@ -50,41 +52,41 @@ avater.value = [
     src: angryPng,
     src1: angryPng,
     itemsrc: angryPng2,
-    itemtext: 'We are so sorry for the inconveniences. We value your experience immensely. ',
+    itemtext: t('scoringGrade.apologyMessage'),
     expression: 'angry',
-    proposal: 'Could you please tell us which aspects of the system you are dissatisfied with?'
+    proposal: t('scoringGrade.dissatisfiedAspectQuestion')
   },
   {
     src: sadPng,
     src1: sadPng,
     itemsrc: sadPng2,
-    itemtext: 'We are so sorry for the inconveniences. We value your experience immensely. ',
+    itemtext: t('scoringGrade.apologyMessage'),
     expression: 'sad',
-    proposal: 'Could you please tell us which aspects of the system you are dissatisfied with?'
+    proposal: t('scoringGrade.dissatisfiedAspectQuestion')
   },
   {
     src: smilePng,
     src1: smilePng,
     itemsrc: smilePng2,
-    itemtext: 'Thank you for sharing your thoughts with us. We value your experience greatly.',
+    itemtext: t('scoringGrade.thanksForSharing'),
     expression: 'smile',
-    proposal: 'Could you share what aspects you liked and what could be improved ?'
+    proposal: t('scoringGrade.shareLikedAndImprovement')
   },
   {
     src: hhhPng,
     src1: hhhPng,
     itemsrc: hhhPng2,
-    itemtext: 'Thank you very much for giving us a satisfied rating on our service or system.',
+    itemtext: t('scoringGrade.thanksForSatisfiedRating'),
     expression: 'hhh',
-    proposal: 'We are curious to learn more about what specifically made you feel satisfied. '
+    proposal: t('scoringGrade.whatMadeYouSatisfied')
   },
   {
     src: happyPng,
     src1: happyPng,
     itemsrc: happyPng2,
-    itemtext: 'Thank you very much for giving us a satisfied rating on our service or system.',
+    itemtext: t('scoringGrade.thanksForSatisfiedRating'),
     expression: 'happy',
-    proposal: 'We are curious to learn more about what specifically made you feel satisfied. '
+    proposal: t('scoringGrade.whatMadeYouSatisfied')
   }
 ]
 const dialogVisible = ref(false)
@@ -105,35 +107,35 @@ const showScore = (item: any) => {
     isLoaded.value = true
     dialogcontent.value = item.itemtext
     if (item.expression == 'angry') {
-      SubmitText.value = 'Apologize once again for your experience.'
+      SubmitText.value = t('scoringGrade.apologizeAgain')
       checkexpression.value = 'angry'
       setTimeout(() => {
         isShowAngry.value = true
         angryproposal.value = item.proposal
       }, 1000)
     } else if (item.expression == 'sad') {
-      SubmitText.value = 'Apologize once again for your experience.'
+      SubmitText.value = t('scoringGrade.apologizeAgain')
       checkexpression.value = 'sad'
       setTimeout(() => {
         isShowAngry.value = true
         angryproposal.value = item.proposal
       }, 1000)
     } else if (item.expression == 'smile') {
-      SubmitText.value = 'We greatly appreciate your valuable time and feedback.'
+      SubmitText.value = t('scoringGrade.appreciateFeedback')
       checkexpression.value = 'smile'
       setTimeout(() => {
         isShowSmile.value = true
         smileproposal.value = item.proposal
       }, 1000)
     } else if (item.expression == 'hhh') {
-      SubmitText.value = 'Once again, thank you for your positive evaluation.'
+      SubmitText.value = t('scoringGrade.thanksPositiveEvaluation')
       checkexpression.value = 'hhh'
       setTimeout(() => {
         isShowHappy.value = true
         angryproposal.value = item.proposal
       }, 1000)
     } else if (item.expression == 'happy') {
-      SubmitText.value = 'Once again, thank you for your positive evaluation.'
+      SubmitText.value = t('scoringGrade.thanksPositiveEvaluation')
       checkexpression.value = 'happy'
       setTimeout(() => {
         isShowHappy.value = true
@@ -309,7 +311,7 @@ onMounted(() => {
           v-for="(item, index) in avater"
           :key="index"
           popper-class="avater_popver"
-          content="How satisfied are you with this system?"
+          :content="t('scoringGrade.satisfactionQuestion')"
         >
           <template #reference>
             <div class="score">
@@ -328,7 +330,7 @@ onMounted(() => {
     </el-popover>
     <el-dialog
       v-model="dialogVisible"
-      title="Hi!"
+      :title="t('scoringGrade.hi')"
       :destroy-on-close="true"
       @close="closeDialog"
       width="640"
@@ -339,7 +341,7 @@ onMounted(() => {
       :close-on-click-modal="false"
     >
       <div v-if="isshowDetails">
-        <DialogUe content="How satisfied are you with this system ?" dialogWidth="298px"></DialogUe>
+        <DialogUe :content="t('scoringGrade.satisfactionQuestionWithSpace')" dialogWidth="298px"></DialogUe>
         <DialogColorful
           :colorfulSrc="colorfulSrc"
           :isshowexpression="isshowexpression"
@@ -373,7 +375,7 @@ onMounted(() => {
         </transition>
         <transition name="fade" v-if="isLoaded_share">
           <DialogUe
-            content="Would you like to share more details with us?"
+            :content="t('scoringGrade.shareMoreDetails')"
             dialogWidth="334px"
           ></DialogUe>
         </transition>
@@ -388,7 +390,7 @@ onMounted(() => {
         class="result"
         v-if="isshowDetails_submit"
         icon="success"
-        title="Submit Successful"
+        :title="t('scoringGrade.submitSuccessful')"
       >
         <template #icon>
           <el-image :src="submitsucessful" />
@@ -396,7 +398,7 @@ onMounted(() => {
         <template #sub-title>
           <div class="sub_title_text">{{ SubmitText }}</div>
           <div class="sub_title_text">
-            We are committed to working hard to provide better services.
+            {{ t('scoringGrade.betterServiceCommitment') }}
           </div>
         </template>
       </el-result>

+ 3 - 1
src/components/SeeAllIcon/src/SeeAllIcon.vue

@@ -1,4 +1,6 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 const isCollapse = defineModel<boolean>()
 const emits = defineEmits<{ collapse: [boolean] }>()
 const handleSeeAll = () => {
@@ -12,7 +14,7 @@ const handleSeeAll = () => {
 
 <template>
   <div class="see-all-icon" @click="handleSeeAll">
-    <span class="btn">See All</span>
+    <span class="btn">{{ t('common.seeAll') }}</span>
     <span
       class="icon font_family icon-icon_dropdown__line_b"
       :class="{ 'is-rotate': isCollapse }"

+ 9 - 7
src/components/SelectTable/src/SelectTable.vue

@@ -1,4 +1,5 @@
 <script setup lang="ts" name="SelTable">
+import { useI18n } from 'vue-i18n'
 import { ref, reactive, watch, onUnmounted, nextTick } from 'vue'
 import { ElMessage } from 'element-plus'
 import { formatNumber } from '@/utils/tools'
@@ -8,6 +9,7 @@ import { useRoute } from 'vue-router'
 import axios from 'axios'
 
 const filtersStore = useFiltersStore()
+const { t } = useI18n()
 const route = useRoute()
 const searchMode = route.path.includes('booking') ? 'booking' : 'tracking'
 
@@ -84,7 +86,7 @@ const fetchData = async (query: string, page: number, isPageChange = false) => {
     }
   } catch (err) {
     if (!axios.isCancel(err) && !searchAbortController?.signal.aborted) {
-      ElMessage.error('Failed to load data')
+      ElMessage.error(t('common.failedToLoadData'))
       state.tableData = []
       state.total = 0
     }
@@ -120,7 +122,7 @@ const handlePageChange = () => {
 const handleRowClick = (row: any) => {
   const keyVal = row[props.needKey]
   if (innerTags.value.includes(keyVal)) {
-    ElMessage.success('Cannot add duplicate cities.')
+    ElMessage.success(t('common.cannotAddDuplicateCities'))
     return
   }
 
@@ -230,7 +232,7 @@ onUnmounted(() => {
               <input
                 v-model="searchVal"
                 ref="tagInputRef"
-                :placeholder="innerTags.length ? '' : 'Please input country/city/uncode'"
+                :placeholder="innerTags.length ? '' : t('common.inputCountryCityUncode')"
                 class="el-input__inner"
                 :disabled="props.disabled"
                 type="text"
@@ -260,14 +262,14 @@ onUnmounted(() => {
         header-row-class-name="cus-header"
         style="width: 100%"
       >
-        <el-table-column property="country" label="Country" width="75" />
-        <el-table-column property="city" label="City" />
-        <el-table-column property="uncode" label="Uncode" width="80" />
+        <el-table-column property="country" :label="t('common.country')" width="75" />
+        <el-table-column property="city" :label="t('common.city')" />
+        <el-table-column property="uncode" :label="t('common.uncode')" width="80" />
       </el-table>
 
       <!-- 分页 -->
       <div class="pagination">
-        <span>Total {{ formatNumber(state.total) }}</span>
+        <span>{{ t('common.total') }} {{ formatNumber(state.total) }}</span>
         <el-pagination
           v-model:currentPage="state.currentPage"
           v-model:page-size="state.pageSize"

+ 7 - 4
src/components/SelectTableSelect/src/SelectTableSelect.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { ref, watch } from 'vue'
 import IconDropDown from '@/components/IconDropDown'
 import SelectTable from '@/components/SelectTable'
@@ -67,7 +70,7 @@ const changePageData = (data: any, title: string) => {
   <div class="AddType" v-for="item in pageData" :key="item.id">
     <div>
       <div class="Date_Title">
-        <div class="ETD_title">Places Type</div>
+        <div class="ETD_title">{{ t('common.placesType') }}</div>
         <span class="iconfont_icon" @click="deleteType(item.id)">
           <svg class="iconfont icon_delete" aria-hidden="true">
             <use xlink:href="#icon-icon_delete_b"></use>
@@ -78,7 +81,7 @@ const changePageData = (data: any, title: string) => {
         :model-value="item.title"
         :suffix-icon="IconDropDown"
         @change="changeTitle($event, item)"
-        placeholder="Please Select Party Type"
+        :placeholder="t('common.pleaseSelectPartyType')"
       >
         <el-option
           v-for="type in getAvailableOptions(item.title)"
@@ -90,7 +93,7 @@ const changePageData = (data: any, title: string) => {
       </el-select>
     </div>
     <div style="margin-top: 16px">
-      <div class="ETD_title">Places Details</div>
+      <div class="ETD_title">{{ t('common.placesDetails') }}</div>
       <SelectTable
         ref="selectTableRef"
         :title="item.title"
@@ -101,7 +104,7 @@ const changePageData = (data: any, title: string) => {
       />
 
       <div class="error" v-if="item.title != '' && item.value.length === 0">
-        Please Input Places Details
+        {{ t('common.pleaseInputPlacesDetails') }}
       </div>
     </div>
   </div>

+ 3 - 1
src/components/ShipmentStatus/src/ShipmentStatus.vue

@@ -1,6 +1,8 @@
 <script setup lang="ts">
 import { useOverflow } from '@/hooks/useOverflow'
 import { formatTimezone } from '@/utils/tools'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 
 const props = defineProps({
   data: Object
@@ -161,7 +163,7 @@ const { isOverflow: isPathOverflow } = useOverflow(pathRef, props)
               </div>
               <div class="bottom">
                 <span class="font_family icon-icon_container_b"></span>
-                <div style="margin-top: 1px; margin-left: 4px">Container:</div>
+                <div style="margin-top: 1px; margin-left: 4px">{{ t('common.container') }}:</div>
                 <div style="margin-left: 4px; margin-top: 3px; font-weight: 700">
                   {{ deliveredItem.container }}
                 </div>

+ 3 - 1
src/components/TableImgEmpty/TableImgEmpty.vue

@@ -1,10 +1,12 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
 import { computed } from 'vue'
 import lightPng from './image/empty-light.png'
 import darkPng from './image/empty-dark.png'
 import { useThemeStore } from '@/stores/modules/theme'
 
 const themeStore = useThemeStore()
+const { t } = useI18n()
 // 判断当前系统主题模式
 const emptyImg = computed(() => {
   return themeStore.theme === 'dark' ? darkPng : lightPng
@@ -18,7 +20,7 @@ const props = defineProps({
 <template>
   <div class="table-img-empty">
     <img :src="emptyImg" />
-    <div class="empty-text">No data</div>
+    <div class="empty-text">{{ t('common.noData') }}</div>
   </div>
 </template>
 

+ 11 - 8
src/components/TransportMode/src/TransportMode.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import type { DropdownInstance } from 'element-plus'
 import { cloneDeep } from 'lodash'
 import { useFiltersStore } from '@/stores/modules/filtersList'
@@ -95,7 +98,7 @@ const handleDropdownVisibleChange = (visible: boolean) => {
       @visible-change="handleDropdownVisibleChange"
     >
       <div class="el-dropdown-link">
-        <div class="select_title">Transport Mode</div>
+        <div class="select_title">{{ t('common.transportMode') }}</div>
         <span class="iconfont_icon">
           <svg class="iconfont icon_dark" aria-hidden="true">
             <use xlink:href="#icon-icon_dropdown_b"></use>
@@ -104,7 +107,7 @@ const handleDropdownVisibleChange = (visible: boolean) => {
       </div>
       <template #dropdown>
         <div class="dropdownwidth">
-          <div class="title">Transport Mode</div>
+          <div class="title">{{ t('common.transportMode') }}</div>
           <el-dropdown-menu>
             <el-dropdown-item>
               <el-checkbox v-model="checkAll" class="checkbox" @change="handleCheckAllChange">
@@ -114,7 +117,7 @@ const handleDropdownVisibleChange = (visible: boolean) => {
                       <use xlink:href="#icon-icon_all_b"></use>
                     </svg>
                   </span>
-                  Select All
+                  {{ t('common.selectAll') }}
                 </div>
                 <div class="checkbox_number">({{ totalNumber }})</div>
               </el-checkbox>
@@ -133,19 +136,19 @@ const handleDropdownVisibleChange = (visible: boolean) => {
                       <use :xlink:href="item.icon"></use>
                     </svg>
                   </span>
-                  {{ item.name }}
+                  {{ item.name === 'All' ? t('common.all') : item.name }}
                 </div>
                 <div class="checkbox_number">({{ item.number }})</div>
               </el-checkbox>
             </el-dropdown-item>
             <div class="transport_bottom">
               <div>
-                <el-button class="clear" type="default" @click="clearList">Reset</el-button>
+                <el-button class="clear" type="default" @click="clearList">{{ t('common.reset') }}</el-button>
               </div>
               <div>
-                <el-button class="search el-button--dark" @click="transportSearch"
-                  >Search</el-button
-                >
+                <el-button class="search el-button--dark" @click="transportSearch">
+                  {{ t('common.search') }}
+                </el-button>
               </div>
             </div>
           </el-dropdown-menu>

+ 8 - 2
src/components/VBox/src/VBox.vue

@@ -1,4 +1,6 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 const props = withDefaults(
   defineProps<{
     id?: number
@@ -48,7 +50,7 @@ const vBoxPopoverRef = ref()
 <template>
   <div class="v-box">
     <div class="header">
-      <slot name="header">Title</slot>
+      <slot name="header">{{ t('common.title') }}</slot>
       <div class="option">
         <SeeAllIcon
           v-if="props.isSeeAll"
@@ -70,7 +72,11 @@ const vBoxPopoverRef = ref()
               :key="item.name"
             >
               <span class="font_family" :class="[`icon-${item.icon}`]"></span>
-              <span>{{ item.name }}</span>
+              <span>{{
+                t(
+                  `common.${item.id === 1 ? 'moveToTop' : item.id === 2 ? 'moveUp' : item.id === 3 ? 'moveDown' : 'moveToBottom'}`
+                )
+              }}</span>
             </div>
           </div>
           <template #reference v-if="props.isDraggable">

+ 3 - 1
src/components/VBox_Dashboard/src/VBox_Dashboard.vue

@@ -1,5 +1,7 @@
 <script setup lang="ts">
 import { ref } from 'vue'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 const props = withDefaults(
   defineProps<{
     id?: number
@@ -44,7 +46,7 @@ const vBoxPopoverRef = ref()
 <template>
   <div class="v-box">
     <div class="header">
-      <slot name="header">Title</slot>
+      <slot name="header">{{ t('common.title') }}</slot>
       <div class="option" style="width: 48px; height: 48px">
         <el-button
           type="text"

+ 9 - 7
src/components/VBreadcrumb/src/VBreadcrumb.vue

@@ -2,10 +2,12 @@
 import { useRouter } from 'vue-router'
 import { useBreadCrumb } from '@/stores/modules/breadCrumb'
 import { useThemeStore } from '@/stores/modules/theme'
+import { useI18n } from 'vue-i18n'
 
 const router = useRouter()
 const breadCrumb = useBreadCrumb()
 const themeStore = useThemeStore()
+const { t } = useI18n()
 const CancelRulesVisible = ref(false)
 
 const handleGoBack = () => {
@@ -67,15 +69,15 @@ const jumpLinkMonitoring = () => {
   <div v-else></div>
   <!-- 取消保存 -->
   <el-dialog v-model="CancelRulesVisible" width="480">
-    <div style="font-weight: 400">You have unsaved changes.</div>
-    <div style="font-weight: 400">Are you sure you want to leave this page?</div>
+    <div style="font-weight: 400">{{ t('destinationDelivery.unsavedChanges') }}</div>
+    <div style="font-weight: 400">{{ t('destinationDelivery.confirmLeavePage') }}</div>
     <template #footer>
       <div class="dialog-footer">
-        <el-button type="default" @click="CancelRulesVisible = false" style="width: 100px"
-          >Cancel</el-button
-        >
+        <el-button type="default" @click="CancelRulesVisible = false" style="width: 100px">{{
+          t('common.cancel')
+        }}</el-button>
         <el-button class="el-button--warning" @click="jumpLinkMonitoring" style="width: 100px">
-          OK
+          {{ t('common.ok') }}
         </el-button>
       </div>
     </template>
@@ -86,7 +88,7 @@ const jumpLinkMonitoring = () => {
             <use xlink:href="#icon-icon_tipsfilled_b"></use>
           </svg>
         </span>
-        Unsaved Changes
+        {{ t('destinationDelivery.unsavedChangesTitle') }}
       </div>
     </template>
   </el-dialog>

+ 5 - 2
src/components/VDriverGuide/src/VDriverGuide.vue

@@ -1,10 +1,13 @@
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
+</script>
 
 <template>
   <div id="page-guide-btn-guide" class="driver-guide-btn">
     <img src="./img/guide-icon.png" alt="" />
     <span style="margin-top: 2px; font-size: 12px; font-style: italic; color: var(--color-theme)">
-      Page Guide
+      {{ t('common.pageGuide') }}
     </span>
   </div>
 </template>

+ 5 - 3
src/components/VEmpty/src/VEmpty.vue

@@ -2,8 +2,10 @@
 import lightPng from './images/default_image.png'
 import darkPng from './images/default_dark_image.png'
 import { useThemeStore } from '@/stores/modules/theme'
+import { useI18n } from 'vue-i18n'
 
 const themeStore = useThemeStore()
+const { t } = useI18n()
 // 判断当前系统主题模式
 const emptyImg = computed(() => {
   return themeStore.theme === 'dark' ? darkPng : lightPng
@@ -16,11 +18,11 @@ const emptyImg = computed(() => {
       <img :src="emptyImg" alt="" />
     </div>
     <p class="title">
-      <slot name="title">No Results Found</slot>
+      <slot name="title">{{ t('common.noResultsFound') }}</slot>
     </p>
     <slot name="result">
-      <p class="light">We didn't find any search results,</p>
-      <p class="light">please try to adjust your search keywords.</p>
+      <p class="light">{{ t('common.emptyResultLine1') }}</p>
+      <p class="light">{{ t('common.emptyResultLine2') }}</p>
     </slot>
     <div class="suggestion">
       <slot name="suggestion"></slot>

+ 3 - 1
src/components/VLoading/src/VLoading.vue

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
 /**
  *
  * @param {string} target - 挂载的目标元素
@@ -15,6 +16,7 @@ const props = withDefaults(defineProps<internalProps>(), {
   target: 'body',
   isLoadingBackground: false
 })
+const { t } = useI18n()
 </script>
 <template>
   <Teleport :to="props.target">
@@ -26,7 +28,7 @@ const props = withDefaults(defineProps<internalProps>(), {
       <div class="v-loading-spinner">
         <div>
           <img class="circular" src="./images/icon_loading.png" alt="" />
-          <p class="loading-text">Loading...</p>
+          <p class="loading-text">{{ t('common.loading') }}</p>
         </div>
       </div>
     </div>

+ 8 - 6
src/components/VSliderVerification/src/VSliderVerification.vue

@@ -1,4 +1,5 @@
 <script lang="ts" setup>
+import { useI18n } from 'vue-i18n'
 //滑动图片验证码部分
 import Img01 from './image/verification-img-1.png'
 import Img02 from './image/verification-img-2.png'
@@ -8,6 +9,7 @@ import { ref } from 'vue'
 //引入'vue3-puzzle-vcode'插件
 import Vcode from './components/SliderVerification.vue'
 // import Vcode from 'vue3-puzzle-vcode'
+const { t } = useI18n()
 
 const openDialog = () => {
   isShow.value = true
@@ -42,8 +44,8 @@ const addTipsNode = () => {
       <span class="font_family icon-icon_reject_b close-icon" style="margin-right: -20px;">
       </span>
     </div>
-    <p>Please drag the slider below to complete the</p>
-    <p>verification to ensure normal access</p>
+    <p>${t('common.sliderVerifyTipLine1')}</p>
+    <p>${t('common.sliderVerifyTipLine2')}</p>
   `
   const parentNode = document.querySelector('.vue-auth-box_')
   if (parentNode.firstChild) {
@@ -115,7 +117,7 @@ const onSuccess = () => {
     const observer = new MutationObserver((mutationsList) => {
       mutationsList.forEach((mutation) => {
         if (mutation.type === 'childList') {
-          if ((mutation.target as any).innerHTML === 'Verification successful') {
+          if ((mutation.target as any).innerHTML === t('common.verificationSuccessful')) {
             updateSliderBackground('success')
             addSliderBtnNode('success')
             setTimeout(() => {
@@ -159,10 +161,10 @@ defineExpose({
       @fail="fail"
       :canvasWidth="320"
       :canvasHeight="180"
-      sliderText="Swipe to verify"
+      :sliderText="t('common.swipeToVerify')"
       :sliderSize="38"
-      successText="Verification successful"
-      failText="Verification failed"
+      :successText="t('common.verificationSuccessful')"
+      :failText="t('common.verificationFailed')"
       :range="5"
       :imgs="[Img01, Img02, Img03, Img04]"
     ></Vcode>

+ 7 - 4
src/components/selectAutoSelect/src/selectAutoSelect.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { ref, watch, onUnmounted } from 'vue'
 import IconDropDown from '@/components/IconDropDown'
 import { cloneDeep, debounce } from 'lodash'
@@ -173,7 +176,7 @@ onUnmounted(() => {
   <div class="addType" v-for="item in pageData" :key="item.id">
     <div>
       <div class="Date_Title">
-        <div class="ETD_title">Party Type</div>
+        <div class="ETD_title">{{ t('common.partyType') }}</div>
         <span class="iconfont_icon" @click="deleteItem(item.title)">
           <svg class="iconfont icon_delete" aria-hidden="true">
             <use xlink:href="#icon-icon_delete_b"></use>
@@ -184,7 +187,7 @@ onUnmounted(() => {
         :model-value="item.title"
         :suffix-icon="IconDropDown"
         @change="changeTitle($event, item)"
-        placeholder="Please Select Party Type"
+        :placeholder="t('common.pleaseSelectPartyType')"
       >
         <el-option
           v-for="type in getAvailableOptions(item.title)"
@@ -195,7 +198,7 @@ onUnmounted(() => {
       </el-select>
     </div>
     <div style="margin-top: 16px">
-      <div class="ETD_title">Party Details</div>
+      <div class="ETD_title">{{ t('common.partyDetails') }}</div>
       <el-select
         :model-value="item.value"
         multiple
@@ -227,7 +230,7 @@ onUnmounted(() => {
         </el-option>
       </el-select>
       <div class="error" v-if="item.value.length === 0 && item.title">
-        Please Input Party Details
+        {{ t('common.pleaseInputPartyDetails') }}
       </div>
     </div>
   </div>

+ 3 - 0
src/directive/VLoading.ts

@@ -1,5 +1,6 @@
 import { createApp, h } from 'vue'
 import Loading from '../components/VLoading'
+import i18n from '../locales'
 
 export const VLoading = {
   mounted(el: any, binding: any) {
@@ -30,6 +31,7 @@ export const VLoading = {
         return h(Loading, { loading: binding.value, ...options })
       }
     })
+    app.use(i18n)
 
     const newDiv = document.createElement('div')
     el.appendChild(newDiv) // 将 Loading 组件的根元素添加到目标元素中
@@ -62,6 +64,7 @@ export const VLoading = {
           return h(Loading, { loading: binding.value, target: `#${target}` })
         }
       })
+      app.use(i18n)
 
       const newDiv = document.createElement('div')
       el.appendChild(newDiv)

+ 1353 - 11
src/locales/en.json

@@ -2,20 +2,1362 @@
   "login": {
     "username": "User Name",
     "password": "Password",
-    "login": "Login",
+    "logIn": "Log in",
+    "seeAll": "See All",
     "forgotPassword": "Forgot Password?",
-    "setPassword": "Set Password",
-    "activateAccount": "Activate Account",
-    "noAccount": "No account?",
-    "registerNow": "Register Now",
-    "captchaPlaceholder": "Please enter the captcha",
-    "sendCaptcha": "Send Captcha",
-    "resendCaptcha": "Resend Captcha",
-    "captchaSent": "Captcha has been sent to your email",
     "resetPassword": "Reset Password",
     "newPassword": "New Password",
     "confirmNewPassword": "Confirm New Password",
     "passwordMismatch": "Passwords do not match",
-    "activationEmailSent": "Activation email has been sent to your email"
+    "welcomeTitle": "Welcome to KLN Portal",
+    "welcomeSubtitle": "Login to your account",
+    "accountNotExist": "This account does not exist.",
+    "accountNotExistShort": "This account does not exist",
+    "loginBtn": "Login",
+    "resetPasswordTitle": "Reset Password",
+    "resetPasswordSubtitle": "We'll send your password to your email address.",
+    "setYourPassword": "Set Your Password",
+    "tooManyTries": "Oops, too many tries!",
+    "helloCreateNewPassword": "Hello {name}, please create a new password for your account.",
+    "passwordAttemptsExceeded": "You have exceeded the maximum number of password attempts.",
+    "errorTips1": "Please try again in 5 minutes or click",
+    "errorTips2": " to reset.",
+    "forgetPassword": "Forget Password",
+    "userName": "User Name",
+    "emailAddress": "Email Address",
+    "sendResetLink": "Send Reset Link",
+    "backToLogin": "Back to login",
+    "usernamePlaceholder": "Please input user name",
+    "passwordPlaceholder": "Please input password",
+    "emailPlaceholder": "Please input your email address",
+    "oldPasswordPlaceholder": "Please input Old Password",
+    "newPasswordComplexityHint": "New password must contain both letter and numeral",
+    "confirmPasswordPlaceholder": "Please Confirm Password",
+    "rememberPassword": "Remember Password",
+    "incorrectPassword": "Incorrect password. Please try again.",
+    "incorrectEmail": "Incorrect email. Please try again.",
+    "passwordSecurityAlert": "Please change your password for security.",
+    "oldPassword": "Old Password",
+    "changePassword": "Change Password",
+    "passwordLength12to20": "Password length between 12 - 20",
+    "passwordMustContainUppercase": "Password must contain uppercase letters",
+    "passwordMustContainLowercase": "Password must contain lowercase letters",
+    "passwordMustContainNumber": "Password must contain numbers",
+    "setPasswordAndActivateAccount": "Set Password & Activate Account",
+    "firstLoginChangePassword": "First login, please change your password.",
+    "updateYourPassword": "Update your password",
+    "firstLoginPasswordUpdateTip": "To make your account secure, please create a new password to replace the temporary password you were given in the email.",
+    "passwordResetSuccessfully": "Password Reset Successfully",
+    "passwordResetSuccessfullyLine1": "Your password has been successfully reset.",
+    "passwordResetSuccessfullyLine2": "You can now log in using your email and your new password.",
+    "passwordWillExpireMessage": "Your password will expire {expiredDays}, please reset your password",
+    "passwordSentToRegisteredEmail": "Your password sent to registered email.",
+    "changedSuccessfullyPleaseLogIn": "Changed successfully. Please log in.",
+    "accountActivatedSuccessfully": "Account Activated Successfully",
+    "accountActivatedSuccessfullyLine1": "Your account has been successfully activated.",
+    "accountActivatedSuccessfullyLine2": "You can now log in using your email and the password you just created.",
+    "goToLogin": "Go to Login",
+    "invalidToken": "Invalid token",
+    "pleaseLoginFirst": "Please login first"
+  },
+  "common": {
+    "search": "Search",
+    "cancel": "Cancel",
+    "save": "Save",
+    "reset": "Reset",
+    "ok": "OK",
+    "change": "Change",
+    "all": "All",
+    "update": "Update",
+    "prompt": "Prompt",
+    "logout": "Logout",
+    "light": "Light",
+    "dark": "Dark",
+    "submit": "Submit",
+    "created": "Created",
+    "confirmed": "Confirmed",
+    "cancelled": "Cancelled",
+    "clearFilters": "Clear Filters",
+    "downloadFile": "Download File",
+    "selectedColumns": "Selected columns",
+    "downloadWithAllColumns": "Download with all columns",
+    "saveSuccess": "Saved successfully",
+    "requestFailedRetry": "The request failed. Please try again later",
+    "errorPleaseTryAgainLater": "Error! Please try again later.",
+    "copySuccess": "Copy success",
+    "details": "Details",
+    "description": "Description",
+    "schedule": "Schedule",
+    "failedToLoadOptions": "Failed to load options",
+    "operation": "Operation",
+    "failedToLoadData": "Failed to load data",
+    "cannotAddDuplicateCities": "Cannot add duplicate cities.",
+    "inputCountryCityUncode": "Please input country/city/uncode",
+    "country": "Country",
+    "city": "City",
+    "uncode": "Uncode",
+    "etd": "ETD",
+    "eta": "ETA",
+    "total": "Total",
+    "noData": "No data",
+    "loading": "Loading...",
+    "input": "Input",
+    "sliderVerifyTipLine1": "Please drag the slider below to complete the",
+    "sliderVerifyTipLine2": "verification to ensure normal access",
+    "swipeToVerify": "Swipe to verify",
+    "verificationSuccessful": "Verification successful",
+    "verificationFailed": "Verification failed",
+    "transportMode": "Transport Mode",
+    "regionalSolutions": "REGIONAL SOLUTIONS",
+    "selectAll": "Select All",
+    "dateRange": "Date Range",
+    "dateType": "Date Type",
+    "pleaseSelectDateType": "Please Select Date Type",
+    "moreDateType": "More Date Type",
+    "moreFilters": "More Filters",
+    "general": "General",
+    "incoterms": "Incoterms",
+    "service": "Service",
+    "parties": "Parties",
+    "places": "Places",
+    "transportation": "Transportation",
+    "pleaseSelectDateRange": "Please Select Date Range",
+    "pleaseSelectService": "Please Select Service",
+    "seeAll": "See All",
+    "action":"Action",
+    "partyType": "Party Type",
+    "partyDetails": "Party Details",
+    "pleaseSelectPartyType": "Please Select Party Type",
+    "pleaseInputPartyDetails": "Please Input Party Details",
+    "placesType": "Places Type",
+    "placesDetails": "Places Details",
+    "pleaseInputPlacesDetails": "Please Input Places Details",
+    "title": "Title",
+    "noResultsFound": "No Results Found",
+    "emptyResultLine1": "We didn't find any search results,",
+    "emptyResultLine2": "please try to adjust your search keywords.",
+    "pageGuide": "Page Guide",
+    "departed": "Departed",
+    "cargoReceived": "Cargo Received",
+    "arrived": "Arrived",
+    "completed": "Completed",
+    "moveToTop": "Move to Top",
+    "moveUp": "Move Up",
+    "moveDown": "Move Down",
+    "moveToBottom": "Move to Bottom",
+    "container": "Container",
+    "trackingOnCarrierWebsite": "Tracking on carrier website"
+  },
+  "tracking": {
+    "title": "Tracking",
+    "searchPlaceholder": "Enter Booking/HBL/PO/Container/Carrier Booking No. ",
+    "referenceTip": "We support the following references number to find bookings: Booking No./HAWB No./MAWB No./PO No./Carrier Booking No./Contract No./File No./Quote No.",
+    "selectRecordForAttachmentDownload": "Please select at least one record to download attachments",
+    "download": "Download",
+    "downloadShipmentDetails": "Download Shipment Details",
+    "downloadAttachments": "Download Attachments",
+    "referenceTipForTracking": "We support the following references number to find tracking:",
+    "subscribe": "Subscribe",
+    "vgm": "VGM",
+    "saveSuccess": "Save success",
+    "customizeColumnsTip": "Drag item over to this selection or click \"add\" icon to show the column on your shipment list",
+    "moreFiltersGuide": "Click \"More Filters\" to see more search options.",
+    "hblGuide": "Click on the HBL No. or double-click anywhere on a single shipment data to enter the detailed page.",
+    "downloadGuideCount": "View the number of shipments selected for download",
+    "downloadGuideTpl": "Two download list templates are available",
+    "columnsGuideRight": "Drag to right to add columns",
+    "columnsGuideLeft": "Drag to left to unselect columns",
+    "columnsGuideOrder": "Drag up and down to reorder or select/unselect",
+    "pageGuideDesc": "After closing, you can still click the \"Page Guide\" button to view the page guide of the current page.",
+    "mawbMblNo": "MAWB/MBL No.",
+    "hawbHblNo": "HAWB/HBL No.",
+    "bookingNo": "Booking No.",
+    "poNo": "PO No.",
+    "incoterm": "Incoterm",
+    "serviceType": "Service Type",
+    "loadTerms": "Load Terms",
+    "shipmentType": "Shipment Type",
+    "co2Emission": "CO2 Emission",
+    "searchGuideTitle": "Frequently Used Search Criteria Display Area",
+    "searchGuideStatus": "Key Booking Status",
+    "searchGuideRef": "Reference Numbers",
+    "searchGuideMode": "Transport Mode",
+    "searchGuideRange": "Date Type & Range",
+    "filterGuideArea": "Selected query criteria display area",
+    "filterGuideClear": "You can quickly clear selected conditions at this position",
+    "shipmentStatus": "Shipment Status",
+    "containerStatus": "Container Status",
+    "shipper": "Shipper",
+    "consignee": "Consignee",
+    "originAgent": "Origin Agent",
+    "destinationAgent": "Destination Agent",
+    "quantityUnit": "Quantity Unit",
+    "gWeight": "G Weight",
+    "chWeight": "CH Weight",
+    "volume": "Volume",
+    "marks": "Marks",
+    "description": "Description",
+    "remark": "Remark",
+    "marksAndDescription":"Marks and Description",
+    "locationCode": "Location Code",
+    "locationCity": "Location City",
+    "pleaseEnterEmailContent": "Please enter the email content",
+    "emailSentSuccessfully": "Email sent successfully",
+    "failedToSendEmail": "Failed to send email",
+    "hblNo": "HBL No.",
+    "carrierBookingNo": "Carrier Booking No.",
+    "vessel": "Vessel",
+    "voyage": "Voyage",
+    "lastUpdatedUser": "Last Updated User",
+    "lastUpdatedTime": "Last Updated Time",
+    "textSearch": "text search",
+    "created": "Created",
+    "cargoReceived": "Cargo Received",
+    "departed": "Departed",
+    "arrived": "Arrived",
+    "completed": "Completed",
+    "shareLinkTip": "Copy the link to share this shipment with anyone.",
+    "publicSearchPlaceholder": "Enter Booking/HBL/MBL/PO/Container No.",
+    "publicReferenceTipTitle": "We support the following references number to find shipment:",
+    "publicReferenceTipList": "· Tracking No./Booking No./HAWB No./MAWB No./PO No./Quote No./Invoice No./Container No.",
+    "multipleResultsTitle": "Sorry,Multiple results",
+    "multipleResultsLine1": "To correctly display the details page,",
+    "multipleResultsLine2": "please search using the HBL No. Thank you.",
+    "selectDataOnShipmentList": "Select data on your shipment list",
+    "downloadWithSelectedColumns": "Download with selected columns",
+    "refNoDialogTitle": "Ref No.",
+    "selectReferenceTypePlaceholder": "Select Reference Type",
+    "enterRefNoPlaceholder": "Enter Ref No.",
+    "uploadFiles": "Upload Files",
+    "upload": "Upload",
+    "fileType": "File Type",
+
+    "All": "All",
+    "Created": "Created",
+    "Cargo Received": "Cargo Received",
+    "Departed": "Departed",
+    "Arrived": "Arrived",
+    "Completed": "Completed",
+    
+    "supportedFormats": "Supported formats: .pdf, .xlsx, .docx",
+    "maximumSize": "Maximum Size: 5MB;",
+    "maximumNumber": "Maximum Number: 5 files",
+    "allowedFileTypes": "The file types allowed for upload are: PDF, XLSX and DOCX.",
+    "communicationToKln": "Communication to KLN:",
+    "cc": "CC",
+    "separatedByTip": "Separated by",
+    "fileSizeLimit": "File size must not exceed 5MB!",
+    "selectFileTypeAndUpload": "Please select file type and upload file",
+    "uploadSuccessfully": "Upload successfully",
+    "uploadFailed": "Upload failed",
+    "selectAtLeastOneFileToDownload": "Please select at least one file to download.",
+    "attachmentSummary": "Attachment Summary",
+    "downloadSelected": "Download Selected ({count})",
+    "noFile": "No file",
+    "attachmentNumber": "Attachment {no}",
+    "defaultSettings": "Default Settings",
+    "settingsSavedSuccessfully": "Settings saved successfully",
+    "submitter": "Submitter",
+    "signature": "Signature",
+    "authorizedEmail": "Authorized Email",
+    "authorizedTel": "Authorized Tel",
+    "addVgm": "Add VGM",
+    "generalInformation": "General Information",
+    "detailInformation": "Detail Information",
+    "isSend": "Is Send",
+    "defaultSetting": "Default Setting",
+    "etd": "ETD",
+    "eta": "ETA",
+    "atd": "ATD",
+    "ata": "ATA",
+    "noAccess": "No access",
+    "drawerTitleBoth": "AMS/ISF",
+    "drawerTitleAms": "AMS",
+    "drawerTitleIsf": "ISF",
+    "amsM1Log": "AMS-M1 Log",
+    "isfLog": "ISF Log",
+    "copiedToClipboard": "Copied to clipboard",
+    "failedToCopy": "Failed to copy",
+    "tips": "Copy the link to share this shipment with anyone.",
+    "copyLink": "Copy Link",
+    "pleaseEnter": "Please enter...",
+    "pleaseSelect": "Please select...",
+    "pickDate": "Pick a Date",
+    "pleaseLoginFirst": "Please login first",
+    "basicInformation": "Basic Information",
+    "businessPartners": "Business Partners",
+    "containers": "Containers",
+    "milestones": "Milestones",
+    "routes": "Routes",
+    "attachment": "Attachment",
+    "legsCount": "Total number of legs: {count}",
+    "leg": "Leg {count}",
+    "origin": "Origin",
+    "destination": "Destination",
+    "etdAtd": "ETD / ATD",
+    "etaAta": "ETA / ATA",
+    "addReference": "Add Reference",
+    "vesselAirline": "Vessel / Airline",
+    "voyageFlight": "Voyage / Flight",
+    "referenceType": "Reference Type",
+    "refNo": "Ref No.",
+    "packing": "Packing",
+    "tdShare": "Share",
+    "tdHouseNo": "House No.",
+    "tdAmsIsf": "AMS/ISF",
+    "tdAddReference": "Add Reference",
+
+    "packages": "Packages",
+    "quantity": "Quantity",
+    "cbm": "CBM",
+    "cft": "CFT",
+    "seal": "Seal#",
+    "size": "Size",
+    "service": "Service",
+    "tracking_no": "Tracking No.",
+    "mbol_mawb_no": "MBOL/MAWB No.",
+    "hbol_hawb_no": "HBOL/HAWB No.",
+    "container_no": "Container No.",
+    "invoice_no": "Invoice No.",
+    "booking_no": "Booking No.",
+    "po_no": "PO No.",
+    "quote_no": "Quote No.",
+    "carrier_booking_no": "Carrier Booking No.",
+    "contract_no": "Contract No.",
+    "other_refenrence_no": "Other refenrence No.",
+    "mode": "Mode",
+    "service_type": "Service Type",
+    "bol_type": "BOL Type",
+    "ex_im": "EX/IM",
+    "incoterms": "Incoterms",
+    "status": "Status",
+    "shipper_id": "Shipper ID",
+    "consignee_id": "Consignee ID",
+    "notify_party": "Notify Party",
+    "notify_party_id": "Notify Party ID",
+    "bill_to": "Bill To",
+    "group_name": "Group Name",
+    "origin_agent": "Origin Agent",
+    "destination_agent": "Destination Agent",
+    "destination_operator": "Destination Operator",
+    "sales": "Sales",
+    "creation_time": "Creation Time",
+    "shipper_city": "Shipper City",
+    "consignee_city": "Consignee City",
+    "place_of_receipt": "Place of Receipt",
+    "port_of_loading": "Port of Loading",
+    "port_of_discharge": "Port of Discharge",
+    "place_of_delivery": "Place of Delivery",
+    "carrier": "Carrier",
+    "voyage_flight": "Voyage/Flight",
+    "vessel_airline": "Vessel/Airline",
+    "week": "Week",
+    "ace_m1_status": "ACE-M1 Status",
+    "is_isf": "Is ISF",
+    "obl_set": "OBL_SET",
+    "created_by": "Created by",
+    "total_qty": "Total QTY",
+    "gross_weight": "Gross Weight",
+    "chargeable_weight": "Chargeable Weight",
+    "manifest_hbol": "Manifest HBOL",
+    "co2_emission": "CO2 Emission",
+    "load_term": "Load Term",
+    "shipment_type": "Shipment Type",
+    "port_of_transhipment": "Port of Transhipment",
+    "container_size": "Container Size",
+    "last_mile_delivery_date": "Last Mile Delivery Date",
+
+
+
+    "date_time": "Date Time",
+    "code": "Code",
+    "name": "Name",
+    "sn": "SN",
+    "vgm_weight": "VGM Weight",
+    "vgm_unit": "VGM Unit",
+    "vgm_time": "VGM Time",
+    "vgm_method": "VGM Method",
+    "file_type": "File Type",
+    "file": "File",
+    "locations": "Locations",
+    "remarks": "Remarks",
+    "all": "All",
+    "reference_no": "Reference No",
+    "general": "General",
+    "time": "Time",
+    "places": "Places",
+    "transportation": "Transportation",
+    "others": "Others"
+
+  },
+  "systemMessage": {
+    "title": "System Message",
+    "eventNotifications": "Event Notifications",
+    "allNotifications": "All Notifications",
+    "markAllRead": "Mark all read",
+    "viewAll": "View all",
+    "milestoneUpdate": "Milestone Update",
+    "containerStatusUpdate": "Container Status Update",
+    "departureArrivalDelay": "Departure/Arrival Delay",
+    "etdEtaChange": "ETD/ETA Change",
+    "featureUpdate": "Feature Update",
+    "unread": "Unread",
+    "read": "Read",
+    "latestStatusUpdates": "Latest Status Updates ({count})",
+    "departureDelay": "Departure Delay",
+    "etdChange": "ETD Change",
+    "arrivalDelay": "Arrival Delay",
+    "etaChange": "ETA Change",
+    "route": "Route",
+    "currentLeg": "Current Leg",
+    "onlyDisplayWithinThreeMonths": "Only display the message data within three months",
+    "smartAssistantContent": "Smart Assistant is live! Ask in natural language, get real-time shipment data with multi-language support.",
+    "smartNotificationContent": "Smart Notification is here! Four key event alerts with customizable rules and multi-channel delivery."
+  },
+  "menu": {
+    "dashboard": "Dashboard",
+    "booking": "Booking",
+    "booking_management": "Booking Management",
+    "report": "Report",
+    "reportManagement": "Report Management",
+    "reportDetail": "Detail",
+    "reportSchedule": "Schedule Configuration",
+    "bookingDetail": "Booking Detail",
+    "destination_delivery": "Destination Delivery",
+    "tracking": "Tracking",
+    "trackingDetail": "Tracking Detail",
+    "trackingDownloadAttachment": "Tracking Download Attachment",
+    "shipmentDetail": "Shipment Detail",
+    "addVGM": "Add VGM",
+    "publicTracking": "Public Tracking",
+    "publicTrackingDetail": "Detail",
+    "login": "Login",
+    "resetPassword": "Reset Password",
+    "system_management": "System Management",
+    "system_message": "System Message",
+    "systemMessageDetail": "Detail",
+    "system_settings": "System Settings",
+    "createNewRule": "Create New Rule",
+    "createNewBooking": "Create New Booking",
+    "configurations": "Configurations",
+    "template_management": "Template Management",
+    "multilingual_config": "Multilingual Config",
+    "chat_log": "Chat Log",
+    "ai_api_log": "AI API Log",
+    "operation_log": "Operation Log",
+    "prompt_configuration": "Prompt Configuration"
+
+  },
+  "layout": {
+    "themes": "Themes",
+    "customizeWorkspaceTip": "Customize your workspace by changing the appearance and theme color",
+    "userManual": "User Manual",
+    "demoVideo": "Demo Video",
+    "logout": "Logout",
+    "downloadKLNPortal": "Download KLN Portal",
+    "loginRequiredToUseFeature": "Please log in to use this feature.",
+    "logoutConfirm": "Are you sure you want to logout?",
+    "changePasswordTitle": "Change Password",
+    "passwordUpdatedSuccessfully": "Password updated successfully",
+    "passwordLength12to20": "Password length between 12 - 20",
+    "passwordMustContainUppercase": "Password must contain uppercase letters",
+    "passwordMustContainLowercase": "Password must contain lowercase letters",
+    "passwordMustContainNumber": "Password must contain numbers",
+    "downloadKLNPortalTitle": "Download KLN Portal",
+    "iphoneAppStore": "iPhone App Store",
+    "androidPlayStore": "Android-Play Store",
+    "androidChinaUser": "Android-China User",
+    "viewAsCustomer": "View as Customer",
+    "selectCustomer": "Select customer",
+    "noData": "no data",
+    "oldPassword": "Old Password",
+    "newPassword": "New Password",
+    "confirmPassword": "Confirm Password",
+    "passwordMismatch": "The password does not match. Please try again.",
+    "passwordExpiredUnavailable": "This account's password has expired and is currently unavailable. Please select a different customer account to continue.",
+    "accountNotActivated": "This account has not been activated yet. Please select a different customer account to continue.",
+    "accountDisabled": "This account has been disabled and is no longer accessible. Please select a different customer account to continue.",
+    "operationFailedRetry": "Operation failed, please try again later",
+    "changePassword": "Change Password"
+  },
+  "report": {
+    "title": "Report",
+    "searchPlaceholder": "Search Report Name",
+    "downloadReport": "Download Report",
+    "manageFields": "Manage Fields",
+    "filters": "Filters",
+    "pleaseEnter": "Please enter...",
+    "pleaseSelect": "Please select...",
+    "to": "To",
+    "startDate": "Start date",
+    "endDate": "End date",
+    "manageReportFields": "Manage Report Fields",
+    "showAll": "Show All",
+    "hideAll": "Hide All",
+    "reportDataReview": "Report Data Review",
+    "reportName": "Report Name",
+    "ascending": "Ascending",
+    "descending": "Descending",
+    "sortBy": "Sort by",
+    "fieldsVisible": "{selected} of {total} fields visible",
+    "systemName": "System Name",
+    "displayNameInReport": "Display Name in Report",
+    "apply": "Apply",
+    "selectScheduleRuleValidityPeriod": "Please select the Schedule Rule Validity Period",
+    "permanentValid": "Permanent Valid",
+    "activeContinuouslyTip": "Active continuously once enabled, until manually disabled or deleted.",
+    "customPeriod": "Custom Period",
+    "onlyExecuteInSpecifiedPeriodTip": "Only automatically execute during specified time period.",
+    "effectiveStartDate": "Effective Start Date",
+    "effectiveEndDate": "Effective End Date",
+    "pickDate": "Pick a Date",
+    "scheduleConfiguration": "Schedule Configuration - {name}",
+    "scheduleRuleValidityPeriod": "Schedule Rule Validity Period",
+    "reportDataTimeRange": "Report Data Time Range",
+    "reportDeliveryFrequencyEmail": "Report Delivery Frequency & Email Configuration",
+    "selectReportDeliveryFrequencyEmailConfig": "Please select the Report Delivery Frequency & Email Configuration",
+    "emailRecipientsPlaceholder": "Enter email address separated by commas",
+    "saveSuccess": "Save Success",
+    "selectReportDataTimeRange": "Please select the Report Data Time Range",
+    "dataTimeReferenceFieldTitle": "Data Time Reference Field Selection",
+    "dataRangeMethodTitle": "Data Range Configuration Method",
+    "dynamicRollingRange": "Dynamic Rolling Range",
+    "fixedRange": "Fixed Range",
+    "past": "Past",
+    "emailRecipients": "Email Recipients",
+    "timezone": "Timezone",
+    "deliveryFrequency": "Delivery Frequency",
+    "daily": "Daily",
+    "weekly": "Weekly",
+    "monthly": "Monthly",
+    "quarterly": "Quarterly",
+    "yearly": "Yearly",
+    "monday": "Monday",
+    "tuesday": "Tuesday",
+    "wednesday": "Wednesday",
+    "thursday": "Thursday",
+    "friday": "Friday",
+    "saturday": "Saturday",
+    "sunday": "Sunday",
+    "january": "January",
+    "february": "February",
+    "march": "March",
+    "april": "April",
+    "may": "May",
+    "june": "June",
+    "july": "July",
+    "august": "August",
+    "september": "September",
+    "october": "October",
+    "november": "November",
+    "december": "December",
+    "firstMonth": "1st Month",
+    "secondMonth": "2nd Month",
+    "thirdMonth": "3rd Month",
+    "time": "Time",
+    "week": "Week",
+    "day": "Day",
+    "month": "Month",
+    "quarter": "Quarter",
+    "quarterMonth": "Quarter Month",
+    "year": "Year",
+    "future": "Future",
+    "daysOfWeekSelectMultiple": "Days of Week (Select Multiple)",
+    "daysOfMonthSelectMultiple": "Days of Month (Select Multiple)",
+    "monthsSelectMultiple": "Months (Select Multiple)",
+    "scheduleDetails": "Schedule Details",
+    "dynamicRollingTooltipLine1": "Configuration: Past X days to Future Y days, always dynamically calculated based on current date when generating reports.",
+    "dynamicRollingTooltipLine2": "Usage: For example, setting 5 days to 3 days means the data range differs each time it's automatically generated. Used for short-cycle operational monitoring.",
+    "fixedRangeTooltipLine1": "Configuration: Specific start and end dates, always query this range when automatically generating reports.",
+    "fixedRangeTooltipLine2": "Example: Start date [2025-01-01], End date [2025-12-31]",
+    "thisMonth": "This Month",
+    "thisQuarter": "This Quarter",
+    "lastQuarter": "Last Quarter",
+    "lastYear": "Last Year",
+
+
+
+    "tracking_no": "Tracking No.",
+    "mbol_mawb_no": "MBOL/MAWB No.",
+    "hbol_hawb_no": "HBOL/HAWB No.",
+    "shipment_invoice_no": "Shipment Invoice No.",
+    "booking_no": "Booking No.",
+    "shipment_po_no": "Shipment PO No.",
+    "container_no_house": "Container No. (House)",
+    "quote_no": "Quote No.",
+    "carrier_booking_no": "Carrier Booking No.",
+    "contract_no": "Contract No.",
+    "other_reference_no": "Other reference No.",
+    "manifest_hbol": "Manifest HBOL",
+    "transportation_mode": "Transportation Mode",
+    "service_type": "Service Type",
+    "shipment_type": "Shipment Type",
+    "container_size_house": "Container Size (House)",
+    "ex_im": "EX/IM",
+    "incoterms": "Incoterms",
+    "load_terms": "Load Terms",
+    "status": "Status",
+    "total_co2_emission": "Total CO2 Emission",
+    "total_distance": "Total Distance",
+    "pickup_co2_emission": "Pickup CO2 Emission",
+    "pickup_distance": "Pickup Distance",
+    "main_route_co2_emission": "Main Route CO2 Emission",
+    "main_route_distance": "Main Route Distance",
+    "delivery_co2_emission": "Delivery CO2 Emission",
+    "delivery_distance": "Delivery Distance",
+    "shipment_qty": "Shipment Qty",
+    "shipment_qty_unit": "Shipment Qty Unit",
+    "shipment_gross_weight": "Shipment Gross Weight",
+    "chargeable_weight": "Chargeable Weight",
+    "shipment_volume": "Shipment Volume",
+    "shipper": "Shipper",
+    "shipper_zip_code": "Shipper Zip Code",
+    "shipper_id": "Shipper ID",
+    "consignee": "Consignee",
+    "consignee_zip_code": "Consignee Zip Code",
+    "consignee_id": "Consignee ID",
+    "notify_party": "Notify party",
+    "notify_party_id": "Notify party ID",
+    "bill_to": "Bill to",
+    "group_name": "Group Name",
+    "origin_agent": "Origin Agent",
+    "destination_agent": "Destination Agent",
+    "destination_operator": "Destination Operator",
+    "sales": "Sales",
+    "etd": "ETD",
+    "eta": "ETA",
+    "creation_time": "Creation Time",
+    "atd": "ATD",
+    "ata": "ATA",
+    "shipper_city": "Shipper City",
+    "consignee_city": "Consignee City",
+    "place_of_receipt": "Place of Receipt",
+    "port_of_loading": "Port of Loading",
+    "port_of_discharge": "Port of Discharge",
+    "place_of_delivery": "Place of delivery",
+    "port_of_transhipment": "Port of Transhipment",
+    "carrier": "Carrier",
+    "voyage_flight": "Voyage/Flight",
+    "vessel_airline": "Vessel/Airline",
+    "booking_confirmation": "Booking Confirmation",
+    "carrier_booking_confirmation_received": "Carrier Booking Confirmation Received",
+    "cargo_pickup": "Cargo Pickup",
+    "empty_container_pickup": "Empty Container Pickup",
+    "cargo_arrived_at_origin": "Cargo Arrived at Origin",
+    "shipping_instruction_received_from_shipper": "Shipping Instruction Received from shipper",
+    "export_customs_documents_received": "Export Customs Documents Received",
+    "export_customs_released": "Export Customs Released",
+    "on_board": "On Board",
+    "departure": "Departure",
+    "arrived_at_transshipment_port": "Arrived at Transshipment Port",
+    "departed_at_transshipment_port": "Departed at Transshipment Port",
+    "arrival_notification": "Arrival Notification",
+    "arrived_at_final_discharge_port": "Arrived at Final Discharge Port",
+    "unloaded": "Unloaded",
+    "container_loaded_on_rail": "Container Loaded on Rail",
+    "container_unloaded_from_rail": "Container Unloaded from Rail",
+    "estimated_arrived_at_final_destination": "Estimated Arrived at Final Destination",
+    "arrived_at_final_destination": "Arrived at Final Destination",
+    "import_customs_documents_received": "Import Customs Documents Received",
+    "import_customs_released": "Import Customs Released",
+    "container_available": "Container Available",
+    "laden_container_pick_up": "Laden Container Pick Up",
+    "last_free_date": "Last Free Date",
+    "actual_door_delivery": "Actual Door Delivery",
+    "arrived_destination_warehouse": "Arrived Destination Warehouse",
+    "departed_destination_warehouse": "Departed Destination Warehouse",
+    "empty_container_return": "Empty Container Return",
+    "hbl_receipt_released": "HBL Receipt / Released",
+    "document_turnover_delivered": "Document Turnover / Delivered",
+    "ace_m1_status": "ACE-M1 Status",
+    "is_isf": "Is ISF",
+    "obl_set": "OBL_SET",
+    "container_no": "Container No.",
+    "container_size": "Container Size",
+    "container_qty": "Container Qty",
+    "container_unit": "Container Unit",
+    "container_weight": "Container Weight",
+    "container_volume": "Container Volume",
+    "container_po_no": "Container PO No.",
+    "item_no": "Item No.",
+    "container_invoice_no": "Container Invoice No.",
+    "gate_in_full_for_a_booking": "Gate in full for a booking",
+    "container_loaded_on_vessel": "Container loaded on vessel",
+    "vessel_departure": "Vessel Departure",
+    "vessel_arrival": "Vessel Arrival",
+    "unloaded_from_vessel": "Unloaded From Vessel",
+    "carrier_and_customs_release": "Carrier and Customs Release",
+    "customs_release": "Customs release",
+    "carrier_release": "Carrier release",
+    "gate_out_full_from_final_discharge_port": "Gate out full from final discharge port",
+    "gate_out_for_delivery_to_customer": "Gate out for delivery to customer",
+    "container_returned_empty": "Container returned empty",
+    "shipment_available_for_pickup_or_delivery": "Shipment available for pickup or delivery",
+    "empty_equipment_dispatched": "Empty Equipment Dispatched",
+    "loaded_at_relay_port": "Loaded at Relay Port",
+    "unloaded_at_relay_port": "Unloaded at Relay Port",
+    "arrive_relay_port": "Arrive Relay Port",
+    "depart_relay_port": "Depart Relay Port",
+    "free_time_expired": "Free Time Expired",
+    "free_time_to_expire": "Free Time to Expire",
+    "in_transit": "In Transit",
+    "arrival": "Arrival",
+    "last_mile_delivery": "Last Mile Delivery",
+    "item_po_no": "Item PO No.",
+    "sku_no": "SKU NO.",
+    "item_qty": "Item Qty",
+    "item_unit": "Item Unit",
+    "item_weight": "Item Weight",
+    "item_volume": "Item Volume",
+    "description": "Description",
+    "inner_pcs": "Inner PCS",
+    "pol_locode": "POL LOCODE",
+    "pod_locode": "POD LOCODE",
+    "vslvoy_flight": "VSLVOY/FLIGHT",
+    "controlling_customer": "Controlling Customer",
+    "seal_no": "Seal No.",
+    "pickup_postal_code": "Pickup-Postal Code",
+    "pickup_mode_type": "Pickup-Mode Type",
+    "last_mile_delivery_postal_code": "Last mile delivery-Postal Code",
+    "last_mile_delivery_mode_type": "Last mile delivery-Mode Type",
+    "co2e_intensity": "CO2e Intensity",
+    "last_mile_delivery_date": "Last Mile Delivery Date",
+    "last_mile_delivery_time": "Last Mile Delivery Time",
+    "last_mile_delivery_city": "Last Mile Delivery City",
+    "last_mile_delivery_address": "Last Mile Delivery Address",
+    "carrier_teu": "CARRIER TEU",
+
+
+
+
+
+    "invoice_no": "Invoice No."
+  },
+ 
+  "notificationRules": {
+    "shipmentRange": "Shipment Range",
+    "selectMilestone": "Select Milestone",
+    "selectContainerStatus": "Select Container Status",
+    "selectDelayedType": "Select Delayed Type",
+    "selectDelayedShipments": "Select Delayed Shipments",
+    "selectTimeType": "Select Time Type",
+    "notificationFrequency": "Notification Frequency",
+    "notificationMethod": "Notification Method",
+    "addedRules": "Added Rules",
+    "oceanShipments": "Ocean Shipments",
+    "airShipments": "Air Shipments",
+    "containerStatus": "Container Status",
+    "time": "Time",
+    "missing": "missing",
+    "duplicateRuleError": "Duplicate Rule Error.",
+    "duplicateRuleExactMatch": "This rule exactly matches an existing rule.",
+    "similarRuleDetected": "Similar Rule Detected",
+    "similarRuleExists": "A similar configuration rule already exists.",
+    "proceedCreateRule": "Would you like to proceed with creating this rule?",
+    "byEmail": "By Email",
+    "bySystemMessage": "By System Message",
+    "instantNotificationEachUpdate": "Instant notification for each update",
+    "dailySummary": "Daily Summary",
+    "weeklySummary": "Weekly Summary",
+    "selectTime": "Select Time",
+    "selectTimeZone": "Select Time Zone",
+    "selectDay": "Select Day",
+    "within": "within",
+    "etdTimeStr": "ETD within {count} Day(s)",
+    "etaTimeStr": "ETA within {count} Day(s)",
+    "days": "Day(s)",
+    "ocean": "Ocean",
+    "air": "Air",
+    "road": "Road",
+    "next30Days": "Next 30 days",
+    "next60Days": "Next 60 days",
+    "past10DaysToNext60Days": "Past 10 days to next 60 days",
+    "past30Days": "Past 30 days",
+    "customize": "Customize",
+    "minus": "minus",
+    "plus": "plus",
+    "currentTimeMinus": "Current time minus",
+    "currentTimePlus": "Current time plus",
+    "departureDelayedAtdEtd": "Departure Delayed (ATD-ETD)",
+    "arrivalDelayedAtaEta": "Arrival Delayed (ATA-ETA)",
+    "delayedTime": "Delayed Time",
+    "hours": "Hour(s)",
+    "notifyAllChanges": "Notify for all changes",
+    "notifyOnlyWhenTimeDifference": "Notify only when time difference",
+    "monday": "Monday",
+    "tuesday": "Tuesday",
+    "wednesday": "Wednesday",
+    "thursday": "Thursday",
+    "friday": "Friday",
+    "saturday": "Saturday",
+    "sunday": "Sunday"
+  },
+  "customizeColumns": {
+    "title": "Customize Columns",
+    "searchPlaceholder": "Search columns you preferred",
+    "selectedColumnsOnList": "Selected columns on your {type} list",
+    "booking": "booking",
+    "shipment": "shipment",
+    "resetToDefault": "Reset to default",
+    "tourStep1": "Drag items to the right group or click the Add icon to add columns to the {type} list.",
+    "tourStep2Line1": "Drag items to the left group or click the Remove icon to delete columns from the {type} list.",
+    "tourStep2Line2": "Drag items up or down to reorder the {type} list, or use the Move up and Move down icons.",
+    "gotIt": "Got it"
+  },
+  "aiRobot": {
+    "greeting": "Hi! I'm your Freight Assistant, always on call",
+    "frequentlyAskedQuestions": "Frequently Asked Questions",
+    "continueConversation": "Continue the conversation",
+    "disclaimerTitle": "Disclaimer",
+    "sidebarWindow": "Sidebar Window",
+    "maximizedWindow": "Maximized Window",
+    "collapsedToWidget": "Collapsed to Widget",
+    "answerInProgress": "Answer in progress, please wait.",
+    "cancelAnswer": "Cancel Answer",
+    "typeYourQuestionHere": "Type your question here...",
+    "contentGeneratedByAI": "Content is generated by Al, please check carefully!",
+    "importantNoticeAI": "Important Notice: AI-Generated Content",
+    "thinkingAboutQuestion": "Thinking about your question...",
+    "searchingRelevantDataPleaseWait": "Searching for relevant data, please wait...",
+    "queryIsComplexMayTakeMoreTime": "This query is complex and may take more time",
+    "trySimplifyingQuestionOrSelectingFaq": "You may try simplifying your question or selecting a Frequently Asked Question",
+    "queryFailedPleaseTryAgainLaterOrSelectFaq": "Sorry, the query failed. Please try again later or select a Frequently Asked Question",
+    "youHaveStoppedThisAnswer": "You have stopped this answer",
+    "disclaimerParagraph1": "This chat assistant is powered by artificial intelligence (AI) and is designed to help you access information and answer your queries efficiently. Please be aware of the following:",
+    "disclaimerBullet1Title": "AI-Generated Responses: ",
+    "disclaimerBullet1Text": "All responses are automatically generated by AI. While we strive for accuracy, errors or inaccuracies may occur. Please verify critical information independently before making business decisions.",
+    "disclaimerBullet2Title": "Data Privacy & Security:",
+    "disclaimerBullet2Text": "You can only access shipment data within your authorized account permissions. Your data remains confidential and will not be shared with other users or third parties.",
+    "disclaimerBullet3Title": "Information Accuracy: ",
+    "disclaimerBullet3Text": "For critical business decisions or time-sensitive matters, we recommend contacting our customer service team directly for verification.",
+    "disclaimerBullet4Title": "Service Limitations: ",
+    "disclaimerBullet4Text": "This assistant provides general guidance and data queries. For complex or specialized requests, please reach out to our support team.",
+    "disclaimerParagraph2": "By using this AI assistant, you acknowledge these limitations and agree to use the information provided accordingly."
+  },
+  "moreFilters": {
+    "vessel": "Vessel",
+    "voyageFlight": "Voyage/Flight",
+    "inputVesselNameOrCode": "Please input vessel name or code",
+    "inputVoyageOrFlightNo": "Please input Voyage or flight no.",
+    "origin": "Origin",
+    "destination": "Destination",
+    "placeOfDelivery": "Place of delivery",
+    "placeOfReceipt": "Place of Receipt",
+    "portOfLoading": "Port of Loading",
+    "placeOfDischarge": "Place of Discharge",
+    "inputPlacesName": "Please input places name",
+    "morePlaceType": "More Place Type",
+    "shipperName": "Shipper Name",
+    "consigneeName": "Consignee Name",
+    "originAgent": "Origin Agent",
+    "destinationAgent": "Destination Agent",
+    "sales": "Sales",
+    "notifyParty": "Notify Party",
+    "billTo": "Bill to",
+    "destinationOperator": "Destination Operator",
+    "controllingCustomer": "Controlling Customer",
+    "inputPartyName": "Please input party name",
+    "morePartyType": "More Party Type"
+  },
+  "scoringGrade": {
+    "feedbackPlaceholder": "We look forward to hearing from you. Thank you for helping to build a better customer system.",
+    "shareYourFeedback": "Share Your Feedback",
+    "apologyMessage": "We are so sorry for the inconveniences. We value your experience immensely.",
+    "dissatisfiedAspectQuestion": "Could you please tell us which aspects of the system you are dissatisfied with?",
+    "thanksForSharing": "Thank you for sharing your thoughts with us. We value your experience greatly.",
+    "shareLikedAndImprovement": "Could you share what aspects you liked and what could be improved ?",
+    "thanksForSatisfiedRating": "Thank you very much for giving us a satisfied rating on our service or system.",
+    "whatMadeYouSatisfied": "We are curious to learn more about what specifically made you feel satisfied.",
+    "apologizeAgain": "Apologize once again for your experience.",
+    "appreciateFeedback": "We greatly appreciate your valuable time and feedback.",
+    "thanksPositiveEvaluation": "Once again, thank you for your positive evaluation.",
+    "satisfactionQuestion": "How satisfied are you with this system?",
+    "satisfactionQuestionWithSpace": "How satisfied are you with this system ?",
+    "shareMoreDetails": "Would you like to share more details with us?",
+    "hi": "Hi!",
+    "submitSuccessful": "Submit Successful",
+    "betterServiceCommitment": "We are committed to working hard to provide better services.",
+    "aspectFunctionality": "Functionality",
+    "aspectDataAccuracy": "Data accuracy",
+    "aspectLookAndFeel": "Look and feel",
+    "aspectEaseOfUse": "Ease of use",
+    "aspectWebsitePerformance": "Website performance",
+    "highlyDissatisfied": "Highly Dissatisfied",
+    "dissatisfied": "Dissatisfied",
+    "neutral": "Neutral",
+    "satisfied": "Satisfied",
+    "highlySatisfied": "Highly Satisfied"
+  },
+  "dateRange": {
+    "startEtaTime": "Start ETA Time",
+    "endEtaTime": "End ETA Time",
+    "startAtaTime": "Start ATA Time",
+    "endAtaTime": "End ATA Time",
+    "startTime": "Start Time",
+    "endTime": "End Time",
+    "earliestTime": "Earliest Time",
+    "latestTime": "Latest Time",
+    "today": "Today",
+    "currentMonth": "Current Month",
+    "lastMonth": "Last Month",
+    "thisYear": "This Year",
+    "noStartLimit": "No Start Limit",
+    "noEndLimit": "No End Limit",
+    "startMonth": "Start month",
+    "endMonth": "End month"
+  },
+  "systemSettings": {
+    "title": "System Settings",
+    "personalProfileTab": "Personal Profile",
+    "subscribeNotificationsTab": "Subscribe Notifications",
+    "monitoringSettingsTab": "Monitoring Settings",
+    "notificationEventsForSubscribedShipments": "Notification Events for Subscribed Shipments",
+    "edit": "Edit",
+    "add": "Add",
+    "subscribedShipments": "Subscribed Shipments",
+    "addRule": "Add Rule",
+    "deleteRule":"Delete Rule",
+    "confirmDeleteRule": "Are you sure to delete this notification event?",
+    "event": "Event",
+    "eventDetails": "Event Details",
+    "frequency": "Frequency",
+    "methods": "Methods",
+    "hbolHawb": "HBOL/HAWB",
+    "shipper": "Shipper",
+    "consignee": "Consignee",
+    "eta": "ETA",
+    "etd": "ETD",
+    "recentMilestone": "Recent Milestone",
+    "createNewRule": "Create New Rule",
+    "unsavedChanges": "You have unsaved changes.",
+    "confirmLeavePage": "Are you sure you want to leave this page?",
+    "unsavedChangesTitle": "Unsaved Changes",
+    "notificationEvents": "Notification Events",
+    "selectEventPlaceholder": "Select event",
+    "milestoneUpdate": "Milestone Update",
+    "containerStatusUpdate": "Container Status Update",
+    "departureArrivalDelay": "Departure/Arrival Delay",
+    "etdEtaChange": "ETD/ETA Change",
+    "setting": "Setting",
+    "pleaseSelectNotificationEventsFirst": "Please select Notification Events first",
+    "shipmentRange": "Shipment Range",
+    "customizeYourShipmentTrackingPreferences": "Customize your shipment tracking preferences.",
+    "basicInformation": "Basic Information",
+    "firstName": "First Name",
+    "lastName": "Last Name",
+    "email": "Email",
+    "password": "Password",
+    "passwordExpirePrefix": "Your password will be expire in",
+    "passwordExpireDays": "{n} day(s)",
+    "maskCustomerInformation": "Mask Customer Information",
+    "yes": "Yes",
+    "no": "No",
+    "saveSuccessfully": "Save successfully",
+    "saveFailed": "Save failed",
+    "personalPreferences": "Personal Preferences",
+    "dateAndTime": "Date & Time",
+    "numbersFormatNav": "Numbers Format",
+    "dateFormat": "Date Format",
+    "numbersFormat": "Numbers Format",
+    "numbersUsUk": "1,234.56 (US/UK)",
+    "numbersEuropean": "1.234,56 (European)"
+  },
+  "dashboard": {
+    "title": "Dashboard",
+    "rememberSaveLayout": "Please remember to click the save button in order to keep the new dashboard layout and widgets.",
+    "save": "Save",
+    "saveFilters": "Save Filters",
+    "saveLayout": "Save Layout",
+    "filters": "Filters",
+    "transportMode": "Transport Mode",
+    "dateRange": "Date Range",
+    "fixed12Months": "The time range is fixed at 12 months.",
+    "salesConfigWarning": "*To ensure the accuracy of the data display, this report needs to be configured and displayed after communicating clearly with Sales.",
+    "kpiReportTip": "KPI Report: Day difference between actual and estimate.",
+    "pendingReportTip": "Pending Report: Showing shipments which are soon to depart/arrive.",
+    "etdToEtaTip": "ETD to ETA (Days): Distribution of Transit Time (ETA-ETD) for All Shipments in Last 12 Months.",
+    "containerCountTip": "Container Count: Total Container Volume by Month (Last 12 Months)",
+    "top10Tip": "Top 10 Origin & Destination: Last 12 Months Shipment Volume Rankings: Top 10 Origin Cities and Top 10 Destination Cities",
+    "co2eTip": "CO2e Emission by Origin or Destination: Last 12 Months CO2e Emission Rankings: Top 10 Origin Cities and Top 10 Destination Cities",
+    "recentStatusTip": "Recent Status: Active shipment list with ETD within the past three months and the next month.",
+    "revenueSpentTip": "Revenue Spent: Based on the billto object, display the corresponding revenue data.",
+    "revenueSpentTitle": "Revenue Spent",
+    "resetZoom": "Reset Zoom",
+    "scoringSorryText": "We are so sorry for the inconveniences. We value your experience immensely.",
+    "scoringDissatisfiedTip": "Could you please tell us which aspects of the system you are dissatisfied with?",
+    "scoringNeutralText": "Thank you for sharing your thoughts with us. We value your experience greatly.",
+    "scoringNeutralProposal": "Could you share what aspects you liked and what could be improved?",
+    "scoringSatisfiedText": "Thank you very much for giving us a satisfied rating on our service or system.",
+    "scoringSatisfiedProposal": "We are curious to learn more about what specifically made you feel satisfied.",
+    "highlyDissatisfied": "Highly dissatisfied",
+    "dissatisfied": "Dissatisfied",
+    "neutral": "Neutral",
+    "satisfied": "Satisfied",
+    "highlySatisfied": "Highly satisfied",
+    "functionality": "Functionality",
+    "dataAccuracy": "Data accuracy",
+    "lookAndFeel": "Look and feel",
+    "easeOfUse": "Ease of use",
+    "websitePerformance": "Website performance",
+    "satisfactionDetailsQuestion": "Would you like to share more details with us?",
+    "previous": "Previous",
+    "submit": "Submit",
+    "submitAndCommitment": "We are committed to working hard to provide better services.",
+
+
+    "shipper":"Shipper",
+    "consignee":"Consignee",
+    "controllingCustomer": "Controlling Customer",
+    "billTo": "Bill to",
+    "notifyParty": "Notify Party",
+    "customerSearchPlaceholder": "Search by Customer code, Customer name",
+    "customerType": "Customer Type",
+    "viewManagement": "View Management",
+
+
+
+
+    "apologizeAgain": "Apologize once again for your experience.",
+    "appreciateFeedback": "We greatly appreciate your valuable time and feedback.",
+    "thankYouPositive": "Once again, thank you for your positive evaluation.",
+    "guidePageGuide": "After closing, you can still click the \"Page Guide\" button to view the page guide of the current page.",
+    "guideDashboardIntro": "The Dashboard integrates different types of reports. You can freely select the reports you need.",
+    "guideDashboardFilter": "Each report comes with its own filter options. Simply make your selection and click \"Search\" to refresh the data.",
+    "guideDashboardTooltip": "Hover the icon next to the report name to view the data explanation for each report.",
+    "guideDashboardDrag": "Drag the icon to rearrange the report cards on the dashboard.",
+    "guideDashboardSaveLayout": "Click \"Save Layout\" to preserve your customized report arrangement."
+  },
+  "booking": {
+    "title": "Booking",
+    "sendEmail": "Send Email",
+    "search": "Search",
+    "selectDataOnBookingList": "Select data on your booking list",
+    "download": "Download",
+    "downloadWithSelectedColumns": "Download with selected columns",
+    "emailSentSuccessfully": "Email sent successfully",
+    "enterRefNoPlaceholder": "Enter Ref No.",
+    "failedToSendEmail": "Failed to send email",
+    "pleaseEnterEmailContent": "Please enter the email content",
+    "refNoDialogTitle": "Ref No.",
+    "searchPlaceholder": "Enter Booking/HBL/PO/Container/Carrier Booking No. ",
+    "customizeColumnsTip": "Drag item over to this selection or click \"add\" icon to show the column on your booking list",
+    "selectReferenceTypePlaceholder": "Select Reference Type",
+    "selected": "Selected",
+    "transportMode": "Transport Mode",
+    "shipmentStatus": "Shipment Status",
+    "bookingListEmptyTipLine1": "We support the following references number to find bookings: Booking No./HAWB No./MAWB No./PO No./Carrier Booking No./Contract No./File No./Quote No.",
+    "bookingListEmptyTipLine2": "",
+    "etd": "ETD",
+    "textSearch": "text search",
+    "bookingNo": "Booking No.",
+    "hawbHblNo": "HAWB/HBL No.",
+    "carrierBookingNo": "Carrier Booking No.",
+    "poNo": "PO No.",
+    "refNo": "Ref No.",
+    "vesselAirline": "Vessel / Airline",
+    "voyageFlight": "Voyage / Flight",
+    "incoterm": "Incoterm",
+    "serviceType": "Service Type",
+    "shipper": "Shipper",
+    "consignee": "Consignee",
+    "originAgent": "Origin Agent",
+    "destinationAgent": "Destination Agent",
+    "quantityUnit": "Quantity / Unit",
+    "gWeight": "G. Weight",
+    "chWeight": "Ch. Weight",
+    "volume": "Volume",
+    "marks": "Marks",
+    "description": "Description",
+    "packing": "Packing",
+    "All": "All",
+    "Created": "Created",
+    "Confirmed": "Confirmed",
+    "Cancelled": "Cancelled",
+    "marksAndDescription": "Marks and Description",
+    "bookingListEmptyTip": "We support the following references number to find booking:",
+    "bookingListEmptyTipDetail": "· Booking No./HAWB No./MAWB No./PO No./Carrier Booking No./Contract No./File No./Quote No.",
+    "basicInformation": "Basic Information",
+    "businessPartners": "Business Partners",
+    "containers": "Containers",
+    "origin": "Origin",
+    "destination": "Destination",
+    "etdAtd": "ETD/ATD",
+    "etaAta": "ETA/ATA",
+    "guideSearchAreaTitle": "Frequently Used Search Criteria Display Area",
+    "guideSearchAreaStatus": "Key Booking Status",
+    "guideSearchAreaReferences": "Reference Numbers",
+    "guideSearchAreaTransportMode": "Transport Mode",
+    "guideSearchAreaDateRange": "Date Type & Range",
+    "guideSelectedCriteria": "Selected query criteria display area",
+    "guideClearCriteria": "You can quickly clear selected conditions at this position",
+    "guideMoreFilters": "Click \"More Filters\" to see more search options.",
+    "guideOpenDetail": "Click on the Booking No. or double-click anywhere on a single booking data to enter the detailed page.",
+    "guideDownloadTipLine1": "View the number of shipments selected for download",
+    "guideDownloadTipLine2": "Two download list templates are available",
+    "guideCustomizeColumns": "Drag to right to add columns",
+    "guideCustomizeColumnsLeft": "Drag to left to unselect columns",
+    "guideCustomizeColumnsReorder": "Drag up and down to reorder or select/unselect",
+    "guidePageGuide": "After closing, you can still click the \"Page Guide\" button to view the page guide of the current page.",
+    "openInNewTab": "Open in New Tab",
+    "tdReferenceType": "Reference Type",
+    "tdAction": "Action",
+
+
+
+    "booking_no": "Booking No.",
+    "mbol_mawb_no": "MBOL/MAWB No.",
+    "hbol_hawb_no": "HBOL/HAWB No.",
+    "po_no": "PO No.",
+    "quote_no": "Quote No.",
+    "carrier_booking_no": "Carrier Booking No.",
+    "contract_no": "Contract No.",
+    "mode": "Mode",
+    "status": "Status",
+    "origin_agent": "Origin Agent",
+    "destination_agent": "Destination Agent",
+    "sales": "Sales",
+    "creation_time": "Creation Time",
+    "confirmation_time": "Confirmation Time",
+    "eta": "ETA",
+    "place_of_receipt": "Place of Receipt",
+    "port_of_loading": "Port of Loading",
+    "place_of_delivery": "Place of Delivery",
+    "carrier": "Carrier",
+    "voyage_flight": "Voyage/Flight",
+    "vessel_airline": "Vessel/Airline",
+    "week": "Week",
+    "created_by": "Created by",
+    "total_qty": "Total QTY",
+    "gross_weight": "Gross Weight",
+    "chargeable_weight": "Chargeable Weight",
+    "port_of_dsicharge": "Port of Dsicharge",
+    "qty": "QTY",
+    "size": "Size",
+    "kgs": "KGS",
+    "cbm": "CBM",
+    "sch_b": "SCH_B",
+    "unit": "Unit",
+    "po": "PO#",
+
+
+
+    "all": "All",
+    "reference_no": "Reference No",
+    "general": "General",
+    "parties": "Parties",
+    "time": "Time",
+    "places": "Places",
+    "transportation": "Transportation",
+    "others": "Others"
+  },
+  "destinationDelivery": {
+    "title": "Destination Delivery",
+    "configurations": "Configurations",
+    "calendarView": "Calendar View",
+    "listView": "List View",
+    "createNewBooking": "Create New Booking",
+    "truck": "Truck",
+    "rail": "Rail",
+    "totalBookings": "Total Bookings",
+    "bookingList": "Booking List",
+    "Pending Approval": "Pending Approval",
+    "Approved": "Approved",
+    "Rejected": "Rejected",
+    "Cancelled": "Cancelled",
+    "selectDataOnOperationLogList": "Select data on your Operation Log list:",
+    "operationLogTitle": "Operation Log",
+    "shipmentInformation": "Shipment Information",
+    "downloadWithSelectedColumns": "Download with selected columns",
+    "addNewDeliveryAddressTitle": "Add New Delivery Address",
+    "addressLine1Placeholder": "Line 1",
+    "addressLine2Placeholder": "Line 2",
+    "addressLine3Placeholder": "Line 3",
+    "addressLine4Placeholder": "Line 4",
+    "selectCountry": "Select Country",
+    "selectCity": "Select City",
+    "enterPostalCode": "Enter postal code",
+    "contactNamePlaceholder": "Name",
+    "contactMobilePlaceholder": "Mobile Numer",
+    "jan": "Jan",
+    "feb": "Feb",
+    "mar": "Mar",
+    "apr": "Apr",
+    "may": "May",
+    "jun": "Jun",
+    "jul": "Jul",
+    "aug": "Aug",
+    "sep": "Sep",
+    "oct": "Oct",
+    "nov": "Nov",
+    "dec": "Dec",
+    "created": "Created",
+    "modified": "Modified",
+    "pending": "Pending",
+    "current": "Current",
+    "deliveryDate": "Delivery Date",
+    "deliveryMode": "Delivery Mode",
+    "creationDate": "Creation Date",
+    "searchQuestionPlaceholder": "Search Question Booking No, HBOL No, MBL No, Container No, Consignee",
+    "stations": "Stations",
+    "bookingWindow": "Booking Window",
+    "communication": "Communication",
+    "result": "result",
+    "customizeColumns": "Customize Columns",
+    "serviceNotActivatedYet": "This service isn't activated yet. Please contact our team to enable it.",
+    "noEligibleShipmentsFoundToCreateNewBooking": "No eligible shipments found to create a new booking.",
+    "customizeColumnsTip": "Drag item over to this selection or click \"add\" icon to show the column on your booking list",
+    "manageAddress": "Manage Address",
+    "address": "Address",
+    "maximumCharacterLimitReached": "Maximum character limit reached (45 characters)",
+    "countryCode": "Country Code",
+    "cityCode": "City Code",
+    "postalCode": "Postal Code",
+    "contactInformation": "Contact Information",
+    "contactPerson": "Contact Person",
+    "contactNumber": "Contact Number",
+    "saveAddress": "Save Address",
+    "savedSuccessfully": "Saved successfully",
+    "unsavedChangesMessageLine1": "You have unsaved changes.",
+    "unsavedChangesMessageLine2": "Are you sure you want to leave this page?",
+    "hobl": "HOBL",
+    "communicateWithUs": "Communicate with us",
+    "separatedBySemicolon": "Separated by",
+    "sendEmail": "Send Email",
+    "pleaseEnterEmailContent": "Please enter the email content",
+    "emailSentSuccessfully": "Email sent successfully",
+    "failedToSendEmail": "Failed to send email",
+    "emptyBookingTitle": "You haven't created any destination delivery bookings yet.",
+    "emptyBookingLine1": "Book truck or rail delivery for your shipments to save time and",
+    "emptyBookingLine2": "ensure smooth last-mile delivery.",
+    "pendingTaskTip": "This is some description information about the pending task, if there is too much content.",
+    "modifyBooking": "Modify Booking",
+    "selected": "Selected",
+    "submit": "Submit",
+    "selectShipments": "Select Shipments",
+    "selectSameConsigneeTip": "Please select items with the same consignee.",
+    "selectSameConsigneeWarning": "Please select same consignee with same delivery information",
+    "searchBookingPlaceholder": "Enter Booking/HBL/PO/Carrier Booking No.",
+    "shipper": "Shipper",
+    "consignee": "Consignee",
+    "requiredFieldsNotEntered": "Required fields not entered.",
+    "outsideRecommendedPeriodForFollowing": "This date for following shipments is outside recommended period.",
+    "outsideRecommendedPeriod": "This date is outside our recommended period.",
+    "eta": "ETA",
+    "ata": "ATA",
+    "recommendedDeliveryDate": "Recommended Delivery Date",
+    "inputVesselName": "Input Vessel Name",
+    "vesselName": "Vessel Name",
+    "deliveryInformation": "Delivery Information",
+    "deliveryAddress": "Delivery Address",
+    "addressBook": "Address Book",
+    "addNewAddress": "Add New Address",
+    "modeType": "Mode Type",
+    "select": "Select",
+    "preferredDeliveryDate": "Preferred Delivery Date",
+    "pleaseSelectDate": "Please Select Date",
+    "deliveryTime": "Delivery Time",
+    "pleaseSelectTime": "Please Select Time",
+    "deliveryReference": "Delivery Reference",
+    "deliveryReferenceTooltip": "Reference to be quoted on arrival at the Warehouse/DC",
+    "specialRequirements": "Special Requirements",
+    "tailLiftRequired": "Tail Lift Required",
+    "sideLoading": "Side Loading",
+    "forkliftRequired": "Forklift Required",
+    "specialEquipment": "Special Equipment",
+    "additionalRequirementsPlaceholder": "Enter any additional requirements or notes...",
+    "modificationReason": "Modification Reason",
+    "createNewRule": "Create New Rule",
+    "completeRequiredFields": "Please complete all required fields.",
+    "unableToSave": "Unable to Save",
+    "setting": "Setting",
+    "selectStationEnableBooking": "Select Station (Enable Booking)",
+    "setBookingWindow": "Set Booking Window",
+    "klnPic": "KLN PIC",
+    "selectEmployeeAccount": "Select Employee Account",
+    "modifyRule": "Modify Rule",
+    "unsavedChanges": "There are unsaved changes.",
+    "confirmLeavePage": "Are you sure you want to leave this page?",
+    "unsavedChangesTitle": "Unsaved Changes",
+    "specificRule": "Specific Rule",
+    "singleDimension": "Single Dimension",
+    "deliveryDateTips": "No Specific recommended time for choosing delivery date",
+    "recommendDeliveryDate": "Recommend Delivery Date (ETA/ATA)",
+    "air": "Air",
+    "sea": "Sea",
+    "ctns": "ctns",
+    "priority": "Priority",
+    "ruleType": "Rule Type",
+    "port": "Port",
+    "carrier": "Carrier",
+    "fromEtaAtaDays": "From (ETA/ATA + Days)",
+    "toEtaAtaDays": "To (ETA/ATA + Days)",
+    "add": "Add",
+    "configureRegionsAndTimeSlots": "Configure available destination delivery regions and time slots.",
+    "addRule": "Add Rule",
+    "deleteRule": "Delete Rule",
+    "confirmDeleteRule": "Are you sure to delete this notification event?",
+    "configuration": "Configuration",
+    "addedRules": "Added Rules",
+    "failedToLoadOptions": "Failed to load options",
+    "bookings": "Bookings",
+    "accountPasswordExpiredUnavailable": "This account's password has expired and is currently unavailable. Please select a different customer account to continue.",
+    "noTimeRestrictionsForBooking": "No Specific time restrictions for creating booking",
+    "bookingWindowEtdAtd": "Booking Window (ETD/ATD)",
+    "bookingWindowEtaAta": "Booking Window (ETA/ATA)",
+    "daysBeforeTo": "days before to",
+    "daysAfter": "days after",
+    "stationList": "Station List",
+    "noData": "No Data",
+    "recommendedDeliveryShipmentsFor": "Recommended Delivery Shipments for {date}",
+    "freeStoragePeriodEnds": "Free Storage Period Ends",
+    "total": "Total",
+    "shipments": "shipments",
+    "totalCartons": "Total Cartons",
+    "bookingDetail": "Booking Detail",
+    "confirmRejectBooking": "Are you sure you want to Reject Booking ",
+    "confirmApproveBooking": "Are you sure you want to Approve Booking ",
+    "confirmCancelBooking": "Are you sure you want to Cancel Booking ",
+    "approve": "Approve",
+    "reject": "Reject",
+    "rejectRemarkRequired": "A remark must be filled in for the rejection operation.",
+    "actionSuccessfully": "successfully",
+    "actionFailedTryAgain": "failed, Please try again.",
+    "inputRemarks": "Input remarks",
+    "booking": "Booking",
+    "serviceNotAvailableTitle": "Destination Delivery Service Not Available",
+    "serviceNotAvailableText": "Destination delivery service is not yet available for your shipment destinations. To request this service, please contact the destination office first.",
+    "noEligibleShipmentsTitle": "No Eligible Shipments for Booking",
+    "noEligibleShipmentsText": "Your shipments are currently outside the booking window. Eligible shipments will appear here when booking window opens.",
+    "additionalStorageFeesMayApply": "Additional storage fees may apply.",
+    "additionalFeesNotice": "Additional Fees Notice",
+
+
+
+    "delivery_booking_no": "Delivery Booking No.",
+    "hbol_no": "HBOL NO.",
+    "mbl_no": "MBL No.",
+    "container_no": "Container No.",
+    "delivery_date": "Delivery Date",
+    "delivery_mode": "Delivery Mode",
+    "status": "Status",
+    "delivery_address": "Delivery Address",
+    "special_requiremnets": "Special Requiremnets",
+    "create_by": "Create_by",
+    "creation_date": "Creation Date",
+    "service_type": "Service Type",
+    "po_number": "PO Number",
+    "reference_no": "Reference No",
+    "mode": "Mode",
+    "pakages": "Pakages",
+    "package_type": "Package Type",
+    "kgw": "KGW",
+    "volume": "Volume",
+    "vessel_airline": "Vessel/Airline",
+    "voyage_flight": "Voyage/Flight",
+    "pol": "POL",
+    "pod": "POD",
+    "controlling_customer": "Controlling Customer",
+    "packing_list": "Packing List",
+    "commercial_invoice": "Commercial Invoice"
   }
-}
+}

+ 90 - 6
src/locales/index.ts

@@ -1,23 +1,107 @@
 // lang -> index.ts
 import { createI18n } from 'vue-i18n'
-import ch from './zh-tw.json'
+// import ch from './zh-tw.json'
+// import zhHans from './zh-cn.json'
 import en from './en.json'
-import zhCN from 'vxe-pc-ui/lib/language/zh-CN'
+// import zhCN from 'vxe-pc-ui/lib/language/zh-CN'
 import enUS from 'vxe-pc-ui/lib/language/en-US'
 
 /* 这里必须是messages名称 */
 const messages: any = {
-  zh_TW: { ...zhCN, ...ch },
-  en_US: { ...enUS, ...en }
+  // zh_CN: { ...en, ...zhCN, ...zhHans },
+  // zh_TW: { ...en, ...zhCN, ...ch },
+  en_US: { ...en, ...enUS }
 }
 messages.en_US.vxe.table.seqTitle = 'seq'
 
 const i18n = createI18n({
   legacy: false, // 使用 Composition API 模式,则需要将其设置为false
   globalInjection: true, //全局生效$t
-  locale: 'en_US', // 默认使用的语言
-  // fallbackLocale: 'zh_TW', // 回退语言
+  locale: 'en_US', // 默认使用简体中文
+  fallbackLocale: 'en_US', // 简体未覆盖时回退英文,避免落回繁体
   messages // 使用数据源
 })
 
+const localeToApiLangKey: Record<string, string> = {
+  en_US: 'english',
+  zh_CN: 'simplifiedChinese',
+  zh_TW: 'traditionalChinese',
+  fr_FR: 'french',
+  es_ES: 'spanish',
+  pt_BR: 'portuguese'
+}
+
+const langLabelToLocale: Record<string, string> = {
+  English: 'en_US',
+  'Chinese (Simplified)': 'zh_CN',
+  'Chinese (Traditional)': 'zh_TW',
+  French: 'fr_FR',
+  Spanish: 'es_ES',
+  Portuguese: 'pt_BR'
+}
+
+const loadedLocaleSet = new Set<string>()
+
+const normalizeApiMessages = (rawData: any): Record<string, any> => {
+  if (!rawData) return {}
+
+  // shape 1: { login: { username: '...' } }
+  if (!Array.isArray(rawData) && typeof rawData === 'object') {
+    const objectValues = Object.values(rawData)
+    if (objectValues.length > 0 && typeof objectValues[0] === 'object' && !Array.isArray(objectValues[0])) {
+      return rawData as Record<string, any>
+    }
+  }
+
+  // shape 2: [{ page: 'login', data: [{ key: 'username', english: '...', french: '...' }] }]
+  if (Array.isArray(rawData)) {
+    const result: Record<string, any> = {}
+    rawData.forEach((pageItem: any) => {
+      if (!pageItem?.page || !Array.isArray(pageItem?.data)) return
+      result[pageItem.page] = {}
+      pageItem.data.forEach((row: any) => {
+        if (!row?.key) return
+        // 优先拿后端已经按语言过滤后的通用 value,否则回退 english
+        result[pageItem.page][row.key] = row.value ?? row.english ?? row.originEnglish ?? ''
+      })
+    })
+    return result
+  }
+
+  return {}
+}
+
+export const resolveLocaleByLangLabel = (langLabel?: string) => {
+  if (!langLabel) return 'en_US'
+  return langLabelToLocale[langLabel] || 'en_US'
+}
+
+export const loadLocaleMessages = async (locale: string) => {
+  if (loadedLocaleSet.has(locale)) return
+
+  const langkey = localeToApiLangKey[locale]
+  if (!langkey) return
+
+  try {
+    // Lazy import avoids circular-init issues while still using API module method.
+    const { getMultilingualJsonFile } = await import('@/api/module/multilingual')
+    const res = await getMultilingualJsonFile({ langkey }, {})
+    const normalized = normalizeApiMessages(res?.data || res)
+    i18n.global.setLocaleMessage(locale, {
+      ...en,
+      ...normalized,
+      ...enUS
+    })
+    loadedLocaleSet.add(locale)
+  } catch (error) {
+    // keep fallback English messages when remote file fails
+    console.error('loadLocaleMessages failed:', error)
+  }
+}
+
+export const switchAppLocale = async (locale: string) => {
+  await loadLocaleMessages(locale)
+  i18n.global.locale.value = locale
+}
+
 export default i18n

+ 934 - 0
src/locales/zh-cn.json

@@ -0,0 +1,934 @@
+{
+  "login": {
+    "username": "User Name",
+    "password": "Password",
+    "login": "Login",
+    "forgotPassword": "Forgot Password?",
+    "setPassword": "Set Password",
+    "activateAccount": "Activate Account",
+    "noAccount": "No account?",
+    "registerNow": "Register Now",
+    "captchaPlaceholder": "Please enter the captcha",
+    "sendCaptcha": "Send Captcha",
+    "resendCaptcha": "Resend Captcha",
+    "captchaSent": "Captcha has been sent to your email",
+    "resetPassword": "Reset Password",
+    "newPassword": "New Password",
+    "confirmNewPassword": "Confirm New Password",
+    "passwordMismatch": "Passwords do not match",
+    "activationEmailSent": "Activation email has been sent to your email",
+    "welcomeTitle": "Welcome to KLN Portal",
+    "welcomeSubtitle": "Login to your account",
+    "accountNotExist": "This account does not exist.",
+    "accountNotExistShort": "This account does not exist",
+    "loginBtn": "Login",
+    "resetPasswordTitle": "Reset Password",
+    "resetPasswordSubtitle": "We'll send your password to your email address.",
+    "setYourPassword": "Set Your Password",
+    "helloCreateNewPassword": "Hello {name}, please create a new password for your account.",
+    "userName": "User Name",
+    "emailAddress": "Email Address",
+    "sendResetLink": "Send Reset Link",
+    "backToLogin": "Back to login",
+    "usernamePlaceholder": "Please input user name",
+    "passwordPlaceholder": "Please input password",
+    "emailPlaceholder": "Please input your email address",
+    "oldPasswordPlaceholder": "Please input Old Password",
+    "newPasswordComplexityHint": "New password must contain both letter and numeral",
+    "confirmPasswordPlaceholder": "Please Confirm Password",
+    "rememberPassword": "Remember Password",
+    "incorrectPassword": "Incorrect password. Please try again.",
+    "incorrectEmail": "Incorrect email. Please try again.",
+    "passwordSecurityAlert": "Please change your password for security.",
+    "changePassword": "Change Password",
+    "passwordLength12to20": "Password length between 12 - 20",
+    "passwordMustContainUppercase": "Password must contain uppercase letters",
+    "passwordMustContainLowercase": "Password must contain lowercase letters",
+    "passwordMustContainNumber": "Password must contain numbers",
+    "setPasswordAndActivateAccount": "Set Password & Activate Account",
+    "firstLoginChangePassword": "First login, please change your password.",
+    "updateYourPassword": "Update your password",
+    "firstLoginPasswordUpdateTip": "To make your account secure, please create a new password to replace the temporary password you were given in the email.",
+    "passwordResetSuccessfully": "Password Reset Successfully",
+    "passwordResetSuccessfullyLine1": "Your password has been successfully reset.",
+    "passwordResetSuccessfullyLine2": "You can now log in using your email and your new password.",
+    "accountActivatedSuccessfully": "Account Activated Successfully",
+    "accountActivatedSuccessfullyLine1": "Your account has been successfully activated.",
+    "accountActivatedSuccessfullyLine2": "You can now log in using your email and the password you just created.",
+    "goToLogin": "Go to Login",
+    "pleaseLoginFirst": "Please login first"
+  },
+  "common": {
+    "search": "Search",
+    "searchUserNamePlaceholder": "Search user name",
+    "searchQuestionPlaceholder": "Search Question ID、User",
+    "searchChatPlaceholder": "Search Question ID、User",
+    "customerSearchPlaceholder": "Search by Customer code, Customer name",
+    "customerType": "Customer Type",
+    "controllingCustomer": "Controlling Customer",
+    "billTo": "Bill to",
+    "notifyParty": "Notify Party",
+    "cancel": "Cancel",
+    "save": "Save",
+    "reset": "Reset",
+    "ok": "OK",
+    "change": "Change",
+    "all": "All",
+    "update": "Update",
+    "prompt": "Prompt",
+    "forgetPassword": "Forget Password",
+    "tooManyTries": "Oops, too many tries!",
+    "passwordAttemptsExceeded": "You have exceeded the maximum number of password attempts.",
+    "tryAgainInFiveMinutesOrReset": "Please try again in 5 minutes or click {action} to reset.",
+    "logout": "Logout",
+    "clearFilters": "Clear Filters",
+    "downloadFile": "Download File",
+    "selectedColumns": "Selected columns",
+    "downloadWithAllColumns": "Download with all columns",
+    "saveSuccess": "Saved successfully",
+    "requestFailedRetry": "The request failed. Please try again later",
+    "errorPleaseTryAgainLater": "Error! Please try again later.",
+    "copySuccess": "Copy success",
+    "invalidToken": "Invalid token",
+    "details": "Details",
+    "description": "Description",
+    "userType": "User Type",
+    "questionType": "Question Type",
+    "answerType": "Answer Type",
+    "answerSatisfaction": "Answer Satisfaction",
+    "page": "Page",
+    "chatLog": "Chat Log",
+    "customer": "Customer",
+    "employee": "Employee",
+    "predefinedQuestion": "Predefined Question",
+    "freeText": "Free Text",
+    "suspend": "Suspend",
+    "timeout": "Timeout",
+    "predefinedTemplate": "Predefined Template",
+    "aiAnswer": "AI Answer",
+    "null": "Null",
+    "good": "Good",
+    "notGood": "Not Good",
+    "responseDuration": "Response Duration",
+    "schedule": "Schedule",
+    "failedToLoadOptions": "Failed to load options",
+    "stations": "Stations",
+    "bookingWindow": "Booking Window",
+    "operation": "Operation",
+    "configureRegionsAndTimeSlots": "Configure available destination delivery regions and time slots.",
+    "deleteRule": "Delete Rule",
+    "confirmDeleteRule": "Are you sure to delete this notification event?",
+    "failedToLoadData": "Failed to load data",
+    "cannotAddDuplicateCities": "Cannot add duplicate cities.",
+    "inputCountryCityUncode": "Please input country/city/uncode",
+    "country": "Country",
+    "city": "City",
+    "uncode": "Uncode",
+    "etd": "ETD",
+    "eta": "ETA",
+    "total": "Total",
+    "noData": "No data",
+    "loading": "Loading...",
+    "input": "Input",
+    "sliderVerifyTipLine1": "Please drag the slider below to complete the",
+    "sliderVerifyTipLine2": "verification to ensure normal access",
+    "swipeToVerify": "Swipe to verify",
+    "verificationSuccessful": "Verification successful",
+    "verificationFailed": "Verification failed",
+    "transportMode": "Transport Mode",
+    "regionalSolutions": "REGIONAL SOLUTIONS",
+    "selectAll": "Select All",
+    "select": "Select",
+    "dateRange": "Date Range",
+    "dateType": "Date Type",
+    "pleaseSelectDateType": "Please Select Date Type",
+    "moreDateType": "More Date Type",
+    "moreFilters": "More Filters",
+    "general": "General",
+    "incoterms": "Incoterms",
+    "service": "Service",
+    "parties": "Parties",
+    "places": "Places",
+    "transportation": "Transportation",
+    "pleaseSelectDateRange": "Please Select Date Range",
+    "pleaseSelectService": "Please Select Service",
+    "seeAll": "See All",
+    "partyType": "Party Type",
+    "partyDetails": "Party Details",
+    "pleaseSelectPartyType": "Please Select Party Type",
+    "pleaseInputPartyDetails": "Please Input Party Details",
+    "placesType": "Places Type",
+    "placesDetails": "Places Details",
+    "pleaseInputPlacesDetails": "Please Input Places Details",
+    "title": "Title",
+    "noResultsFound": "No Results Found",
+    "emptyResultLine1": "We didn't find any search results,",
+    "emptyResultLine2": "please try to adjust your search keywords.",
+    "pageGuide": "Page Guide"
+  },
+  "tracking": {
+    "title": "Tracking",
+    "searchPlaceholder": "Enter Booking/HBL/PO/Container/Carrier Booking No. ",
+    "referenceTip": "We support the following references number to find bookings: Booking No./HAWB No./MAWB No./PO No./Carrier Booking No./Contract No./File No./Quote No.",
+    "selectRecordForAttachmentDownload": "Please select at least one record to download attachments",
+    "download": "Download",
+    "downloadShipmentDetails": "Download Shipment Details",
+    "downloadAttachments": "Download Attachments",
+    "referenceTipForTracking": "We support the following references number to find tracking:",
+    "subscribe": "Subscribe",
+    "vgm": "VGM",
+    "publicSearchPlaceholder": "Enter Booking/HBL/MBL/PO/Container No.",
+    "publicReferenceTipTitle": "We support the following references number to find shipment:",
+    "publicReferenceTipList": "· Tracking No./Booking No./HAWB No./MAWB No./PO No./Quote No./Invoice No./Container No.",
+    "multipleResultsTitle": "Sorry,Multiple results",
+    "multipleResultsLine1": "To correctly display the details page,",
+    "multipleResultsLine2": "please search using the HBL No. Thank you.",
+    "selectDataOnShipmentList": "Select data on your shipment list",
+    "downloadWithSelectedColumns": "Download with selected columns",
+    "refNoDialogTitle": "Ref No.",
+    "selectReferenceTypePlaceholder": "Select Reference Type",
+    "enterRefNoPlaceholder": "Enter Ref No.",
+    "uploadFiles": "Upload Files",
+    "upload": "Upload",
+    "fileType": "File Type",
+    "supportedFormats": "Supported formats: .pdf, .xlsx, .docx",
+    "maximumSize": "Maximum Size: 5MB;",
+    "maximumNumber": "Maximum Number: 5 files",
+    "selectFileTypeAndUpload": "Please select file type and upload file",
+    "uploadSuccessfully": "Upload successfully",
+    "uploadFailed": "Upload failed",
+    "allowedFileTypes": "The file types allowed for upload are: PDF, XLSX and DOCX.",
+    "fileSizeLimit": "File size must not exceed 5MB!",
+    "selectAtLeastOneFileToDownload": "Please select at least one file to download.",
+    "attachmentSummary": "Attachment Summary",
+    "downloadSelected": "Download Selected ({count})",
+    "noFile": "No file",
+    "attachmentNumber": "Attachment {no}",
+    "defaultSettings": "Default Settings",
+    "settingsSavedSuccessfully": "Settings saved successfully",
+    "submitter": "Submitter",
+    "signature": "Signature",
+    "authorizedEmail": "Authorized Email",
+    "authorizedTel": "Authorized Tel",
+    "addVgm": "Add VGM",
+    "generalInformation": "General Information",
+    "detailInformation": "Detail Information",
+    "isSend": "Is Send",
+    "defaultSetting": "Default Setting",
+    "hblNo": "HBL No.",
+    "carrierBookingNo": "Carrier Booking No.",
+    "vessel": "Vessel",
+    "voyage": "Voyage",
+    "etd": "ETD",
+    "eta": "ETA",
+    "lastUpdatedUser": "Last Updated User",
+    "lastUpdatedTime": "Last Updated Time",
+    "noAccess": "No access",
+    "drawerTitleBoth": "AMS/ISF",
+    "drawerTitleAms": "AMS",
+    "drawerTitleIsf": "ISF",
+    "amsM1Log": "AMS-M1 Log",
+    "isfLog": "ISF Log",
+    "copiedToClipboard": "Copied to clipboard",
+    "failedToCopy": "Failed to copy",
+    "tips": "Copy the link to share this shipment with anyone.",
+    "copyLink": "Copy Link",
+    "pleaseEnter": "Please enter...",
+    "pleaseSelect": "Please select...",
+    "pickDate": "Pick a Date",
+    "pleaseLoginFirst": "Please login first",
+    "basicInformation": "Basic Information",
+    "businessPartners": "Business Partners",
+    "containers": "Containers",
+    "milestones": "Milestones",
+    "routes": "Routes",
+    "attachment": "Attachment",
+    "legsCount": "Total number of legs: {count}",
+    "leg": "Leg {count}",
+    "origin": "Origin",
+    "destination": "Destination",
+    "etdAtd": "ETD / ATD",
+    "etaAta": "ETA / ATA",
+    "vesselAirline": "Vessel / Airline",
+    "voyageFlight": "Voyage / Flight",
+    "mawbMblNo": "MAWB/MBL No.",
+    "hawbHblNo": "HAWB/HBL No.",
+    "bookingNo": "Booking No.",
+    "poNo": "PO No.",
+    "refNo": "Ref No.",
+    "quantityUnit": "Quantity / Unit",
+    "gWeight": "G. Weight",
+    "chWeight": "Ch. Weight",
+    "volume": "Volume",
+    "marks": "Marks",
+    "remark": "Remark",
+    "packing": "Packing",
+    "marksAndDescription": "Marks and Description",
+    "seeAll": "See All",
+    "tdShare": "Share",
+    "tdHouseNo": "House No.",
+    "tdAmsIsf": "AMS/ISF",
+    "tdAddReference": "Add Reference",
+    "tdReferenceType": "Reference Type",
+    "tdRefNo": "Ref No.",
+    "tdAction": "Action",
+    "ptMawbMblNo": "MAWB/MBL No.",
+    "ptHawbHblNo": "HAWB/HBL No.",
+    "ptBookingNo": "Booking No.",
+    "ptPoNo": "PO No.",
+    "ptVesselAirline": "Vessel / Airline",
+    "ptVoyageFlight": "Voyage / Flight",
+    "ptOriginAgent": "Origin Agent",
+    "ptDestinationAgent": "Destination Agent",
+    "ptQuantityUnit": "Quantity / Unit",
+    "ptGWeight": "G. Weight",
+    "ptChWeight": "Ch. Weight",
+    "ptVolume": "Volume",
+    "ptMarks": "Marks",
+    "ptDescription": "Description"
+  },
+  "systemMessage": {
+    "title": "System Message",
+    "eventNotifications": "Event Notifications",
+    "allNotifications": "All Notifications",
+    "unread": "Unread",
+    "read": "Read",
+    "featureUpdate": "Feature Update",
+    "milestoneUpdate": "Milestone Update",
+    "containerStatusUpdate": "Container Status Update",
+    "departureArrivalDelay": "Departure/Arrival Delay",
+    "etdEtaChange": "ETD/ETA Change"
+  },
+  "layout": {
+    "logoutConfirm": "Are you sure you want to logout?",
+    "changePasswordTitle": "Change Password",
+    "passwordUpdatedSuccessfully": "Password updated successfully",
+    "passwordLength12to20": "Password length between 12 - 20",
+    "passwordMustContainUppercase": "Password must contain uppercase letters",
+    "passwordMustContainLowercase": "Password must contain lowercase letters",
+    "passwordMustContainNumber": "Password must contain numbers",
+    "downloadKLNPortalTitle": "Download KLN Portal",
+    "iphoneAppStore": "iPhone App Store",
+    "androidPlayStore": "Android-Play Store",
+    "androidChinaUser": "Android-China User",
+    "viewAsCustomer": "View as Customer",
+    "selectCustomer": "Select customer",
+    "noData": "no data",
+    "passwordExpiredUnavailable": "This account's password has expired and is currently unavailable. Please select a different customer account to continue.",
+    "accountNotActivated": "This account has not been activated yet. Please select a different customer account to continue.",
+    "accountDisabled": "This account has been disabled and is no longer accessible. Please select a different customer account to continue.",
+    "operationFailedRetry": "Operation failed, please try again later",
+    "customizeColumns": "Customize Columns",
+    "openDetailedPage": "Open Detailed Page",
+    "closeDetailedPage": "Close Detailed Page",
+    "sendEmail": "Send Email",
+    "publicTracking": "Public Tracking",
+    "addReference": "Add Reference",
+    "uploadFile": "Upload File",
+    "amsIsf": "AMS/ISF",
+    "changePassword": "Change Password"
+  },
+  "report": {
+    "title": "Report",
+    "searchPlaceholder": "Search Report Name",
+    "excelFile": "Excel(.xlsx)",
+    "csvFile": "CSV(.csv)",
+    "downloadReport": "Download Report",
+    "manageFields": "Manage Fields",
+    "filters": "Filters",
+    "pleaseEnter": "Please enter...",
+    "pleaseSelect": "Please select...",
+    "to": "To",
+    "startDate": "Start date",
+    "endDate": "End date",
+    "manageReportFields": "Manage Report Fields",
+    "showAll": "Show All",
+    "hideAll": "Hide All",
+    "of": "of",
+    "fieldsVisible": "Fields Visible",
+    "systemName": "System Name",
+    "displayNameInReport": "Display Name in Report",
+    "apply": "Apply",
+    "selectScheduleRuleValidityPeriod": "Please select the Schedule Rule Validity Period",
+    "permanentValid": "Permanent Valid",
+    "activeContinuouslyTip": "Active continuously once enabled, until manually disabled or deleted.",
+    "customPeriod": "Custom Period",
+    "onlyExecuteInSpecifiedPeriodTip": "Only automatically execute during specified time period.",
+    "effectiveStartDate": "Effective Start Date",
+    "effectiveEndDate": "Effective End Date",
+    "pickDate": "Pick a Date"
+  },
+  "templateManagement": {
+    "title": "Report Template Management",
+    "createNew": "Create New Report Template",
+    "searchReportNamePlaceholder": "Search report name",
+    "isActivePlaceholder": "Is Active",
+    "applicationScopePlaceholder": "Application Scope",
+    "partyIdsPlaceholder": "Party IDs",
+    "active": "Active",
+    "inactive": "Inactive",
+    "allUsers": "All Users",
+    "specificUsers": "Specific Users",
+    "selectPartyIdsMulti": "Select Party IDs (Multi-select allowed)",
+    "selectGroupNameMulti": "Select Group Name (Multi-select allowed)",
+    "addEditFieldTitle": "Add/Edit Field",
+    "searchFieldPlaceholder": "Search field",
+    "reportFieldsConfigurationNewFieldNameRequired": "Please enter the new field name.",
+    "reportFieldsConfigurationFixedValueRequired": "Please enter the fixed value.",
+    "reportFieldsConfigurationReportLevelRequired": "Please select the report level.",
+    "createReportTemplateReportNameRequired": "Please enter the Report Name.",
+    "createReportTemplateReportLevelRequired": "Please enter the Report Level",
+    "createReportTemplateReportDescriptionRequired": "Please enter the Report Description.",
+    "createReportTemplateSavedSuccessfully": "Report Template saved successfully!",
+    "createReportTemplateAddNewFieldTitle": "Add New Field",
+    "createReportTemplateNewFieldNameLabel": "New Field Name",
+    "createReportTemplateReportNameLabel": "Report Name",
+    "createReportTemplateReportLevelLabel": "Report Level",
+    "createReportTemplateReportDescriptionLabel": "Report Description",
+    "createReportTemplateBlankText": "Blank",
+    "createReportTemplateFixedValueText": "Fixed Value",
+    "createReportTemplateFieldValueLabel": "Field Value",
+    "createReportTemplateFixedValueLabel": "Fixed Value",
+    "createReportTemplateMappingTitle": "Mapping (Field: Final Destination)",
+    "createReportTemplateSystemValueLabel": "System Value",
+    "createReportTemplateOutputValueLabel": "Output Value",
+    "createReportTemplateAddMapping": "Add Mapping",
+    "createReportTemplateFilter": "Filter",
+    "createReportTemplateSort": "Sort",
+    "createReportTemplateCancel": "Cancel",
+    "createReportTemplateApply": "Apply"
+  },
+  "reportSchedule": {
+    "scheduleConfiguration": "Schedule Configuration - {name}",
+    "scheduleRuleValidityPeriod": "Schedule Rule Validity Period",
+    "reportDataTimeRange": "Report Data Time Range",
+    "reportDeliveryFrequencyEmail": "Report Delivery Frequency & Email Configuration",
+    "selectReportDeliveryFrequencyEmailConfig": "Please select the Report Delivery Frequency & Email Configuration",
+    "emailRecipientsPlaceholder": "Enter email address separated by commas",
+    "saveSuccess": "Save Success",
+    "selectReportDataTimeRange": "Please select the Report Data Time Range",
+    "dataTimeReferenceFieldTitle": "Data Time Reference Field Selection",
+    "dataRangeMethodTitle": "Data Range Configuration Method",
+    "dynamicRollingRange": "Dynamic Rolling Range",
+    "fixedRange": "Fixed Range",
+    "startDate": "Start Date",
+    "endDate": "End Date",
+    "past": "Past",
+    "future": "Future",
+    "dynamicRollingTooltipLine1": "Configuration: Past X days to Future Y days, always dynamically calculated based on current date when generating reports.",
+    "dynamicRollingTooltipLine2": "Usage: For example, setting 5 days to 3 days means the data range differs each time it's automatically generated. Used for short-cycle operational monitoring.",
+    "fixedRangeTooltipLine1": "Configuration: Specific start and end dates, always query this range when automatically generating reports.",
+    "fixedRangeTooltipLine2": "Example: Start date [2025-01-01], End date [2025-12-31]",
+    "thisMonth": "This Month",
+    "thisQuarter": "This Quarter",
+    "lastQuarter": "Last Quarter",
+    "lastYear": "Last Year"
+  },
+  "personalProfile": {
+    "basicInformation": "Basic Information",
+    "firstName": "First Name",
+    "lastName": "Last Name",
+    "email": "Email",
+    "password": "Password",
+    "passwordExpirePrefix": "Your password will be expire in",
+    "passwordExpireDays": "{n} day(s)",
+    "maskCustomerInformation": "Mask Customer Information",
+    "yes": "Yes",
+    "no": "No",
+    "saveSuccessfully": "Save successfully",
+    "saveFailed": "Save failed",
+    "personalPreferences": "Personal Preferences",
+    "dateAndTime": "Date & Time",
+    "numbersFormatNav": "Numbers Format",
+    "dateFormat": "Date Format",
+    "numbersFormat": "Numbers Format",
+    "numbersUsUk": "1,234.56 (US/UK)",
+    "numbersEuropean": "1.234,56 (European)"
+  },
+  "aiApiLog": {
+    "title": "AI API Log",
+    "searchPlaceholder": "Search Request ID, Question ID",
+    "aiModel": "AI Model",
+    "responseDuration": "Response Duration",
+    "selected": "Selected",
+    "requestContent": "Request Content",
+    "responseContent": "Response Content",
+    "selectDataOnLogList": "Select data on your Operation Log list:",
+    "downloadWithSelectedColumns": "Download with selected columns",
+    "deepseek": "Deepseek",
+    "claude": "Claude",
+    "greaterOrEqual": ">=",
+    "equal": "=",
+    "lessOrEqual": "<=",
+    "download": "Download"
+  },
+  "notificationRules": {
+    "shipmentRange": "Shipment Range",
+    "selectMilestone": "Select Milestone",
+    "selectContainerStatus": "Select Container Status",
+    "selectDelayedType": "Select Delayed Type",
+    "selectDelayedShipments": "Select Delayed Shipments",
+    "selectTimeType": "Select Time Type",
+    "notificationFrequency": "Notification Frequency",
+    "notificationMethod": "Notification Method",
+    "addedRules": "Added Rules",
+    "oceanShipments": "Ocean Shipments",
+    "airShipments": "Air Shipments",
+    "containerStatus": "Container Status",
+    "time": "Time",
+    "missing": "missing",
+    "duplicateRuleError": "Duplicate Rule Error.",
+    "duplicateRuleExactMatch": "This rule exactly matches an existing rule.",
+    "similarRuleDetected": "Similar Rule Detected",
+    "similarRuleExists": "A similar configuration rule already exists.",
+    "proceedCreateRule": "Would you like to proceed with creating this rule?",
+    "byEmail": "By Email",
+    "bySystemMessage": "By System Message",
+    "instantNotificationEachUpdate": "Instant notification for each update",
+    "dailySummary": "Daily Summary",
+    "weeklySummary": "Weekly Summary",
+    "selectTime": "Select Time",
+    "selectTimeZone": "Select Time Zone",
+    "selectDay": "Select Day",
+    "within": "within",
+    "days": "Day(s)",
+    "ocean": "Ocean",
+    "air": "Air",
+    "road": "Road",
+    "next30Days": "Next 30 days",
+    "next60Days": "Next 60 days",
+    "past10DaysToNext60Days": "Past 10 days to next 60 days",
+    "past30Days": "Past 30 days",
+    "customize": "Customize",
+    "minus": "minus",
+    "plus": "plus",
+    "currentTimeMinus": "Current time minus",
+    "currentTimePlus": "Current time plus",
+    "departureDelayedAtdEtd": "Departure Delayed (ATD-ETD)",
+    "arrivalDelayedAtaEta": "Arrival Delayed (ATA-ETA)",
+    "delayedTime": "Delayed Time",
+    "hours": "Hour(s)",
+    "notifyAllChanges": "Notify for all changes",
+    "notifyOnlyWhenTimeDifference": "Notify only when time difference",
+    "monday": "Monday",
+    "tuesday": "Tuesday",
+    "wednesday": "Wednesday",
+    "thursday": "Thursday",
+    "friday": "Friday",
+    "saturday": "Saturday",
+    "sunday": "Sunday"
+  },
+  "customizeColumns": {
+    "title": "Customize Columns",
+    "searchPlaceholder": "Search columns you preferred",
+    "selectedColumnsOnList": "Selected columns on your {type} list",
+    "booking": "booking",
+    "shipment": "shipment",
+    "resetToDefault": "Reset to default",
+    "tourStep1": "Drag items to the right group or click the Add icon to add columns to the {type} list.",
+    "tourStep2Line1": "Drag items to the left group or click the Remove icon to delete columns from the {type} list.",
+    "tourStep2Line2": "Drag items up or down to reorder the {type} list, or use the Move up and Move down icons.",
+    "gotIt": "Got it"
+  },
+  "aiRobot": {
+    "greeting": "Hi! I'm your Freight Assistant, always on call",
+    "frequentlyAskedQuestions": "Frequently Asked Questions",
+    "continueConversation": "Continue the conversation",
+    "disclaimerTitle": "Disclaimer",
+    "sidebarWindow": "Sidebar Window",
+    "maximizedWindow": "Maximized Window",
+    "collapsedToWidget": "Collapsed to Widget",
+    "answerInProgress": "Answer in progress, please wait.",
+    "cancelAnswer": "Cancel Answer",
+    "typeYourQuestionHere": "Type your question here...",
+    "contentGeneratedByAI": "Content is generated by Al, please check carefully!",
+    "importantNoticeAI": "Important Notice: AI-Generated Content",
+    "aiGeneratedResponses": "AI-Generated Responses:",
+    "dataPrivacySecurity": "Data Privacy & Security:",
+    "informationAccuracy": "Information Accuracy:",
+    "serviceLimitations": "Service Limitations:",
+    "aiAcknowledgement": "By using this AI assistant, you acknowledge these limitations and agree to use the information provided accordingly.",
+    "sidebarWindowTip": "Sidebar Window",
+    "maximizedWindowTip": "Maximized Window",
+    "collapsedToWidgetTip": "Collapsed to Widget"
+  },
+  "containerStatus": {
+    "container": "Container",
+    "trackingOnCarrierWebsite": "Tracking on carrier website:"
+  },
+  "notificationMessage": {
+    "latestStatusUpdates": "Latest Status Updates ({count})",
+    "departureDelay": "Departure Delay",
+    "etdChange": "ETD Change",
+    "arrivalDelay": "Arrival Delay",
+    "etaChange": "ETA Change",
+    "route": "Route",
+    "currentLeg": "Current Leg",
+    "onlyDisplayWithinThreeMonths": "Only display the message data within three months",
+    "smartAssistantContent": "Smart Assistant is live! Ask in natural language, get real-time shipment data with multi-language support.",
+    "smartNotificationContent": "Smart Notification is here! Four key event alerts with customizable rules and multi-channel delivery."
+  },
+  "vbox": {
+    "moveToTop": "Move to Top",
+    "moveUp": "Move Up",
+    "moveDown": "Move Down",
+    "moveToBottom": "Move to Bottom"
+  },
+  "moreFilters": {
+    "vessel": "Vessel",
+    "voyageFlight": "Voyage/Flight",
+    "inputVesselNameOrCode": "Please input vessel name or code",
+    "inputVoyageOrFlightNo": "Please input Voyage or flight no.",
+    "origin": "Origin",
+    "destination": "Destination",
+    "placeOfDelivery": "Place of delivery",
+    "placeOfReceipt": "Place of Receipt",
+    "portOfLoading": "Port of Loading",
+    "placeOfDischarge": "Place of Discharge",
+    "inputPlacesName": "Please input places name",
+    "morePlaceType": "More Place Type",
+    "shipperName": "Shipper Name",
+    "consigneeName": "Consignee Name",
+    "originAgent": "Origin Agent",
+    "destinationAgent": "Destination Agent",
+    "sales": "Sales",
+    "notifyParty": "Notify Party",
+    "billTo": "Bill to",
+    "destinationOperator": "Destination Operator",
+    "controllingCustomer": "Controlling Customer",
+    "inputShipperName": "Please input shipper name",
+    "inputConsigneeName": "Please input consignee name",
+    "inputPartyName": "Please input party name",
+    "morePartyType": "More Party Type"
+  },
+  "scoringGrade": {
+    "feedbackPlaceholder": "We look forward to hearing from you. Thank you for helping to build a better customer system.",
+    "shareYourFeedback": "Share Your Feedback",
+    "apologyMessage": "We are so sorry for the inconveniences. We value your experience immensely.",
+    "dissatisfiedAspectQuestion": "Could you please tell us which aspects of the system you are dissatisfied with?",
+    "thanksForSharing": "Thank you for sharing your thoughts with us. We value your experience greatly.",
+    "shareLikedAndImprovement": "Could you share what aspects you liked and what could be improved ?",
+    "thanksForSatisfiedRating": "Thank you very much for giving us a satisfied rating on our service or system.",
+    "whatMadeYouSatisfied": "We are curious to learn more about what specifically made you feel satisfied.",
+    "apologizeAgain": "Apologize once again for your experience.",
+    "appreciateFeedback": "We greatly appreciate your valuable time and feedback.",
+    "thanksPositiveEvaluation": "Once again, thank you for your positive evaluation.",
+    "satisfactionQuestion": "How satisfied are you with this system?",
+    "satisfactionQuestionWithSpace": "How satisfied are you with this system ?",
+    "shareMoreDetails": "Would you like to share more details with us?",
+    "hi": "Hi!",
+    "submitSuccessful": "Submit Successful",
+    "betterServiceCommitment": "We are committed to working hard to provide better services.",
+    "aspectFunctionality": "Functionality",
+    "aspectDataAccuracy": "Data accuracy",
+    "aspectLookAndFeel": "Look and feel",
+    "aspectEaseOfUse": "Ease of use",
+    "aspectWebsitePerformance": "Website performance",
+    "highlyDissatisfied": "Highly Dissatisfied",
+    "dissatisfied": "Dissatisfied",
+    "neutral": "Neutral",
+    "satisfied": "Satisfied",
+    "highlySatisfied": "Highly Satisfied"
+  },
+  "dateRange": {
+    "startEtaTime": "Start ETA Time",
+    "endEtaTime": "End ETA Time",
+    "startAtaTime": "Start ATA Time",
+    "endAtaTime": "End ATA Time",
+    "startTime": "Start Time",
+    "endTime": "End Time",
+    "earliestTime": "Earliest Time",
+    "latestTime": "Latest Time",
+    "today": "Today",
+    "current": "Current",
+    "currentMonth": "Current Month",
+    "lastMonth": "Last Month",
+    "thisYear": "This Year",
+    "noStartLimit": "No Start Limit",
+    "noEndLimit": "No End Limit",
+    "startMonth": "Start month",
+    "endMonth": "End month"
+  },
+  "systemSettings": {
+    "title": "System Settings",
+    "profile": "Profile",
+    "personalProfileTab": "Personal Profile",
+    "subscribeNotificationsTab": "Subscribe Notifications",
+    "monitoringSettingsTab": "Monitoring Settings",
+    "notificationEventsForSubscribedShipments": "Notification Events for Subscribed Shipments",
+    "edit": "Edit",
+    "add": "Add",
+    "subscribedShipments": "Subscribed Shipments",
+    "addRule": "Add Rule"
+  },
+ 
+  "dashboard": {
+    "title": "Dashboard",
+    "rememberSaveLayout": "Please remember to click the save button in order to keep the new dashboard layout and widgets.",
+    "save": "Save",
+    "saveFilters": "Save Filters",
+    "saveLayout": "Save Layout",
+    "filters": "Filters",
+    "transportMode": "Transport Mode",
+    "dateRange": "Date Range",
+    "fixed12Months": "The time range is fixed at 12 months.",
+    "salesConfigWarning": "*To ensure the accuracy of the data display, this report needs to be configured and displayed after communicating clearly with Sales.",
+    "kpiReportTip": "KPI Report: Day difference between actual and estimate.",
+    "pendingReportTip": "Pending Report: Showing shipments which are soon to depart/arrive.",
+    "etdToEtaTip": "ETD to ETA (Days): Distribution of Transit Time (ETA-ETD) for All Shipments in Last 12 Months.",
+    "containerCountTip": "Container Count: Total Container Volume by Month (Last 12 Months)",
+    "top10Tip": "Top 10 Origin & Destination: Last 12 Months Shipment Volume Rankings: Top 10 Origin Cities and Top 10 Destination Cities",
+    "co2eTip": "CO2e Emission by Origin or Destination: Last 12 Months CO2e Emission Rankings: Top 10 Origin Cities and Top 10 Destination Cities",
+    "recentStatusTip": "Recent Status: Active shipment list with ETD within the past three months and the next month.",
+    "revenueSpentTip": "Revenue Spent: Based on the billto object, display the corresponding revenue data.",
+    "revenueSpentTitle": "Revenue Spent",
+    "departure": "Departure",
+    "arrival": "Arrival",
+    "satisfactionQuestionWithSpace": "Satisfaction Question ",
+    "shareYourFeedback": "Share your feedback",
+    "feedbackPlaceholder": "Please enter your feedback.",
+    "submitSuccessful": "Submit successfully",
+    "scoringSorryText": "We are so sorry for the inconveniences. We value your experience immensely.",
+    "scoringDissatisfiedTip": "Could you please tell us which aspects of the system you are dissatisfied with?",
+    "scoringNeutralText": "Thank you for sharing your thoughts with us. We value your experience greatly.",
+    "scoringNeutralProposal": "Could you share what aspects you liked and what could be improved?",
+    "scoringSatisfiedText": "Thank you very much for giving us a satisfied rating on our service or system.",
+    "scoringSatisfiedProposal": "We are curious to learn more about what specifically made you feel satisfied.",
+    "highlyDissatisfied": "Highly dissatisfied",
+    "dissatisfied": "Dissatisfied",
+    "neutral": "Neutral",
+    "satisfied": "Satisfied",
+    "highlySatisfied": "Highly satisfied",
+    "satisfactionDetailsQuestion": "Would you like to share more details with us?",
+    "previous": "Previous",
+    "submit": "Submit",
+    "submitAndCommitment": "We are committed to working hard to provide better services.",
+    "apologizeAgain": "Apologize once again for your experience.",
+    "appreciateFeedback": "We greatly appreciate your valuable time and feedback.",
+    "thankYouPositive": "Once again, thank you for your positive evaluation."
+  },
+  "booking": {
+    "title": "Booking",
+    "sendEmail": "Send Email",
+    "search": "Search",
+    "selectDataOnBookingList": "Select data on your booking list",
+    "download": "Download",
+    "downloadWithSelectedColumns": "Download with selected columns",
+    "emailSentSuccessfully": "Email sent successfully",
+    "enterRefNoPlaceholder": "Enter Ref No.",
+    "failedToSendEmail": "Failed to send email",
+    "pleaseEnterEmailContent": "Please enter the email content",
+    "refNoDialogTitle": "Ref No.",
+    "searchPlaceholder": "Enter Booking/HBL/PO/Container/Carrier Booking No. ",
+    "selectReferenceTypePlaceholder": "Select Reference Type",
+    "selected": "Selected",
+    "total": "Total",
+    "transportMode": "Transport Mode",
+    "shipmentStatus": "Shipment Status",
+    "etd": "ETD",
+    "textSearch": "text search",
+    "clearFilters": "Clear Filters",
+    "bookingNo": "Booking No.",
+    "hawbHblNo": "HAWB/HBL No.",
+    "carrierBookingNo": "Carrier Booking No.",
+    "poNo": "PO No.",
+    "refNo": "Ref No.",
+    "vesselAirline": "Vessel / Airline",
+    "voyageFlight": "Voyage / Flight",
+    "incoterm": "Incoterm",
+    "serviceType": "Service Type",
+    "shipper": "Shipper",
+    "consignee": "Consignee",
+    "originAgent": "Origin Agent",
+    "destinationAgent": "Destination Agent",
+    "quantityUnit": "Quantity / Unit",
+    "gWeight": "G. Weight",
+    "chWeight": "Ch. Weight",
+    "volume": "Volume",
+    "marks": "Marks",
+    "description": "Description",
+    "packing": "Packing",
+    "marksAndDescription": "Marks and Description",
+    "bookingListEmptyTip": "We support the following references number to find booking:",
+    "bookingListEmptyTipDetail": "· Booking No./HAWB No./MAWB No./PO No./Carrier Booking No./Contract No./File No./Quote No.",
+    "noResultsFound": "No Results Found",
+    "basicInformation": "Basic Information",
+    "businessPartners": "Business Partners",
+    "containers": "Containers",
+    "origin": "Origin",
+    "destination": "Destination",
+    "etdAtd": "ETD/ATD",
+    "etaAta": "ETA/ATA",
+    "guideSearchAreaTitle": "Frequently Used Search Criteria Display Area",
+    "guideSearchAreaStatus": "Key Booking Status",
+    "guideSearchAreaReferences": "Reference Numbers",
+    "guideSearchAreaTransportMode": "Transport Mode",
+    "guideSearchAreaDateRange": "Date Type & Range",
+    "guideSelectedCriteria": "Selected query criteria display area",
+    "guideClearCriteria": "You can quickly clear selected conditions at this position",
+    "guideMoreFilters": "Click \"More Filters\" to see more search options.",
+    "guideOpenDetail": "Click on the Booking No. or double-click anywhere on a single booking data to enter the detailed page.",
+    "guideDownloadTipLine1": "View the number of shipments selected for download",
+    "guideDownloadTipLine2": "Two download list templates are available",
+    "guideCustomizeColumns": "Drag to right to add columns",
+    "guideCustomizeColumnsLeft": "Drag to left to unselect columns",
+    "guideCustomizeColumnsReorder": "Drag up and down to reorder or select/unselect",
+    "guidePageGuide": "After closing, you can still click the \"Page Guide\" button to view the page guide of the current page.",
+    "guideDashboardIntro": "The Dashboard integrates different types of reports. You can freely select the reports you need.",
+    "guideDashboardFilter": "Each report comes with its own filter options. Simply make your selection and click \"Search\" to refresh the data.",
+    "guideDashboardTooltip": "Hover the icon next to the report name to view the data explanation for each report.",
+    "guideDashboardDrag": "Drag the icon to rearrange the report cards on the dashboard.",
+    "guideDashboardSaveLayout": "Click \"Save Layout\" to preserve your customized report arrangement.",
+    "openInNewTab": "Open in New Tab",
+    "customizeColumnsTip": "Drag item over to this selection or click \"add\" icon to show the column on your booking list",
+    "tdReferenceType": "Reference Type",
+    "tdAction": "Action"
+  },
+  "destinationDelivery": {
+    "title": "Destination Delivery",
+    "configurations": "Configurations",
+    "calendarView": "Calendar View",
+    "listView": "List View",
+    "createNewBooking": "Create New Booking",
+    "truck": "Truck",
+    "rail": "Rail",
+    "totalBookings": "Total Bookings",
+    "pendingApproval": "Pending Approval",
+    "approved": "Approved",
+    "rejected": "Rejected",
+    "cancelled": "Cancelled",
+    "bookingList": "Booking List",
+    "selectDataOnOperationLogList": "Select data on your Operation Log list:",
+    "downloadWithSelectedColumns": "Download with selected columns",
+    "addNewDeliveryAddressTitle": "Add New Delivery Address",
+    "addressLine1Placeholder": "Line 1",
+    "addressLine2Placeholder": "Line 2",
+    "addressLine3Placeholder": "Line 3",
+    "addressLine4Placeholder": "Line 4",
+    "selectCountry": "Select Country",
+    "selectCity": "Select City",
+    "enterPostalCode": "Enter postal code",
+    "contactNamePlaceholder": "Name",
+    "contactMobilePlaceholder": "Mobile Numer",
+    "deliveryDate": "Delivery Date",
+    "deliveryMode": "Delivery Mode",
+    "creationDate": "Creation Date",
+    "searchQuestionPlaceholder": "Search Question Booking No, HBOL No, MBL No, Container No, Consignee",
+    "communication": "Communication",
+    "communicateWithUs": "Communicate with us",
+    "separatedBySemicolon": "Separated by ;",
+    "sendEmail": "Send Email",
+    "pleaseEnterEmailContent": "Please enter the email content",
+    "emailSentSuccessfully": "Email sent successfully",
+    "failedToSendEmail": "Failed to send email",
+    "emptyBookingTitle": "You haven't created any destination delivery bookings yet.",
+    "emptyBookingLine1": "Book truck or rail delivery for your shipments to save time and",
+    "emptyBookingLine2": "ensure smooth last-mile delivery.",
+    "modifyBooking": "Modify Booking",
+    "selected": "Selected",
+    "submit": "Submit",
+    "selectShipments": "Select Shipments",
+    "selectSameConsigneeTip": "Please select items with the same consignee.",
+    "selectSameConsigneeWarning": "Please select same consignee with same delivery information",
+    "searchBookingPlaceholder": "Enter Booking/HBL/PO/Carrier Booking No.",
+    "shipper": "Shipper",
+    "consignee": "Consignee",
+    "requiredFieldsNotEntered": "Required fields not entered.",
+    "outsideRecommendedPeriodForFollowing": "This date for following shipments is outside recommended period.",
+    "outsideRecommendedPeriod": "This date is outside our recommended period.",
+    "eta": "ETA",
+    "ata": "ATA",
+    "recommendedDeliveryDate": "Recommended Delivery Date",
+    "inputVesselName": "Input Vessel Name",
+    "vesselName": "Vessel Name",
+    "deliveryInformation": "Delivery Information",
+    "deliveryAddress": "Delivery Address",
+    "addressBook": "Address Book",
+    "addNewAddress": "Add New Address",
+    "modeType": "Mode Type",
+    "select": "Select",
+    "preferredDeliveryDate": "Preferred Delivery Date",
+    "pleaseSelectDate": "Please Select Date",
+    "deliveryTime": "Delivery Time",
+    "pleaseSelectTime": "Please Select Time",
+    "deliveryReference": "Delivery Reference",
+    "deliveryReferenceTooltip": "Reference to be quoted on arrival at the Warehouse/DC",
+    "specialRequirements": "Special Requirements",
+    "tailLiftRequired": "Tail Lift Required",
+    "sideLoading": "Side Loading",
+    "forkliftRequired": "Forklift Required",
+    "specialEquipment": "Special Equipment",
+    "additionalRequirementsPlaceholder": "Enter any additional requirements or notes...",
+    "modificationReason": "Modification Reason",
+    "createNewRule": "Create New Rule",
+    "completeRequiredFields": "Please complete all required fields.",
+    "unableToSave": "Unable to Save",
+    "setting": "Setting",
+    "selectStationEnableBooking": "Select Station (Enable Booking)",
+    "setBookingWindow": "Set Booking Window",
+    "klnPic": "KLN PIC",
+    "selectEmployeeAccount": "Select Employee Account",
+    "modifyRule": "Modify Rule",
+    "unsavedChanges": "There are unsaved changes.",
+    "confirmLeavePage": "Are you sure you want to leave this page?",
+    "unsavedChangesTitle": "Unsaved Changes",
+    "specificRule": "Specific Rule",
+    "singleDimension": "Single Dimension",
+    "defaultRule": "Default Rule",
+    "deliveryDateTips": "No Specific recommended time for choosing delivery date",
+    "recommendDeliveryDate": "Recommend Delivery Date (ETA/ATA)",
+    "etdAtdRuleWindow": "ETD/ATD: {before} days before to {after} days after",
+    "etaAtaRuleWindow": "ETA/ATA: {before} days before to {after} days after",
+    "ok": "OK",
+    "savingSuccess": "Saving successful",
+    "air": "Air",
+    "sea": "Sea",
+    "priority": "Priority",
+    "ruleType": "Rule Type",
+    "port": "Port",
+    "carrier": "Carrier",
+    "fromEtaAtaDays": "From (ETA/ATA + Days)",
+    "toEtaAtaDays": "To (ETA/ATA + Days)",
+    "add": "Add",
+    "configureRegionsAndTimeSlots": "Configure available destination delivery regions and time slots.",
+    "addRule": "Add Rule",
+    "deleteRule": "Delete Rule",
+    "confirmDeleteRule": "Are you sure to delete this notification event?",
+    "configuration": "Configuration",
+    "addedRules": "Added Rules",
+    "failedToLoadOptions": "Failed to load options",
+    "bookings": "Bookings",
+    "accountPasswordExpiredUnavailable": "This account's password has expired and is currently unavailable. Please select a different customer account to continue.",
+    "noTimeRestrictionsForBooking": "No Specific time restrictions for creating booking",
+    "bookingWindowEtdAtd": "Booking Window (ETD/ATD)",
+    "bookingWindowEtaAta": "Booking Window (ETA/ATA)",
+    "revenueSpentTitle": "Revenue Spent",
+    "daysBeforeTo": "days before to",
+    "daysAfter": "days after",
+    "stationList": "Station List",
+    "noData": "No Data",
+    "recommendedDeliveryShipmentsFor": "Recommended Delivery Shipments for {date}",
+    "freeStoragePeriodEnds": "Free Storage Period Ends",
+    "total": "Total",
+    "shipments": "shipments",
+    "totalCartons": "Total Cartons",
+    "packingList": "Packing List",
+    "bookingDetail": "Booking Detail",
+    "confirmRejectBooking": "Are you sure you want to Reject Booking ",
+    "confirmApproveBooking": "Are you sure you want to Approve Booking ",
+    "confirmCancelBooking": "Are you sure you want to Cancel Booking ",
+    "approve": "Approve",
+    "reject": "Reject",
+    "rejectRemarkRequired": "A remark must be filled in for the rejection operation.",
+    "actionSuccessfully": "successfully",
+    "actionFailedTryAgain": "failed, Please try again.",
+    "inputRemarks": "Input remarks",
+    "booking": "Booking",
+    "serviceNotAvailableTitle": "Destination Delivery Service Not Available",
+    "serviceNotAvailableText": "Destination delivery service is not yet available for your shipment destinations. To request this service, please contact the destination office first.",
+    "noEligibleShipmentsTitle": "No Eligible Shipments for Booking",
+    "noEligibleShipmentsText": "Your shipments are currently outside the booking window. Eligible shipments will appear here when booking window opens.",
+    "additionalStorageFeesMayApply": "Additional storage fees may apply.",
+    "additionalFeesNotice": "Additional Fees Notice"
+  }
+}

+ 2 - 0
src/main.ts

@@ -24,6 +24,7 @@ import { createPinia } from 'pinia'
 
 import App from './App.vue'
 import router from './router'
+import i18n from './locales'
 import { useThemeStore } from '@/stores/modules/theme'
 import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
 
@@ -48,6 +49,7 @@ app.use(createPinia())
 const pinia = createPinia()
 pinia.use(piniaPluginPersistedstate)
 app.use(pinia)
+app.use(i18n)
 app.use(VXETable)
 app.use(VxeUI)
 app.use(router)

+ 71 - 30
src/router/index.ts

@@ -3,7 +3,9 @@ import { useUserStore } from '@/stores/modules/user'
 import { useBreadCrumb } from '@/stores/modules/breadCrumb'
 import { useHeaderSearch } from '@/stores/modules/headerSearch'
 import { useFiltersStore } from '@/stores/modules/filtersList'
+import i18n from '@/locales/index'
 
+const t = i18n.global.t
 const router = createRouter({
   history: createWebHistory(`${import.meta.env.VITE_BASE_URL}`),
   routes: [
@@ -16,17 +18,26 @@ const router = createRouter({
         {
           path: '/dashboard',
           name: 'Dashboard',
-          component: () => import('../views/Dashboard')
+          component: () => import('../views/Dashboard'),
+          meta: {
+            breadName: t('menu.dashboard')
+          }
         },
         {
           path: '/booking',
           name: 'Booking',
-          component: () => import('../views/Booking')
+          component: () => import('../views/Booking'),
+          meta: {
+            breadName: t('menu.booking')
+          }
         },
         {
           path: '/report',
           name: 'Report Management',
-          component: () => import('../views/Report')
+          component: () => import('../views/Report'),
+          meta: {
+            breadName: t('menu.reportManagement')
+          }
         },
         {
           path: '/report/detail',
@@ -34,7 +45,7 @@ const router = createRouter({
           component: () => import('../views/Report/src/components/ReportDetail'),
           meta: {
             activeMenu: '/report',
-            breadName: 'Detail'
+            breadName: t('menu.reportDetail')
           }
         },
         {
@@ -43,7 +54,7 @@ const router = createRouter({
           component: () => import('../views/Report/src/components/ReportSchedule'),
           meta: {
             activeMenu: '/report',
-            breadName: 'Schedule Configuration'
+            breadName: t('menu.reportSchedule')
           }
         },
         {
@@ -51,20 +62,25 @@ const router = createRouter({
           name: 'Booking Detail',
           component: () => import('../views/Booking/src/components/BookingDetail'),
           meta: {
-            activeMenu: '/booking'
+            activeMenu: '/booking',
+            breadName: t('menu.bookingDetail')
           }
         },
         {
           path: '/tracking',
           name: 'Tracking',
-          component: () => import('../views/Tracking')
+          component: () => import('../views/Tracking'),
+          meta: {
+            breadName: t('menu.tracking')
+          }
         },
         {
           path: '/tracking/detail',
           name: 'Tracking Detail',
           component: () => import('../views/Tracking/src/components/TrackingDetail'),
           meta: {
-            activeMenu: '/tracking'
+            activeMenu: '/tracking',
+            breadName: t('menu.trackingDetail')
           }
         },
         {
@@ -72,7 +88,9 @@ const router = createRouter({
           name: 'Tracking Download Attachment',
           component: () => import('../views/Tracking/src/components/DownloadAttachment'),
           meta: {
-            activeMenu: '/tracking'
+            activeMenu: '/tracking',
+            breadName: t('menu.trackingDownloadAttachment')
+
           }
         },
         {
@@ -80,7 +98,8 @@ const router = createRouter({
           name: 'Shipment Detail',
           component: () => import('../views/Tracking/src/components/TrackingDetail'),
           meta: {
-            activeMenu: '/tracking'
+            activeMenu: '/tracking',
+            breadName: t('menu.shipmentDetail')
           }
         },
         {
@@ -89,7 +108,8 @@ const router = createRouter({
           component: () =>
             import('../views/Tracking/src/components/TrackingTable/src/components/VGMView.vue'),
           meta: {
-            activeMenu: '/tracking'
+            activeMenu: '/tracking',
+            breadName: t('menu.addVGM')
           }
         },
         {
@@ -97,7 +117,8 @@ const router = createRouter({
           name: 'Public Tracking',
           component: () => import('../views/Tracking/src/components/PublicTracking'),
           meta: {
-            activeMenu: '/tracking'
+            activeMenu: '/tracking',
+            breadName: t('menu.publicTracking')
           }
         },
         {
@@ -108,7 +129,9 @@ const router = createRouter({
               '../views/Tracking/src/components/PublicTracking/src/components/PublicTrackingDetail.vue'
             ),
           meta: {
-            activeMenu: '/tracking'
+            activeMenu: '/tracking',
+            breadName: t('menu.publicTrackingDetail')
+
           }
         },
         {
@@ -116,18 +139,22 @@ const router = createRouter({
           name: 'Login',
           component: () => import('../views/Login'),
           meta: {
-            activeMenu: '/tracking'
+            activeMenu: '/tracking',
+            breadName: t('menu.login')
           }
         },
         {
           path: '/reset-password',
           name: 'Reset Password',
-          component: () => import('../views/Login/src/components/ChangePasswordCard.vue')
+          component: () => import('../views/Login/src/components/ChangePasswordCard.vue'),
+          meta: {
+            breadName: t('menu.resetPassword')
+          }
         },
         {
           path: '/operation-log',
           name: 'Operation log',
-          component: () => import('../views/OperationLog')
+          component: () => import('../views/OperationLog'),
         },
         {
           path: '/chat-log',
@@ -160,54 +187,68 @@ const router = createRouter({
         {
           path: '/system-message',
           name: 'System Message',
-          component: () => import('../views/SystemMessage')
+          component: () => import('../views/SystemMessage'),
+          meta: {
+            breadName: t('menu.systemMessage')
+          }
         },
         {
           path: '/system-message-detail',
           name: 'System Message Detail',
+          component: () => import('../views/SystemMessage/src/components/SystemMessageDetail.vue'),
           meta: {
-            breadName: 'Detail',
+            breadName: t('menu.systemMessageDetail'),
             activeMenu: '/system-message'
           },
-          component: () => import('../views/SystemMessage/src/components/SystemMessageDetail.vue')
         },
         {
           path: '/system-settings',
           name: 'System Settings',
-          component: () => import('../views/SystemSettings')
+          component: () => import('../views/SystemSettings'),
+          meta: {
+            breadName: t('menu.systemSettings')
+          }
         },
         {
           path: '/system-settings/create-new-rule',
           name: 'Create New Rule',
           component: () => import('../views/SystemSettings/src/components/CreateNewrule'),
           meta: {
-            activeMenu: '/system-settings'
+            activeMenu: '/system-settings',
+            breadName: t('menu.createNewRule')
           }
         },
         {
           path: '/multilingual-config',
           name: 'Multilingual Config',
           component: () => import('../views/MultilingualConfig'),
-          meta: {
-            activeMenu: '/system-settings'
-          }
         },
         {
           path: '/destination-delivery',
           name: 'Destination Delivery',
-          component: () => import('../views/DestinationDelivery')
+          component: () => import('../views/DestinationDelivery'),
+          meta: {
+            breadName: t('menu.destinationDelivery')
+          }
+
         },
         {
           path: '/destination-delivery/create-new-booking',
           name: 'Create New Booking',
-          component: () => import('../views/DestinationDelivery/src/components/CreateNewBooking')
+          component: () => import('../views/DestinationDelivery/src/components/CreateNewBooking'),
+          meta: {
+            breadName: t('menu.createNewBooking'),
+            activeMenu: '/destination-delivery'
+          }
+
         },
         {
           path: '/destination-delivery/configurations',
           name: 'Configurations',
           component: () => import('../views/DestinationDelivery/src/components/ConfiguRations'),
           meta: {
-            activeMenu: '/destination-delivery'
+            activeMenu: '/destination-delivery',
+            breadName: t('menu.configurations')
           }
         },
         {
@@ -218,7 +259,9 @@ const router = createRouter({
               '../views/DestinationDelivery/src/components/ConfiguRations/src/components/CreateNewRule.vue'
             ),
           meta: {
-            activeMenu: '/destination-delivery'
+            activeMenu: '/destination-delivery',
+            breadName: t('menu.createNewRule')
+
           }
         },
         {
@@ -306,8 +349,6 @@ router.beforeEach(async (to, from, next) => {
     // 如果满足上述任一“保留”场景,则 NOT 清除 (返回 false)
     // 否则,清除 (返回 true)
 
-    console.log('路由守卫判断:', from)
-    console.log('路由TO', to)
     const shouldKeepFilters = isReturningFromDetail || isReturningFromVgm || isEnteringWhitelist || isRefresh;
 
     return !shouldKeepFilters;

+ 1 - 1
src/stores/modules/breadCrumb.ts

@@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
 
 interface Route {
   breadName: string
-
+  label: string
   path: string
   query?: string
 }

+ 5 - 4
src/stores/modules/filtersList.ts

@@ -63,7 +63,8 @@ export const useFiltersStore = defineStore('filtersStore', {
           }
           return {
             title: filter.title,
-            value: `${filter.title}: ${displayValue}`
+            value: `${filter.title}: ${displayValue}`,
+            key: filter.key
           }
         })
     }
@@ -71,7 +72,7 @@ export const useFiltersStore = defineStore('filtersStore', {
   actions: {
     getFilterIndexByKey(key: string) {
       return this.filtersList.findIndex(filter => {
-        return filter.key === key
+        return JSON.stringify(filter.key) === JSON.stringify(key)
       })
     },
     getFilterIndexByTitle(title: string) {
@@ -80,12 +81,12 @@ export const useFiltersStore = defineStore('filtersStore', {
       })
     },
     getFilterByKey(key: string) {
-      return this.filtersList.find(filter => filter.key === key)
+      return this.filtersList.find(filter => JSON.stringify(filter.key) === JSON.stringify(key))
     },
     getFilterByTitle(title: string) {
       return this.filtersList.find(filter => filter.title === title)
     },
-    deleteFilterByKey(key: string) {
+    deleteFilterByKey(key: string | string[]) {
       const index = this.getFilterIndexByKey(key)
       if (index !== -1) {
         this.filtersList.splice(index, 1)

+ 28 - 3
src/styles/theme.scss

@@ -79,9 +79,21 @@
   --color-tag-approved-bg: rgb(232, 251, 228);
   --color-tag-approved: rgb(91, 180, 98);
 
-  --color-tag-rejected-bg: rgba(201, 53, 63,0.2);
+  --color-tag-rejected-bg: #ffdcde;
   --color-tag-rejected: rgb(201, 53, 63);
 
+  --color-tag-checked-bg: rgb(255, 117, 0, 0.1);
+  --color-tag-chinese-simplified-bg: rgb(191, 102, 234,0.15);
+  --color-tag-chinese-simplified-text-color: #c9353f;
+  --color-tag-chinese-traditional-bg: #eaecff;
+  --color-tag-chinese-traditional-text-color: #bf66ea;
+  --color-tag-spanish-bg: rgb(158, 97, 19,0.15);
+  --color-tag-spanish-text-color: #9e6113;
+  --color-tag-portuguese-bg: rgb(74, 94, 40,0.15);
+  --color-tag-portuguese-text-color: #4a5e28;
+
+  --color-multilingual-page-select-item-tag-bg: #fff4d1;
+
   --color-border: #eaebed;
   --color-select-border: #eaebed;
   --border-color-2: #eaebed;
@@ -599,14 +611,27 @@
 
   --color-tag-unfinished-approval-bg: rgba(224, 161, 0, 0.2);
   --color-tag-approved-bg: rgba(91, 180, 98, 0.2);
-  --color-tag-rejected-bg: rgb(255, 220, 222);
+
+  --color-tag-rejected-bg: rgba(201, 53, 63,0.2);
+
+  --color-tag-checked-bg: rgb(255, 117, 0, 0.2);
+  --color-tag-chinese-simplified-bg: rgb(201, 53, 63,0.2);
+  --color-tag-chinese-simplified-text-color: #c9353f;
+  --color-tag-chinese-traditional-bg: rgb(191, 102, 234,0.2);
+  --color-tag-chinese-traditional-text-color: #bf66ea;
+  --color-tag-spanish-bg: rgb(158, 97, 19,0.3);
+  --color-tag-spanish-text-color: #9e6113;
+  --color-tag-portuguese-bg: rgb(168, 201, 122,0.3);
+  --color-tag-portuguese-text-color: #a8c97a;
+
+  --color-multilingual-page-select-item-tag-bg: rgb(224, 161, 0, 0.2);
 
   --color-card-icon-box-bg: #4f535c;
   --color-card-number-cancelled: #babcc0;
   --color-steps-unfinished-line: #6a6d73;
   --color-steps-current-icon-color: #e0a100;
   --color-steps-current-icon-bg: #534b30;
-  --color-steps-rejected-bg: #4f353d;
+  --color-steps-rejected-bg: rgba(201, 53, 63,0.2);
   --color-steps-approved-bg: #394e44;
   --color-steps-cancelled: #c2c4c7;
   --color-steps-cancelled-bg: rgba(240, 241, 243, 0.2);

+ 5 - 3
src/utils/table.ts

@@ -16,13 +16,15 @@ export const autoWidth = (tableData: VxeGridProps, grid: VxeGridInstance, custom
     let curStr = ''
     let width = 0
     const field = column.field
+    const language = localStorage.getItem('lang') || 'English'
+    const isChinese = language === 'Chinese (Simplified)' || language === 'Chinese (Traditional)'
     // 判断表头的宽度
     if (column.title.length < 12) {
-      width = column.title.length * 11 + 40
+      width = column.title.length * (isChinese ? 14 : 11) + 40
     } else if (column.title.length < 20) {
-      width = column.title.length * 10 + 30
+      width = column.title.length * (isChinese ? 13 : 10) + 30
     } else {
-      width = column.title.length * 7 + 40
+      width = column.title.length * (isChinese ? 10 : 7) + 40
     }
     const curData = data.length > 1000 ? data.slice(0, 1000) : data
     // 判断表格内容的宽度

+ 1 - 1
src/views/AIApiLog/src/components/LogDialog.vue

@@ -91,4 +91,4 @@ defineExpose({
     background-color: var(--color-json-item-hover);
   }
 }
-</style>
+</style>

+ 43 - 38
src/views/AIRobotChat/src/AIRobotChat.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import AutoResizeTextarea from './components/AutoResizeTextarea.vue'
 import LoadingDots from './components/LoadingDots.vue'
 import AIQuestions from './components/AIQuestions.vue'
@@ -65,7 +68,7 @@ const messages = ref<MessageItem[]>([
   {
     type: 'robot',
     isShowFeedback: false,
-    content: 'You can click on Frequently Asked Questions above or type your own question'
+    content: t('aiRobot.continueConversation')
   }
 ])
 messages.value = JSON.parse(sessionStorage.getItem('AIChat')) || messages.value
@@ -86,13 +89,13 @@ watch(
 const queryTime = ref(-1)
 // 当前用户问题回复进度
 const progressStatus = {
-  init: 'You can click on Frequently Asked Questions above or type your own question',
-  '0': 'Thinking about your question...',
-  '15': 'Searching for relevant data, please wait...',
-  '30': 'This query is complex and may take more time',
-  '60': 'You may try simplifying your question or selecting a Frequently Asked Question',
-  '120': 'Sorry, the query failed. Please try again later or select a Frequently Asked Question',
-  cancel: 'You have stopped this answer'
+  init: t('aiRobot.continueConversation'),
+  '0': t('aiRobot.thinkingAboutQuestion'),
+  '15': t('aiRobot.searchingRelevantDataPleaseWait'),
+  '30': t('aiRobot.queryIsComplexMayTakeMoreTime'),
+  '60': t('aiRobot.trySimplifyingQuestionOrSelectingFaq'),
+  '120': t('aiRobot.queryFailedPleaseTryAgainLaterOrSelectFaq'),
+  cancel: t('aiRobot.youHaveStoppedThisAnswer')
 }
 
 const isShowTips = ref(false) // 是否展示提示信息
@@ -322,7 +325,7 @@ const clearData = () => {
     {
       type: 'robot',
       isShowFeedback: false,
-      content: 'You can click on Frequently Asked Questions above or type your own question'
+      content: t('aiRobot.continueConversation')
     }
   ]
   progressInterval.value && clearInterval(progressInterval.value)
@@ -350,9 +353,13 @@ defineExpose({
   <div class="ai-robot" :style="{ width: modalSize === 'large' ? '1000px' : '484px' }">
     <div class="top-section" :class="[isShowHeaderShadow ? 'box-shadow' : '']">
       <div class="header">
-        <span class="welcome">Hi! I'm your Freight Assistant</span>
+        <span class="welcome">{{ t('aiRobot.greeting') }}</span>
         <div class="option-icon">
-          <el-tooltip v-if="modalSize === 'large'" trigger="hover" content="Sidebar Window">
+          <el-tooltip
+            v-if="modalSize === 'large'"
+            trigger="hover"
+            :content="t('aiRobot.sidebarWindow')"
+          >
             <el-button
               style="width: 24px; height: 24px"
               class="el-button--text"
@@ -361,7 +368,11 @@ defineExpose({
             ></el-button>
           </el-tooltip>
 
-          <el-tooltip v-else-if="modalSize !== 'large'" trigger="hover" content="Maximized Window">
+          <el-tooltip
+            v-else-if="modalSize !== 'large'"
+            trigger="hover"
+            :content="t('aiRobot.maximizedWindow')"
+          >
             <el-button
               style="width: 24px; height: 24px"
               class="el-button--text"
@@ -370,7 +381,7 @@ defineExpose({
             ></el-button>
           </el-tooltip>
 
-          <el-tooltip trigger="hover" content="Collapsed to Widget">
+          <el-tooltip trigger="hover" :content="t('aiRobot.collapsedToWidget')">
             <el-button
               style="width: 24px; height: 24px"
               class="el-button--text"
@@ -388,7 +399,7 @@ defineExpose({
         <div class="warning-bg">
           <span class="warning-icon font_family icon-icon_warning_fill_b"></span>
         </div>
-        <span>Answer in progress, please wait.</span>
+        <span>{{ t('aiRobot.answerInProgress') }}</span>
       </div>
     </div>
     <div class="chat-messages" ref="messagesRef" @scroll="handleScroll">
@@ -465,7 +476,7 @@ defineExpose({
         <!-- 暂停回答 icon -->
         <el-tooltip
           v-if="index === messages.length - 1 && queryTime > 29 && queryTime < 120"
-          content="Cancel Answer"
+          :content="t('aiRobot.cancelAnswer')"
           placement="bottom-start"
           effect="dark"
           ><div class="pause-btn" @click="handlePause">
@@ -487,7 +498,7 @@ defineExpose({
         <AutoResizeTextarea
           style="flex: 1"
           v-model="userQuestion"
-          :placeholder="'Type your question here...'"
+          :placeholder="t('aiRobot.typeYourQuestionHere')"
           @focus="isFooterInputFocus = true"
           @blur="isFooterInputFocus = false"
         />
@@ -500,14 +511,16 @@ defineExpose({
         </div>
       </div>
       <div class="liability-exemption">
-        <span>Content is generated by Al, please check carefully!</span>
-        <span class="liability-exemption-btn" @click="handleLiabilityExeDialog">Disclaimer</span>
+        <span>{{ t('aiRobot.contentGeneratedByAI') }}</span>
+        <span class="liability-exemption-btn" @click="handleLiabilityExeDialog">{{
+          t('aiRobot.disclaimerTitle')
+        }}</span>
       </div>
     </div>
     <el-dialog
       class="liability-exemption-dialog"
       v-model="liabilityExeDialog"
-      title="Disclaimer"
+      :title="t('aiRobot.disclaimerTitle')"
       width="800"
       top="20vh"
     >
@@ -515,34 +528,26 @@ defineExpose({
         class="title"
         style="margin-bottom: 8px; font-size: 16px; font-weight: 700; line-height: 24px"
       >
-        Important Notice: AI-Generated Content
+        {{ t('aiRobot.importantNoticeAI') }}
       </div>
+      <p>{{ t('aiRobot.disclaimerParagraph1') }}</p>
       <p>
-        This chat assistant is powered by artificial intelligence (AI) and is designed to help you
-        access information and answer your queries efficiently. Please be aware of the following:
-      </p>
-      <p>
-        <strong>• AI-Generated Responses: </strong>All responses are automatically generated by AI.
-        While we strive for accuracy, errors or inaccuracies may occur. Please verify critical
-        information independently before making business decisions.
-      </p>
-      <p>
-        <strong>• Data Privacy & Security:</strong> You can only access shipment data within your
-        authorized account permissions. Your data remains confidential and will not be shared with
-        other users or third parties.
+        <strong>• {{ t('aiRobot.disclaimerBullet1Title') }}</strong
+        >{{ t('aiRobot.disclaimerBullet1Text') }}
       </p>
       <p>
-        <strong>• Information Accuracy: </strong> For critical business decisions or time-sensitive
-        matters, we recommend contacting our customer service team directly for verification.
+        <strong>• {{ t('aiRobot.disclaimerBullet2Title') }}</strong
+        >{{ t('aiRobot.disclaimerBullet2Text') }}
       </p>
       <p>
-        <strong>• Service Limitations: </strong> This assistant provides general guidance and data
-        queries. For complex or specialized requests, please reach out to our support team.
+        <strong>• {{ t('aiRobot.disclaimerBullet3Title') }}</strong
+        >{{ t('aiRobot.disclaimerBullet3Text') }}
       </p>
       <p>
-        By using this AI assistant, you acknowledge these limitations and agree to use the
-        information provided accordingly.
+        <strong>• {{ t('aiRobot.disclaimerBullet4Title') }}</strong
+        >{{ t('aiRobot.disclaimerBullet4Text') }}
       </p>
+      <p>{{ t('aiRobot.disclaimerParagraph2') }}</p>
     </el-dialog>
   </div>
 </template>

+ 4 - 1
src/views/AIRobotChat/src/components/AIQuestions.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { ref, onMounted } from 'vue'
 import emitter from '@/utils/bus'
 
@@ -118,7 +121,7 @@ const clickQuestion = (question) => {
             src="../image/icon_ai_robot48_b@2x.png"
             width="48px"
           />
-          <div class="dialogue_title_left_text">Frequently Asked Questions</div>
+          <div class="dialogue_title_left_text">{{ t('aiRobot.frequentlyAskedQuestions') }}</div>
         </div>
       </div>
       <el-carousel

+ 13 - 10
src/views/Booking/src/BookingView.vue

@@ -4,12 +4,15 @@ import TransportMode from '@/components/TransportMode'
 import BookingTable from './components/BookingTable'
 import DateRange from '@/components/DateRange'
 import MoreFilters from '@/components/MoreFilters'
-import { ref, reactive } from 'vue'
+import { ref } from 'vue'
+import { useI18n } from 'vue-i18n'
 import { useCalculatingHeight } from '@/hooks/calculatingHeight'
 import { useUserStore } from '@/stores/modules/user'
 import dayjs from 'dayjs'
 import { useFiltersStore } from '@/stores/modules/filtersList'
 
+const { t } = useI18n()
+
 const userStore = useUserStore()
 const formatDate = userStore.dateFormat
 const filtersStore = useFiltersStore()
@@ -53,7 +56,7 @@ const tableLoadingTableData = ref(false)
 const initPage = () => {
   if (!filtersList.value || (filtersList.value && filtersList.value.length == 0)) {
     filtersStore.updateFilter({
-      title: 'Transport Mode',
+      title: 'transport_mode',
       value: ['All'],
       keyType: 'array',
       key: 'transport_mode'
@@ -152,7 +155,7 @@ const tabChange = (changeTabList: any) => {
 // 点击search按钮
 const SearchInput = () => {
   filtersStore.updateFilter({
-    title: 'text search',
+    title: 'Text Search',
     value: textSearch.value,
     keyType: 'normal',
     key: '_textSearch',
@@ -189,7 +192,7 @@ const handleSearch = () => {
 
 <template>
   <div class="Title">
-    <span>Booking</span>
+    <span>{{ t('booking.title') }}</span>
     <VDriverGuide @click="handleGuide"></VDriverGuide>
   </div>
   <BookingGuide ref="bookingGuideRef"></BookingGuide>
@@ -200,7 +203,7 @@ const handleSearch = () => {
         <div class="heaer_top">
           <div class="search">
             <el-input
-              placeholder="Enter Booking/HBL/PO/Carrier Booking No. "
+              :placeholder="t('booking.searchPlaceholder')"
               v-model="textSearch"
               class="log_input"
               @keyup.enter="SearchInput"
@@ -218,7 +221,7 @@ const handleSearch = () => {
                   :offset="6"
                   popper-class="ShowAlerIcon"
                   effect="dark"
-                  content="We support the following references number to find bookings:· Booking No./HAWB No./MAWB No./PO No./Carrier Booking No./Contract No./File No./Quote No."
+                  :content="t('booking.bookingListEmptyTipLine1')"
                   placement="bottom"
                 >
                   <span class="iconfont_icon iconfont_icon_tip">
@@ -245,9 +248,9 @@ const handleSearch = () => {
         @clearMoreFiltersTags="clearMoreFiltersTags"
         :isShowMoreFiltersGuidePhoto="guideStore.booking.isShowMoreFiltersGuidePhoto"
       ></MoreFilters>
-      <el-button class="el-button--dark" style="margin-left: 8px" @click="SearchInput"
-        >Search</el-button
-      >
+      <el-button class="el-button--dark" style="margin-left: 8px" @click="SearchInput">{{
+        t('booking.search')
+      }}</el-button>
     </div>
     <!-- 筛选项 -->
     <div class="filtersTag" v-if="filtersStore.getTagsList.length" id="booking-filter-tag-guide">
@@ -272,7 +275,7 @@ const handleSearch = () => {
         </el-tooltip>
         <div v-else>{{ tag.value }}</div>
       </el-tag>
-      <div class="text_button" @click="clearfilters">Clear Filters</div>
+      <div class="text_button" @click="clearfilters">{{ t('common.clearFilters') }}</div>
     </div>
   </div>
   <BookingTable

+ 12 - 10
src/views/Booking/src/components/BookingDetail/src/BookingDetail.vue

@@ -9,15 +9,17 @@ import { useRoute } from 'vue-router'
 import { useOverflow } from '@/hooks/useOverflow'
 import { formatTimezone } from '@/utils/tools'
 import { useThemeStore } from '@/stores/modules/theme'
+import { useI18n } from 'vue-i18n'
 
+const { t } = useI18n()
 const route = useRoute()
 
 const themeStore = useThemeStore()
 
 // 可拖拽模块的列表
 const boxList = ref([
-  { id: 1, name: 'Basic Information' },
-  { id: 2, name: 'Containers' }
+  { id: 1, name: t('booking.basicInformation') },
+  { id: 2, name: t('booking.containers') }
 ])
 
 const infoContentRef = ref()
@@ -104,7 +106,7 @@ const { isOverflow: isDestinationOverflow } = useOverflow(destinationRef, allDat
           :class="[`icon-${transportationMode?.[allData?.transportInfo?.mode]}`]"
           style="font-size: 64px"
         ></span>
-        <div class="no">Booking No. {{ allData?.transportInfo?.['bookingNo.'] }}</div>
+        <div class="no">{{ t('booking.bookingNo') }} {{ allData?.transportInfo?.['bookingNo.'] }}</div>
         <VTag large :type="allData?.transportInfo?.status">{{
           allData?.transportInfo?.status
         }}</VTag>
@@ -112,7 +114,7 @@ const { isOverflow: isDestinationOverflow } = useOverflow(destinationRef, allDat
       <div class="detail-info">
         <div class="item transport-way">
           <div class="origin">
-            <div class="title">Origin</div>
+            <div class="title">{{ t('booking.origin') }}</div>
             <div class="content">
               <el-tooltip v-if="isOriginOverflow" placement="top">
                 <template #content>{{ allData?.transportInfo?.origin || '--' }}</template>
@@ -130,7 +132,7 @@ const { isOverflow: isDestinationOverflow } = useOverflow(destinationRef, allDat
             </div>
           </div>
           <div class="destination">
-            <div class="title">Destination</div>
+            <div class="title">{{ t('booking.destination') }}</div>
             <div class="content">
               <el-tooltip v-if="isDestinationOverflow" placement="top">
                 <template #content>{{ allData?.transportInfo?.destination || '--' }}</template>
@@ -145,7 +147,7 @@ const { isOverflow: isDestinationOverflow } = useOverflow(destinationRef, allDat
           </div>
         </div>
         <div class="item">
-          <div class="title">ETD/ATD</div>
+          <div class="title">{{ t('booking.etdAtd') }}</div>
           <div class="content">
             <span
               >{{
@@ -159,7 +161,7 @@ const { isOverflow: isDestinationOverflow } = useOverflow(destinationRef, allDat
           </div>
         </div>
         <div class="item">
-          <div class="title">ETA/ATA</div>
+          <div class="title">{{ t('booking.etaAta') }}</div>
           <div class="content">
             <span
               >{{
@@ -196,13 +198,13 @@ const { isOverflow: isDestinationOverflow } = useOverflow(destinationRef, allDat
               <template #header>
                 <div style="display: flex; border-radius: 12px 12px 0 0">
                   <div class="basic-information-header">
-                    <span>Basic Information</span>
+                    <span>{{ t('booking.basicInformation') }}</span>
                     <SeeAllIcon
                       v-model="isBusinessPartnersCollapse"
                       @collapse="handleBusinessPartnersCollapse"
                     ></SeeAllIcon>
                   </div>
-                  <div style="padding-left: 16px">Business Partners</div>
+                  <div style="padding-left: 16px">{{ t('booking.businessPartners') }}</div>
                 </div>
               </template>
               <template #content>
@@ -214,7 +216,7 @@ const { isOverflow: isDestinationOverflow } = useOverflow(destinationRef, allDat
           <!-- Containers -->
           <div v-if="item.id === 2 && allData?.transportInfo?.mode !== 'Air Freight'">
             <VBox :id="item.id" :isSeeAll="false" @draggable="handleDraggable">
-              <template #header>Containers</template>
+              <template #header>{{ t('booking.containers') }}</template>
               <template #content>
                 <ContainersView :data="allData"></ContainersView>
               </template>

+ 21 - 8
src/views/Booking/src/components/BookingDetail/src/components/AddReferenceDialog.vue

@@ -1,5 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
 import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
+const { t } = useI18n()
 const dialogVisible = ref(false)
 
 const openDialog = () => {
@@ -24,20 +26,20 @@ const tableData = ref<VxeGridProps<any>>({
   minHeight: 100,
   columns: [
     {
-      title: 'Reference Type',
+      title: t('booking.tdReferenceType'),
       field: 'referenceType',
       width: 250,
       editRender: { enabled: true },
       slots: { edit: 'refType' }
     },
     {
-      title: 'Ref No.',
+      title: t('booking.refNo'),
       field: 'refNo',
       editRender: { enabled: true },
       slots: { edit: 'refNo' }
     },
     {
-      title: 'Action',
+      title: t('booking.tdAction'),
       width: 90,
       fixed: 'left',
       slots: { default: 'action' }
@@ -98,11 +100,16 @@ defineExpose({
 </script>
 
 <template>
-  <el-dialog v-model="dialogVisible" :width="800" title="Ref No." @closed="clearData">
+  <el-dialog
+    v-model="dialogVisible"
+    :width="800"
+    :title="t('booking.refNoDialogTitle')"
+    @closed="clearData"
+  >
     <div>
       <el-button style="margin-bottom: 8px" class="el-button--text" @click="addNewReference">
         <span class="font_family icon-icon_add_b"></span>
-        Add Reference
+        {{ t('tracking.tdAddReference') }}
       </el-button>
       <vxe-grid ref="tableRef" v-bind="tableData">
         <!-- action操作栏的插槽 -->
@@ -123,7 +130,10 @@ defineExpose({
         </template>
         <!-- Reference Type的编辑插槽 -->
         <template #refType="{ row, column }">
-          <el-select v-model="row[column.field]" placeholder="Select Reference Type">
+          <el-select
+            v-model="row[column.field]"
+            :placeholder="t('booking.selectReferenceTypePlaceholder')"
+          >
             <el-option
               v-for="num in 15"
               :key="num"
@@ -134,10 +144,13 @@ defineExpose({
         </template>
         <!-- Ref No.的编辑插槽 -->
         <template #refNo="{ row, column }">
-          <el-input v-model="row[column.field]" placeholder="Enter Ref No." />
+          <el-input
+            v-model="row[column.field]"
+            :placeholder="t('booking.enterRefNoPlaceholder')"
+          />
         </template>
         <template>
-          <div>No data</div>
+          <div>{{ t('common.noData') }}</div>
         </template>
       </vxe-grid>
     </div>

+ 47 - 44
src/views/Booking/src/components/BookingDetail/src/components/BasicInformation.vue

@@ -1,9 +1,12 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
 import { useRouter } from 'vue-router'
 import XEClipboard from 'xe-clipboard'
 import AddReferenceDialog from './AddReferenceDialog.vue'
 import { formatNumber } from '@/utils/tools'
 
+const { t } = useI18n()
+
 const router = useRouter()
 
 const props = defineProps({
@@ -13,67 +16,67 @@ const allData: any = ref({
   basicInformation: {
     top: [
       {
-        label: 'Booking No.',
+        label: t('booking.bookingNo'),
         content: ''
       },
       {
-        label: 'HAWB/HBL No.',
+        label: t('booking.hawbHblNo'),
         content: '',
         type: 'link'
       },
       {
-        label: 'Carrier Booking No.',
+        label: t('booking.carrierBookingNo'),
         content: ''
       },
       {
-        label: 'PO No.',
+        label: t('booking.poNo'),
         content: ''
       },
       {
-        label: 'Ref No.',
+        label: t('booking.refNo'),
         content: []
       }
     ],
     bottom: [
       {
-        label: 'Vessel / Airline',
+        label: t('booking.vesselAirline'),
         content: ''
       },
       {
-        label: 'Voyage / Flight',
+        label: t('booking.voyageFlight'),
         content: ''
       },
       {
-        label: 'Incoterm',
+        label: t('booking.incoterm'),
         content: ''
       },
       {
-        label: 'Service Type',
+        label: t('booking.serviceType'),
         content: ''
       }
     ]
   },
   businessPartners: [
     {
-      title: 'Shipper',
+      title: t('booking.shipper'),
       company: '',
       address: '',
       phone: ''
     },
     {
-      title: 'Consignee',
+      title: t('booking.consignee'),
       company: '',
       address: '',
       phone: ''
     },
     {
-      title: 'Origin Agent',
+      title: t('booking.originAgent'),
       company: '',
       address: '',
       phone: ''
     },
     {
-      title: 'Destination Agent',
+      title: t('booking.destinationAgent'),
       company: '',
       address: '',
       phone: ''
@@ -81,29 +84,29 @@ const allData: any = ref({
   ],
   packing: [
     {
-      label: 'Quantity / Unit',
+      label: t('booking.quantityUnit'),
       content: ''
     },
     {
-      label: 'G. Weight',
+      label: t('booking.gWeight'),
       content: ''
     },
     {
-      label: 'Ch. Weight',
+      label: t('booking.chWeight'),
       content: ''
     },
     {
-      label: 'Volume',
+      label: t('booking.volume'),
       content: ''
     }
   ],
   marksAndDescription: [
     {
-      label: 'Marks',
+      label: t('booking.marks'),
       content: ''
     },
     {
-      label: 'Description',
+      label: t('booking.description'),
       content: ''
     }
   ]
@@ -122,42 +125,42 @@ const convertData = (data: any) => {
     basicInformation: {
       top: [
         {
-          label: 'Booking No.',
+          label: t('booking.bookingNo'),
           content: data.basicInfo['bookingNo.'] || '--'
         },
         {
-          label: 'HAWB/HBL No.',
+          label: t('booking.hawbHblNo'),
           content: data.basicInfo['HAWB/HBOL'] || '--',
           type: 'link'
         },
         {
-          label: 'Carrier Booking No.',
+          label: t('booking.carrierBookingNo'),
           content: data.basicInfo['Carrier_Booking_No'] || '--'
         },
         {
-          label: 'PO No.',
+          label: t('booking.poNo'),
           content: data.basicInfo['PO_NO'] || '--'
         },
         {
-          label: 'Ref No.',
+          label: t('booking.refNo'),
           content: data.ref_no
         }
       ],
       bottom: [
         {
-          label: 'Vessel / Airline',
+          label: t('booking.vesselAirline'),
           content: data.basicInfo['Vessel/Airline'] || '--'
         },
         {
-          label: 'Voyage / Flight',
+          label: t('booking.voyageFlight'),
           content: data.basicInfo['Voyage/Filght'] || '--'
         },
         {
-          label: 'Incoterm',
+          label: t('booking.incoterm'),
           content: data.basicInfo['Incoterm'] || '--'
         },
         {
-          label: 'Service Type',
+          label: t('booking.serviceType'),
           content: data.basicInfo['Service_Type'] || '--'
         }
       ],
@@ -165,25 +168,25 @@ const convertData = (data: any) => {
     },
     businessPartners: [
       {
-        title: 'Shipper',
+        title: t('booking.shipper'),
         company: data.businessPartners.shipper.company || '--',
         address: data.businessPartners.shipper.address || '--',
         phone: data.businessPartners.shipper.phone || '--'
       },
       {
-        title: 'Consignee',
+        title: t('booking.consignee'),
         company: data.businessPartners.consignee.company || '--',
         address: data.businessPartners.consignee.address || '--',
         phone: data.businessPartners.consignee.phone || '--'
       },
       {
-        title: 'Origin Agent',
+        title: t('booking.originAgent'),
         company: data.businessPartners.origin.company || '--',
         address: data.businessPartners.origin.address || '--',
         phone: data.businessPartners.origin.phone || '--'
       },
       {
-        title: 'Destination Agent',
+        title: t('booking.destinationAgent'),
         company: data.businessPartners.destination.company || '--',
         address: data.businessPartners.destination.address || '--',
         phone: data.businessPartners.destination.phone || '--'
@@ -191,29 +194,29 @@ const convertData = (data: any) => {
     ],
     packing: [
       {
-        label: 'Quantity / Unit',
+        label: t('booking.quantityUnit'),
         content: data.packing['Quantity/Unit'] || '--'
       },
       {
-        label: 'G. Weight',
+        label: t('booking.gWeight'),
         content: substringFromStart(data.packing['G. Weight'], -4) + ' KGS'
       },
       {
-        label: 'Ch. Weight',
+        label: t('booking.chWeight'),
         content: substringFromStart(data.packing['Ch. Weight'], -4) + ' KGS'
       },
       {
-        label: 'Volume',
+        label: t('booking.volume'),
         content: substringFromStart(data.packing['Volume'], -4) + ' CBM'
       }
     ],
     marksAndDescription: [
       {
-        label: 'Marks',
+        label: t('booking.marks'),
         content: data.marksAndDescription.marsk || '--'
       },
       {
-        label: 'Description',
+        label: t('booking.description'),
         content: data.marksAndDescription.description || '--'
       }
     ]
@@ -289,7 +292,7 @@ const handleCopy = (data: any) => {
   }) // 用空格替换换行符
   const copyText = sanitizedData.join('\n') // 拼接为单行文本
   if (XEClipboard.copy(copyText)) {
-    ElMessage.success('Copy success')
+    ElMessage.success(t('common.copySuccess'))
   }
 }
 
@@ -353,7 +356,7 @@ defineExpose({
             </div>
           </template>
           <template v-else>
-            <div class="content">--</div>
+            <div class="content">{{ t('common.noData') }}</div>
           </template>
         </template>
       </div>
@@ -396,7 +399,7 @@ defineExpose({
       </div>
 
       <div class="packing">
-        <div class="header">Packing</div>
+        <div class="header">{{ t('booking.packing') }}</div>
         <div class="content">
           <div class="data-item" v-for="item in allData.packing" :key="item.label">
             <div class="title">{{ item.label }}</div>
@@ -406,7 +409,7 @@ defineExpose({
       </div>
       <div class="marks-and-description">
         <div class="header">
-          <span>Marks and Description</span>
+          <span>{{ t('booking.marksAndDescription') }}</span>
         </div>
         <div class="data-info">
           <div class="data-item" v-for="item in allData.marksAndDescription" :key="item.label">
@@ -439,7 +442,7 @@ defineExpose({
                 popper-class="marks-popover description-see-all"
                 placement="top-end"
                 :width="700"
-                title="Description"
+                :title="t('common.description')"
                 trigger="click"
               >
                 <div class="description-info">
@@ -447,7 +450,7 @@ defineExpose({
                 </div>
                 <template #reference>
                   <el-button class="description-see-all el-button--text">
-                    <span>See All</span>
+                    <span>{{ t('common.seeAll') }}</span>
                   </el-button>
                 </template>
               </el-popover>

+ 5 - 2
src/views/Booking/src/components/BookingDetail/src/components/ContainersView.vue

@@ -3,6 +3,9 @@ import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
 // import { autoWidth } from '@/utils/table'
 import { useRowClickStyle } from '@/hooks/rowClickStyle'
 import { formatTimezone, formatNumber } from '@/utils/tools'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 
 const props = defineProps({
   data: Object
@@ -34,7 +37,7 @@ const tableData = ref<VxeGridProps<any>>({
 const handleColumns = (columns: any) => {
   const newColumns = columns.map((item: any) => {
     let curColumn: any = {
-      title: item.title,
+      title: t(item.cleaned_field_name),
       field: item.field,
       minWidth: 30
     }
@@ -81,7 +84,7 @@ useRowClickStyle(tableRef)
   <div class="containers">
     <vxe-grid ref="tableRef" v-bind="tableData">
       <template #empty>
-        <div class="empty">No data</div>
+        <div class="empty">{{ t('common.noData') }}</div>
       </template>
     </vxe-grid>
   </div>

+ 11 - 8
src/views/Booking/src/components/BookingDetail/src/components/EmailView.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import '@wangeditor/editor/dist/css/style.css'
 import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
 import { i18nChangeLanguage, DomEditor } from '@wangeditor/editor'
@@ -138,7 +141,7 @@ const sendEmail = () => {
   const html = editorRef.value.getHtml()
   const text = editorRef.value.getText()
   if (!text) {
-    ElMessage.warning('Please enter the email content')
+    ElMessage.warning(t('booking.pleaseEnterEmailContent'))
     return
   }
   $api
@@ -152,12 +155,12 @@ const sendEmail = () => {
     })
     .then((res: any) => {
       if (res.code === 200) {
-        ElMessage.success('Email sent successfully')
+        ElMessage.success(t('booking.emailSentSuccessfully'))
         emailRecords.value = res.data.emailRecords
       }
     })
     .catch(() => {
-      ElMessage.error('Failed to send email')
+      ElMessage.error(t('booking.failedToSendEmail'))
     })
 }
 </script>
@@ -166,7 +169,7 @@ const sendEmail = () => {
   <div class="email-view">
     <div class="email-path">
       <span class="font_family icon-icon_email_b" style="font-size: 18px"></span>
-      <span class="label">Communicate with us:&nbsp;</span>
+      <span class="label">{{ t('destinationDelivery.communicateWithUs') }}:&nbsp;</span>
       <span class="content">{{ emailData.email }}</span>
     </div>
     <div class="separated-by">
@@ -180,11 +183,11 @@ const sendEmail = () => {
               cursor: default;
             "
           >
-            <span style="font-weight: 600">CC:</span>
+            <span style="font-weight: 600">{{ t('tracking.cc') }}:</span>
             <el-tooltip
               class="box-item"
               effect="dark"
-              content="Separated by;"
+              :content="t('destinationDelivery.separatedBySemicolon') + ' ;'"
               placement="top-start"
               :offset="-8"
             >
@@ -214,8 +217,8 @@ const sendEmail = () => {
         @click="sendEmail"
         class="el-button--dark"
         style="float: right; margin: 8px 0 14px 0; height: 40px"
-        ><span class="font_family icon-icon_submit_b" style="margin-right: 4px"></span> Send
-        Email</el-button
+        ><span class="font_family icon-icon_submit_b" style="margin-right: 4px"></span>
+        {{ t('booking.sendEmail') }}</el-button
       >
     </div>
     <div class="show-records">

+ 22 - 21
src/views/Booking/src/components/BookingGuide.vue

@@ -2,6 +2,7 @@
 import { useDriver } from '@/utils/driverGuide'
 import { useGuideStore } from '@/stores/modules/guide'
 import { useThemeStore } from '@/stores/modules/theme'
+import { useI18n } from 'vue-i18n'
 
 import customizeColumnsImgLight from '../image/customize-columns.png'
 import customizeColumnsImgDark from '../image/dark-customize-columns.png'
@@ -9,6 +10,8 @@ import customizeColumnsImgDark from '../image/dark-customize-columns.png'
 import downloadFileImgLight from '@/views/Tracking/src/image/download-guide.png'
 import downloadFileImgDark from '@/views/Tracking/src/image/dark-download-guide.png'
 
+const { t } = useI18n()
+
 const themeStore = useThemeStore()
 
 const customizeColumnsImg = computed(() => {
@@ -26,12 +29,12 @@ const steps: any = [
     element: '#booking-filters-container-guide',
     popover: {
       title: '',
-      description: `Frequently Used Search Criteria Display Area:
+      description: `${t('booking.guideSearchAreaTitle')}:
         <ul>
-          <li>Key Booking Status</li>
-          <li>Reference Numbers</li>
-          <li>Transport Mode</li>
-          <li>Date Type & Range</li>
+          <li>${t('booking.guideSearchAreaStatus')}</li>
+          <li>${t('booking.guideSearchAreaReferences')}</li>
+          <li>${t('booking.guideSearchAreaTransportMode')}</li>
+          <li>${t('booking.guideSearchAreaDateRange')}</li>
         </ul>`,
       side: 'bottom'
     }
@@ -42,8 +45,8 @@ const steps: any = [
       title: '',
       description: `
         <ul>
-          <li>Selected query criteria display area</li>
-          <li>You can quickly clear selected conditions at this position</li>
+          <li>${t('booking.guideSelectedCriteria')}</li>
+          <li>${t('booking.guideClearCriteria')}</li>
         </ul>
       `,
       side: 'bottom'
@@ -53,7 +56,7 @@ const steps: any = [
     element: '#more-filters-guide',
     popover: {
       title: '',
-      description: 'Click "More Filters" to see more search options.',
+      description: t('booking.guideMoreFilters'),
       side: 'left'
     }
   },
@@ -61,8 +64,7 @@ const steps: any = [
     element: '.booking-no-header',
     popover: {
       title: '',
-      description:
-        'Click on the Booking No. or double-click anywhere on a single booking data to enter the detailed page.',
+      description: t('booking.guideOpenDetail'),
       side: 'bottom'
     }
   },
@@ -72,8 +74,8 @@ const steps: any = [
       title: '',
       description: `
         <ul>
-          <li>View the number of shipments selected for download</li>
-          <li>Two download list templates are available</li>
+          <li>${t('booking.guideDownloadTipLine1')}</li>
+          <li>${t('booking.guideDownloadTipLine2')}</li>
         </ul>
       `,
       side: 'bottom'
@@ -85,9 +87,9 @@ const steps: any = [
       title: '',
       description: `
         <ul>
-          <li>Drag to right to add columns</li>
-          <li>Drag to left to unselect columns</li>
-          <li>Drag up and down to reorder or select/unselect</li>
+          <li>${t('booking.guideCustomizeColumns')}</li>
+          <li>${t('booking.guideCustomizeColumnsLeft')}</li>
+          <li>${t('booking.guideCustomizeColumnsReorder')}</li>
         </ul>
       `,
       side: 'bottom'
@@ -97,8 +99,7 @@ const steps: any = [
     element: '#page-guide-btn-guide',
     popover: {
       title: '',
-      description:
-        'After closing, you can still click the "Page Guide" button to view the page guide of the current page.',
+      description: t('booking.guidePageGuide'),
       side: 'bottom'
     }
   }
@@ -223,13 +224,13 @@ defineExpose({
 }
 
 .download-file-guide-class {
-  right: 187px;
+  right: 186px;
   top: 246px;
-  width: 431px;
+  width: 831px;
   height: 304px;
   &.download-file-guide-dark-class {
-    right: 187px;
-    width: 431px;
+    right: 186px;
+    width: 593px;
     height: 304px;
   }
 }

+ 16 - 19
src/views/Booking/src/components/BookingTable/src/BookingTable.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
 import DownloadDialog from './components/DownloadDialog.vue'
 import { autoWidth } from '@/utils/table'
@@ -37,7 +40,7 @@ const tableOriginColumnsField = ref()
 const handleColumns = (columns: any, status?: string) => {
   const newColumns = columns.map((item: any) => {
     let curColumn: any = {
-      title: item.title,
+      title: t(item.cleaned_field_name),
       field: item.field,
       sortable: true
     }
@@ -220,7 +223,7 @@ const bookingTable = ref<VxeGridProps<any>>({
         [
           {
             code: 'newTab',
-            name: 'Open in New Tab',
+            name: t('booking.openInNewTab'),
             prefixConfig: { icon: 'icon-icon_jumplink_b1 font_family' }
           }
         ]
@@ -260,16 +263,13 @@ const bookingTable = ref<VxeGridProps<any>>({
               return Number(aValue) - Number(bValue)
             }
           }
-
           const result = compareResult(aValue, bValue)
           if (result !== 0) {
             return order === 'asc' ? result : -result
           }
         }
-
         return 0 // 如果所有字段都相等
       })
-
       return sortedData
     }
   },
@@ -403,11 +403,7 @@ const handleCustomizeColumns = () => {
       model_name: 'Booking_Search'
     }
   }
-  CustomizeColumnsRef.value.openDialog(
-    params,
-    -220,
-    'Drag item over to this selection or click "add" icon to show the column on your booking list'
-  )
+  CustomizeColumnsRef.value.openDialog(params, -220, t('booking.customizeColumnsTip'))
 }
 // 定制表格
 const customizeColumns = async () => {
@@ -488,12 +484,12 @@ defineExpose({
     style="padding: 0px 24px"
     class="booking-table"
     v-loading.fullscreen.lock="exportLoading"
-    element-loading-text="Loading..."
+    :element-loading-text="t('common.loading')"
     element-loading-custom-class="element-loading"
     element-loading-background="rgb(43, 47, 54, 0.7)"
   >
     <div class="table-tools">
-      <div class="left-total-records">{{ selectedNumber }} Selected</div>
+      <div class="left-total-records">{{ selectedNumber }} {{ t('booking.selected') }}</div>
       <div class="right-tools-btn">
         <el-button
           :class="{ 'el-button--pain-theme': themeStore.theme === 'dark' }"
@@ -505,11 +501,11 @@ defineExpose({
           }"
         >
           <span style="margin-right: 7px" class="font_family icon-icon_download_b"></span>
-          Download
+          {{ t('booking.download') }}
         </el-button>
         <el-button style="padding: 0 17px 0 9px" type="default" @click="handleCustomizeColumns">
           <span style="margin-right: 6px" class="font_family icon-icon_column_b"></span>
-          Customize Columns
+          {{ t('customizeColumns.title') }}
         </el-button>
       </div>
     </div>
@@ -531,11 +527,10 @@ defineExpose({
         <VEmpty>
           <template #suggestion>
             <p style="color: var(--color-neutral-3)">
-              We support the following references number to find booking:
+              {{ t('booking.bookingListEmptyTip') }}
             </p>
             <p style="color: var(--color-neutral-3)">
-              · Booking No./HAWB No./MAWB No./PO No./Carrier Booking No./Contract No./File No./Quote
-              No.
+              {{ t('booking.bookingListEmptyTipDetail') }}
             </p>
           </template>
         </VEmpty>
@@ -552,7 +547,7 @@ defineExpose({
       </template>
       <!-- Status字段的插槽 -->
       <template #status="{ row, column }">
-        <VTag :type="row[column.field]">{{ row[column.field] }}</VTag>
+        <VTag :type="row[column.field]">{{ t(`booking.${row[column.field]}`) }}</VTag>
       </template>
       <!-- Booking No字段的插槽 -->
       <template #link="{ row, column }">
@@ -566,7 +561,9 @@ defineExpose({
     </vxe-grid>
     <vxe-grid :height="10" ref="allTableRef" class="all-table" v-bind="allTable"> </vxe-grid>
     <div class="bottom-pagination">
-      <div class="left-total-records">Total {{ formatNumber(pageInfo.total) }}</div>
+      <div class="left-total-records">
+        {{ t('common.total') }} {{ formatNumber(pageInfo.total) }}
+      </div>
       <div class="right-pagination">
         <el-pagination
           v-model:current-page="pageInfo.pageNo"

+ 10 - 7
src/views/Booking/src/components/BookingTable/src/components/DownloadDialog.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { useFiltersStore } from '@/stores/modules/filtersList'
 
 const filtersStore = useFiltersStore()
@@ -36,11 +39,11 @@ defineExpose({
 
 <template>
   <div>
-    <el-dialog @close="clearData" v-model="dialogVisible" title="Download File" width="540">
+    <el-dialog @close="clearData" v-model="dialogVisible" :title="t('common.downloadFile')" width="540">
       <div class="download-dialog">
         <div class="select-data">
           <div style="display: inline-block">
-            Select data on your booking list:<span style="color: var(--color-theme)">{{
+            {{ t('booking.selectDataOnBookingList') }}:<span style="color: var(--color-theme)">{{
               selectedDataNumber
             }}</span>
           </div>
@@ -63,7 +66,7 @@ defineExpose({
         <div class="download-filter">
           <el-radio-group v-model="downloadFilter">
             <el-radio :value="1"
-              >Download with selected columns
+              >{{ t('booking.downloadWithSelectedColumns') }}
               <span class="column-number">{{ columns.length }}</span>
               <SeeAllIcon v-model="isShowSelectColumn" />
             </el-radio>
@@ -72,23 +75,23 @@ defineExpose({
               class="select-columns"
               :class="{ show: isShowSelectColumn }"
             >
-              <div class="title">Selected columns</div>
+              <div class="title">{{ t('common.selectedColumns') }}</div>
               <div class="content">
                 <div class="column-item" v-for="item in columns" :key="item">{{ item }}</div>
               </div>
             </div>
-            <el-radio :value="2">Download with all columns</el-radio>
+            <el-radio :value="2">{{ t('common.downloadWithAllColumns') }}</el-radio>
           </el-radio-group>
         </div>
       </div>
       <template #footer>
         <div class="dialog-footer">
           <el-button class="cancel-btn" type="default" @click="dialogVisible = false"
-            >Cancel</el-button
+            >{{ t('common.cancel') }}</el-button
           >
           <el-button class="download-btn el-button--dark" @click="handleDownload"
             ><span style="margin-right: 8px" class="font_family icon-icon_download_b"></span>
-            Download</el-button
+            {{ t('booking.download') }}</el-button
           >
         </div>
       </template>

+ 20 - 20
src/views/Dashboard/src/DashboardView.vue

@@ -16,6 +16,9 @@ import { formatNumber } from '@/utils/tools'
 import { useFiltersStore, FiltersType } from '@/stores/modules/filtersList'
 import dayjs from 'dayjs'
 import { useUserStore } from '@/stores/modules/user'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 
 const userStore = useUserStore()
 const formatDate = userStore.dateFormat
@@ -686,7 +689,6 @@ const transformDateRange = (
 const ClickParams = (val: string) => {
   const configFactory = CLICK_CONFIG_MAP[val]
   if (!configFactory) {
-    console.warn(`No config found for click value: ${val}`)
     return
   }
 
@@ -831,7 +833,7 @@ const handleGuide = () => {
     <!-- Title -->
     <div class="Title">
       <div>
-        <span>Dashboard</span>
+        <span>{{ t('dashboard.title') }}</span>
         <VDriverGuide style="margin-top: -1px" @click="handleGuide"></VDriverGuide>
       </div>
 
@@ -844,12 +846,12 @@ const handleGuide = () => {
                   <use xlink:href="#icon-icon_view__management_b"></use>
                 </svg>
               </span>
-              View Management
+              {{ t('dashboard.viewManagement') }}
             </el-button>
           </template>
 
           <div class="Management">
-            <div class="title">View Management</div>
+            <div class="title">{{ t('dashboard.viewManagement') }}</div>
             <div class="management-content">
               <div class="management-item" v-for="(item, index) in Management" :key="index">
                 <div class="management_flex">
@@ -868,8 +870,7 @@ const handleGuide = () => {
                   class="content_text_warining"
                   v-if="item.isRevenueDisplay != undefined && item.isRevenueDisplay == false"
                 >
-                  *To ensure the accuracy of the data display, this report needs to be configured
-                  and displayed after communicating clearly with Sales.
+                  {{ t('dashboard.salesConfigWarning') }}
                 </div>
               </div>
             </div>
@@ -881,8 +882,7 @@ const handleGuide = () => {
                 </svg>
               </span>
               <div class="tips_text">
-                Please remember to click the save button in order to keep the new dashboard layout
-                and widgets.
+                {{ t('dashboard.rememberSaveLayout') }}
               </div>
             </div>
           </div>
@@ -922,7 +922,7 @@ const handleGuide = () => {
                   <use xlink:href="#icon-icon_save_b"></use>
                 </svg>
               </span>
-              Save
+              {{ t('dashboard.save') }}
               <span class="iconfont_icon">
                 <svg class="iconfont" aria-hidden="true">
                   <use xlink:href="#icon-icon_dropdown_b"></use>
@@ -936,7 +936,7 @@ const handleGuide = () => {
                 <use xlink:href="#icon-icon_save_b"></use>
               </svg>
             </span>
-            <div>Save Filters</div>
+            <div>{{ t('dashboard.saveFilters') }}</div>
           </div>
           <div class="Save_filters" @click="SaveLayout">
             <span class="iconfont_icon iconfont_icon_save">
@@ -944,7 +944,7 @@ const handleGuide = () => {
                 <use xlink:href="#icon-icon_save_b"></use>
               </svg>
             </span>
-            <div>Save Layout</div>
+            <div>{{ t('dashboard.saveLayout') }}</div>
           </div>
         </el-popover>
 
@@ -1018,7 +1018,7 @@ const handleGuide = () => {
                     <VTipTooltip
                       :img="kpiChartTip"
                       :width="410"
-                      :label="'KPI Report:Day difference between actual and estimate.'"
+                      :label="t('dashboard.kpiReportTip')"
                       placement="bottom-start"
                     ></VTipTooltip>
                   </div>
@@ -1065,7 +1065,7 @@ const handleGuide = () => {
                       :img="pendingChartTip"
                       :width="420"
                       :placement="'bottom-start'"
-                      :label="'Pending Report:Showing shipments which are soon to depart/arrive.'"
+                      :label="t('dashboard.pendingReportTip')"
                     ></VTipTooltip>
                   </div>
                   <DashFilters
@@ -1114,7 +1114,7 @@ const handleGuide = () => {
                       :img="etdToEtaChartsTip"
                       :width="430"
                       :placement="'bottom-start'"
-                      :label="'ETD to ETA (Days):Distribution of Transit Time (ETA-ETD) for All Shipments in Last 12 Months.'"
+                      :label="t('dashboard.etdToEtaTip')"
                     ></VTipTooltip>
                   </div>
                   <DashFilters
@@ -1149,7 +1149,7 @@ const handleGuide = () => {
                     <VTipTooltip
                       :img="containerChartTip"
                       :placement="'bottom-start'"
-                      :label="'Container Count:Total Container Volume by Month (Last 12 Months)'"
+                      :label="t('dashboard.containerCountTip')"
                     ></VTipTooltip>
                   </div>
                   <DashFilters
@@ -1193,7 +1193,7 @@ const handleGuide = () => {
                     <VTipTooltip
                       style="margin-left: 4px"
                       :img="top10ChartTip"
-                      :label="'Top 10 Origin & Destination: Last 12 Months Shipment Volume Rankings: Top 10 Origin Cities and Top 10 Destination Cities'"
+                      :label="t('dashboard.top10Tip')"
                       :width="700"
                     ></VTipTooltip>
                   </div>
@@ -1254,7 +1254,7 @@ const handleGuide = () => {
                     {{ item.title }}
                     <VTipTooltip
                       :img="co2eChartTip"
-                      :label="'CO2e Emission by Origin or Destination: Last 12 Months CO2e Emission Rankings: Top 10 Origin Cities and Top 10 Destination Cities'"
+                      :label="t('dashboard.co2eTip')"
                       :width="700"
                       :placement="'bottom-start'"
                     ></VTipTooltip>
@@ -1330,7 +1330,7 @@ const handleGuide = () => {
                     {{ item.title }}
                     <VTipTooltip
                       :img="recentStatusChartTip"
-                      :label="'Recent Status: Active shipment list with ETD within the past three months and the next month.'"
+                      :label="t('dashboard.recentStatusTip')"
                       :width="700"
                       :placement="'bottom-start'"
                     ></VTipTooltip>
@@ -1367,10 +1367,10 @@ const handleGuide = () => {
               <template #header>
                 <div class="Title_flex">
                   <div>
-                    Revenue Spent
+                    {{ t('dashboard.revenueSpentTitle') }}
                     <VTipTooltip
                       :img="revenueSpentChartTip"
-                      :label="'Revenue Spent: Based on the billto object, display the corresponding revenue data. '"
+                      :label="t('dashboard.revenueSpentTip')"
                       :placement="'bottom-start'"
                       :width="700"
                     ></VTipTooltip>

+ 12 - 9
src/views/Dashboard/src/components/CustomerFilter.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { cloneDeep, debounce } from 'lodash'
 import axios from 'axios'
 
@@ -73,7 +76,7 @@ const remoteMethod = (query: string) => {
     .catch((err) => {
       options.value = []
       if (!axios.isCancel(err) && !newController.signal.aborted) {
-        ElMessage.error('Failed to load options')
+        ElMessage.error(t('common.failedToLoadOptions'))
       }
     })
     .finally(() => {
@@ -93,23 +96,23 @@ onUnmounted(() => {
 
 const typeOptions = ref([
   {
-    label: 'Shipper',
+    label: t('dashboard.shipper'),
     value: 'shipper_id'
   },
   {
-    label: 'Consignee',
+    label: t('dashboard.consignee'),
     value: 'consignee_id'
   },
   {
-    label: 'Controlling Customer',
+    label: t('dashboard.controllingCustomer'),
     value: 'customer_code'
   },
   {
-    label: 'Bill to',
+    label: t('dashboard.billTo'),
     value: 'billto_id'
   },
   {
-    label: 'Notify Party',
+    label: t('dashboard.notifyParty'),
     value: 'notify_party_id'
   }
 ])
@@ -126,7 +129,7 @@ const handleSearch = () => {
       multiple
       filterable
       reserve-keyword
-      placeholder="Search by Customer code, Customer name"
+      :placeholder="t('dashboard.customerSearchPlaceholder')"
       :loading="loading"
       style="width: 400px"
       collapse-tags
@@ -155,7 +158,7 @@ const handleSearch = () => {
       </el-option>
     </el-select>
     <el-select
-      placeholder="Customer Type"
+      :placeholder="t('dashboard.customerType')"
       multiple
       :max-collapse-tags="1"
       collapse-tags
@@ -180,7 +183,7 @@ const handleSearch = () => {
         </div>
       </el-option>
     </el-select>
-    <el-button class="el-button--default" @click="handleSearch">Search</el-button>
+    <el-button class="el-button--default" @click="handleSearch">{{ t('common.search') }}</el-button>
   </div>
 </template>
 

+ 11 - 8
src/views/Dashboard/src/components/DashFiters.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { ref, watch, onMounted, computed } from 'vue'
 import dayjs from 'dayjs'
 import { formatTimezone } from '@/utils/tools'
@@ -198,7 +201,7 @@ const guideStore = useGuideStore()
               <use xlink:href="#icon-icon_filter_b"></use>
             </svg>
           </span>
-          Filters
+          {{ t('dashboard.filters') }}
           <span class="iconfont_icon">
             <svg class="iconfont icon_dark" aria-hidden="true">
               <use xlink:href="#icon-icon_dropdown_b"></use>
@@ -206,7 +209,7 @@ const guideStore = useGuideStore()
           </span>
         </el-button>
       </template>
-      <div class="Dash_title">Transport Mode</div>
+      <div class="Dash_title">{{ t('dashboard.transportMode') }}</div>
       <div class="filter_filter_one">
         <el-checkbox-group
           @change="changeCheckboxGroup1"
@@ -225,9 +228,9 @@ const guideStore = useGuideStore()
         </el-checkbox-group>
       </div>
       <div class="Dash_title">
-        <div>Date Range</div>
+        <div>{{ t('dashboard.dateRange') }}</div>
         <div v-if="props.isContainer && !props.isETDToETA" class="dash_tips">
-          The time range is fixed at 12 months.
+          {{ t('dashboard.fixed12Months') }}
         </div>
       </div>
       <div class="dash_flex">
@@ -255,7 +258,7 @@ const guideStore = useGuideStore()
               type="month"
               @change="StartChange"
               :clearable="false"
-              placeholder="Start month"
+              :placeholder="t('dateRange.startMonth')"
               placement="bottom"
             />
             <el-date-picker
@@ -269,7 +272,7 @@ const guideStore = useGuideStore()
               type="month"
               @change="EndChange"
               :clearable="false"
-              placeholder="End month"
+              :placeholder="t('dateRange.endMonth')"
               placement="bottom"
             />
           </div>
@@ -282,9 +285,9 @@ const guideStore = useGuideStore()
         </div>
       </div>
       <div class="daterange_bottom">
-        <div><el-button type="default" @click="clearrest" class="Clear">Reset</el-button></div>
+        <div><el-button type="default" @click="clearrest" class="Clear">{{ t('common.reset') }}</el-button></div>
         <div>
-          <el-button class="search el-button--dark" @click="DateRangeSearch">Search</el-button>
+          <el-button class="search el-button--dark" @click="DateRangeSearch">{{ t('common.search') }}</el-button>
         </div>
       </div>
     </el-popover>

+ 9 - 6
src/views/Dashboard/src/components/DashboardGuide.vue

@@ -1,6 +1,9 @@
 <script setup lang="ts">
 import { useDriver } from '@/utils/driverGuide'
 import { useGuideStore } from '@/stores/modules/guide'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 
 const guideStore = useGuideStore()
 
@@ -11,7 +14,7 @@ const steps: any = [
     popover: {
       title: '',
       description:
-        'The Dashboard integrates different types of reports. You can freely select the reports you need. ',
+        t('dashboard.guideDashboardIntro'),
       side: 'left'
     }
   },
@@ -20,7 +23,7 @@ const steps: any = [
     popover: {
       title: '',
       description:
-        'Each report comes with its own filter options. Simply make your selection and click "Search" to refresh the data. ',
+        t('dashboard.guideDashboardFilter'),
       side: 'bottom'
     }
   },
@@ -29,7 +32,7 @@ const steps: any = [
     popover: {
       title: '',
       description:
-        'Hover the icon next to the report name to view the data explanation for each report.',
+        t('dashboard.guideDashboardTooltip'),
       side: 'bottom'
     }
   },
@@ -38,7 +41,7 @@ const steps: any = [
     popover: {
       title: '',
       description:
-        'Drag and drop reports using the drag handle (eight-dot icon) to reposition them in any direction. ',
+        t('dashboard.guideDashboardDrag'),
       side: 'bottom'
     }
   },
@@ -46,7 +49,7 @@ const steps: any = [
     element: '#save-config-guide',
     popover: {
       title: '',
-      description: 'Click "Save Layout" to preserve your customized report arrangement.',
+      description: t('dashboard.guideDashboardSaveLayout'),
       side: 'bottom'
     }
   },
@@ -55,7 +58,7 @@ const steps: any = [
     popover: {
       title: '',
       description:
-        'After closing, you can still click the "Page Guide" button to view the page guide of the current page.',
+        t('dashboard.guidePageGuide'),
       side: 'bottom'
     }
   }

+ 52 - 7
src/views/Dashboard/src/components/RecentStatus.vue

@@ -1,8 +1,49 @@
 <script lang="ts" setup>
 import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
 import { formatTimezoneByUTCorGMT, formatTimezone } from '@/utils/tools'
 
+const { t } = useI18n()
+
 const router = useRouter()
+
+type VTagType =
+  | 'Confirmed'
+  | 'Cancelled'
+  | 'Created'
+  | 'Booked'
+  | 'Cargo Received'
+  | 'Departure'
+  | 'Arrived'
+  | 'Completed'
+  | 'Departed'
+  | 'Pending Approval'
+  | 'Approved'
+  | 'Rejected'
+  | 'Active'
+  | 'Inactive'
+
+const V_TAG_TYPES = new Set<VTagType>([
+  'Confirmed',
+  'Cancelled',
+  'Created',
+  'Booked',
+  'Cargo Received',
+  'Departure',
+  'Arrived',
+  'Completed',
+  'Departed',
+  'Pending Approval',
+  'Approved',
+  'Rejected',
+  'Active',
+  'Inactive'
+])
+
+const asVTagType = (val: string): VTagType | undefined => {
+  return V_TAG_TYPES.has(val as VTagType) ? (val as VTagType) : undefined
+}
+
 interface RecentItem {
   Title: string
   name: string
@@ -78,7 +119,7 @@ const SubscribeShipments = (val: any) => {
         <div class="recent-title" @click="RouteToDetail(item)">{{ item.Title }}</div>
         <div class="recent-name">{{ item.name }}</div>
         <div class="recent-booking">
-          Booking No.:
+          {{ t('booking.bookingNo') }}:
           <span>{{ item.bookingNumber }}</span>
         </div>
       </div>
@@ -98,7 +139,7 @@ const SubscribeShipments = (val: any) => {
               <use xlink:href="#icon-icon_unmark_b"></use>
             </svg>
           </span>
-          <span class="Subscribe">Subscribe</span>
+          <span class="Subscribe">{{ t('tracking.subscribe') }}</span>
         </el-button>
       </div>
     </div>
@@ -106,10 +147,10 @@ const SubscribeShipments = (val: any) => {
       <!-- 左 -->
       <div class="recent-content-left">
         <div class="left_title">
-          Shipper: <span class="left_text">{{ item.shipperName }}</span>
+          {{ t('booking.shipper') }}: <span class="left_text">{{ item.shipperName }}</span>
         </div>
         <div class="left_title">
-          Consignee: <span class="left_text">{{ item.consigneeName }}</span>
+          {{ t('booking.consignee') }}: <span class="left_text">{{ item.consigneeName }}</span>
         </div>
       </div>
       <!-- 中 -->
@@ -123,7 +164,9 @@ const SubscribeShipments = (val: any) => {
             </span>
             {{ item.startStation }}
           </div>
-          <div class="startStation_time">ETD: {{ formatTimezone(item.ETD) }}</div>
+          <div class="startStation_time">
+            {{ t('tracking.etd') }}: {{ formatTimezone(item.ETD) }}
+          </div>
         </div>
         <div class="StationIcon">
           <div>
@@ -144,12 +187,14 @@ const SubscribeShipments = (val: any) => {
             </span>
             {{ item.endStation }}
           </div>
-          <div class="startStation_time">ETA: {{ formatTimezone(item.ETA) }}</div>
+          <div class="startStation_time">
+            {{ t('tracking.eta') }}: {{ formatTimezone(item.ETA) }}
+          </div>
         </div>
       </div>
       <!-- 右 -->
       <div class="recent-content-right">
-        <VTag :type="item.type" style="margin-bottom: 4px">{{ item.type }}</VTag>
+        <VTag :type="asVTagType(item.type)" style="margin-bottom: 4px">{{ item.type }}</VTag>
         <div class="right_text">{{ item.Arrived }}</div>
         <div class="startStation_time" v-if="item.Time != null">
           {{ formatTimezoneByUTCorGMT(item.Time, item.timezone) }}&nbsp;&nbsp;{{ item.timezone }}

+ 9 - 4
src/views/Dashboard/src/components/RevenueChart.vue

@@ -5,7 +5,9 @@ import { useThemeStore } from '@/stores/modules/theme'
 import { onMounted, ref, reactive, watch, computed } from 'vue'
 import * as XLSX from 'xlsx'
 import { formatNumber } from '@/utils/tools'
-import { on } from 'events'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 
 const themeStore = useThemeStore()
 const props = defineProps({
@@ -210,7 +212,9 @@ const initOption = reactive({
       allnum = allnum.toFixed(2)
       str +=
         '<div style= ' +
-        'color:#FFF;font-family: Lato-Light>Total: ' +
+        'color:#FFF;font-family: Lato-Light>' +
+        t('common.total') +
+        ': ' +
         formatNumber(allnum) +
         '</div>'
       return str
@@ -296,7 +300,7 @@ const initOption = reactive({
       saveAsImage: {
         icon: 'path://M588.8 830.976H179.52a48 48 0 0 1-48-48v-110.72l212.032-120.96 117.76 67.2a16 16 0 0 0 15.744 0.128l396.16-220.8-0.192-0.256h0.384v-156.8c0-44.16-35.84-80-80-80H179.52c-44.16 0-80 35.84-80 80v542.208c0 44.16 35.84 80 80 80H588.8v-32zM131.52 240.832a48 48 0 0 1 48-48h613.888a48 48 0 0 1 48 48V378.88L469.312 586.24l-117.76-67.2a16.128 16.128 0 0 0-12.032-1.664l-3.84 1.6-204.16 116.48V240.896zM309.632 467.2a85.76 85.76 0 0 0 85.376-76.992l0.384-8.768c0-44.416-33.728-80.96-76.992-85.376L309.76 295.68c-47.36 0-85.824 38.4-85.824 85.76l0.448 8.832A85.76 85.76 0 0 0 300.8 466.816l8.832 0.448z m0-32a53.76 53.76 0 1 1 0-107.584 53.76 53.76 0 0 1 0 107.52z m452.736 423.04a16 16 0 0 0 22.592 0l138.368-138.368-22.592-22.656-111.104 111.104V545.6h-32v262.784l-111.04-111.104-22.592 22.656 138.368 138.368z',
         show: true,
-        name: 'Revenue Spent',
+        name: t('dashboard.revenueSpentTitle'),
         pixelRatio: 2
       },
       myTool1: {
@@ -307,7 +311,8 @@ const initOption = reactive({
           'M560.384 115.392a16.064 16.064 0 0 1 13.056 4.608l257.216 257.216c1.92 1.92 3.136 4.288 3.84 6.784h0.832v118.72h-32V404.48H626.112c-44.16 0-80-35.84-80-80V147.2H230.208a48 48 0 0 0-48 48v672c0 26.56 21.504 48 48 48h341.504v32H230.208c-44.16 0-80-35.776-80-80V195.2c0-44.16 35.84-80 80-80h330.24v0.192z m232.768 495.296v281.984l119.872-119.872 22.592 22.656-147.072 147.2a16.064 16.064 0 0 1-22.656 0l-147.2-147.2 22.656-22.656 119.808 119.872V610.688h32zM427.648 476.352c2.496 0 4.224 0.448 5.312 1.216a8.96 8.96 0 0 1 3.072 3.84L474.24 565.12a48.256 48.256 0 0 1 1.792-3.2c0.64-1.152 1.344-2.24 2.112-3.392l53.952-76.736a13.76 13.76 0 0 1 3.648-4.032 9.152 9.152 0 0 1 4.864-1.28h37.76L498.432 583.04l59.712 125.824h-32.64a7.872 7.872 0 0 1-5.76-2.112 15.936 15.936 0 0 1-3.264-4.48l-43.52-97.088a42.112 42.112 0 0 1-3.2 5.376l-64.768 91.648a14.208 14.208 0 0 1-5.312 5.248 13.952 13.952 0 0 1-6.272 1.408h-36.352L448 584.768l-53.248-108.416h32.96zM578.112 324.48c0 26.496 21.504 48 48 48h154.56L578.176 169.92V324.48z',
         onclick: function () {
           let filename =
-            'Revenue Spent Details ' +
+            'Revenue Spent' +
+            ' Details ' +
             barName.value[0] +
             '-' +
             barName.value[barName.value.length - 1]

+ 124 - 79
src/views/Dashboard/src/components/ScoringSystem.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import { ref } from 'vue'
+import { useI18n } from 'vue-i18n'
 import angryPng from '../image/score_angry.png'
 import angryPng2 from '../image/angry_2.png'
 import sadPng from '../image/score_sad.png'
@@ -14,6 +15,7 @@ import submitsucessful from '../image/submit_successful.png'
 import { useUserStore } from '@/stores/modules/user'
 
 const userStore = useUserStore()
+const { t } = useI18n()
 
 const dialogVisible = ref(false)
 const innerVisible = ref(false)
@@ -36,6 +38,19 @@ interface AvaterItem {
   itemtitle: string
   changecolortext: boolean
 }
+
+interface ScoreOption {
+  label: string
+  value: string
+}
+
+interface SmileAspectsItem {
+  title: string
+  titleLabel: string
+  proposal: ScoreOption[]
+  radio: string
+}
+
 const avater = ref<AvaterItem[]>([])
 const inputdetails = ref('')
 avater.value = [
@@ -43,106 +58,130 @@ avater.value = [
     src: angryPng,
     src1: angryPng,
     itemsrc: angryPng2,
-    itemtext: 'We are so sorry for the inconveniences. We value your experience immensely. ',
+    itemtext: t('dashboard.scoringSorryText'),
     expression: 'angry',
-    proposal: 'Could you please tell us which aspects of the system you are dissatisfied with?',
-    itemtitle: 'Highly dissatisfied',
+    proposal: t('dashboard.scoringDissatisfiedTip'),
+    itemtitle: t('dashboard.highlyDissatisfied'),
     changecolortext: false
   },
   {
     src: sadPng,
     src1: sadPng,
     itemsrc: sadPng2,
-    itemtext: 'We are so sorry for the inconveniences. We value your experience immensely. ',
+    itemtext: t('dashboard.scoringSorryText'),
     expression: 'sad',
-    proposal: 'Could you please tell us which aspects of the system you are dissatisfied with?',
-    itemtitle: 'Dissatisfied',
+    proposal: t('dashboard.scoringDissatisfiedTip'),
+    itemtitle: t('dashboard.dissatisfied'),
     changecolortext: false
   },
   {
     src: smilePng,
     src1: smilePng,
     itemsrc: smilePng2,
-    itemtext: 'Thank you for sharing your thoughts with us. We value your experience greatly.',
+    itemtext: t('dashboard.scoringNeutralText'),
     expression: 'smile',
-    proposal: 'Could you share what aspects you liked and what could be improved ?',
-    itemtitle: 'Neutral',
+    proposal: t('dashboard.scoringNeutralProposal'),
+    itemtitle: t('dashboard.neutral'),
     changecolortext: false
   },
   {
     src: hhhPng,
     src1: hhhPng,
     itemsrc: hhhPng2,
-    itemtext: 'Thank you very much for giving us a satisfied rating on our service or system.',
+    itemtext: t('dashboard.scoringSatisfiedText'),
     expression: 'hhh',
-    proposal: 'We are curious to learn more about what specifically made you feel satisfied. ',
-    itemtitle: 'Satisfied',
+    proposal: t('dashboard.scoringSatisfiedProposal'),
+    itemtitle: t('dashboard.satisfied'),
     changecolortext: false
   },
   {
     src: happyPng,
     src1: happyPng,
     itemsrc: happyPng2,
-    itemtext: 'Thank you very much for giving us a satisfied rating on our service or system.',
+    itemtext: t('dashboard.scoringSatisfiedText'),
     expression: 'happy',
-    proposal: 'We are curious to learn more about what specifically made you feel satisfied. ',
-    itemtitle: 'Highly satisfied',
+    proposal: t('dashboard.scoringSatisfiedProposal'),
+    itemtitle: t('dashboard.highlySatisfied'),
     changecolortext: false
   }
 ]
-const checkboxGroup1 = ref([])
-const checkboxGroup2 = ref([])
+const checkboxGroup1 = ref<string[]>([])
+const checkboxGroup2 = ref<string[]>([])
 const evaluate = [
-  'Functionality',
-  'Data accuracy',
-  'Look and feel',
-  'Ease of use',
-  'Website performance'
-]
-const happyevaluate = [
-  'Functionality',
-  'Data accuracy',
-  'Look and feel',
-  'Ease of use',
-  'Website performance'
+  { label: t('dashboard.functionality'), value: 'functionality' },
+  { label: t('dashboard.dataAccuracy'), value: 'dataAccuracy' },
+  { label: t('dashboard.lookAndFeel'), value: 'lookAndFeel' },
+  { label: t('dashboard.easeOfUse'), value: 'easeOfUse' },
+  { label: t('dashboard.websitePerformance'), value: 'websitePerformance' }
 ]
+const happyevaluate = evaluate
 const Aspects = ref([
-  'Highly Dissatisfied',
-  'Dissatisfied',
-  'Neutral',
-  'Satisfied',
-  'Highly Satisfied'
+  { label: t('dashboard.highlyDissatisfied'), value: 'highlyDissatisfied' },
+  { label: t('dashboard.dissatisfied'), value: 'dissatisfied' },
+  { label: t('dashboard.neutral'), value: 'neutral' },
+  { label: t('dashboard.satisfied'), value: 'satisfied' },
+  { label: t('dashboard.highlySatisfied'), value: 'highlySatisfied' }
 ])
-interface smileAspectsItem {
-  title: string
-  proposal: Array<string>
-  radio: string
-}
-const smileAspects = ref<smileAspectsItem[]>([])
+const smileAspects = ref<SmileAspectsItem[]>([])
 smileAspects.value = [
   {
-    title: 'Functionality',
-    proposal: ['Highly Dissatisfied', 'Dissatisfied', 'Neutral', 'Satisfied', 'Highly Satisfied'],
+    title: 'functionality',
+    titleLabel: t('dashboard.functionality'),
+    proposal: [
+      { label: t('dashboard.highlyDissatisfied'), value: 'highlyDissatisfied' },
+      { label: t('dashboard.dissatisfied'), value: 'dissatisfied' },
+      { label: t('dashboard.neutral'), value: 'neutral' },
+      { label: t('dashboard.satisfied'), value: 'satisfied' },
+      { label: t('dashboard.highlySatisfied'), value: 'highlySatisfied' }
+    ],
     radio: ''
   },
   {
-    title: 'Data accuracy',
-    proposal: ['Highly Dissatisfied', 'Dissatisfied', 'Neutral', 'Satisfied', 'Highly Satisfied'],
+    title: 'dataAccuracy',
+    titleLabel: t('dashboard.dataAccuracy'),
+    proposal: [
+      { label: t('dashboard.highlyDissatisfied'), value: 'highlyDissatisfied' },
+      { label: t('dashboard.dissatisfied'), value: 'dissatisfied' },
+      { label: t('dashboard.neutral'), value: 'neutral' },
+      { label: t('dashboard.satisfied'), value: 'satisfied' },
+      { label: t('dashboard.highlySatisfied'), value: 'highlySatisfied' }
+    ],
     radio: ''
   },
   {
-    title: 'Look and feel',
-    proposal: ['Highly Dissatisfied', 'Dissatisfied', 'Neutral', 'Satisfied', 'Highly Satisfied'],
+    title: 'lookAndFeel',
+    titleLabel: t('dashboard.lookAndFeel'),
+    proposal: [
+      { label: t('dashboard.highlyDissatisfied'), value: 'highlyDissatisfied' },
+      { label: t('dashboard.dissatisfied'), value: 'dissatisfied' },
+      { label: t('dashboard.neutral'), value: 'neutral' },
+      { label: t('dashboard.satisfied'), value: 'satisfied' },
+      { label: t('dashboard.highlySatisfied'), value: 'highlySatisfied' }
+    ],
     radio: ''
   },
   {
-    title: 'Ease of use',
-    proposal: ['Highly Dissatisfied', 'Dissatisfied', 'Neutral', 'Satisfied', 'Highly Satisfied'],
+    title: 'easeOfUse',
+    titleLabel: t('dashboard.easeOfUse'),
+    proposal: [
+      { label: t('dashboard.highlyDissatisfied'), value: 'highlyDissatisfied' },
+      { label: t('dashboard.dissatisfied'), value: 'dissatisfied' },
+      { label: t('dashboard.neutral'), value: 'neutral' },
+      { label: t('dashboard.satisfied'), value: 'satisfied' },
+      { label: t('dashboard.highlySatisfied'), value: 'highlySatisfied' }
+    ],
     radio: ''
   },
   {
-    title: 'Website performance',
-    proposal: ['Highly Dissatisfied', 'Dissatisfied', 'Neutral', 'Satisfied', 'Highly Satisfied'],
+    title: 'websitePerformance',
+    titleLabel: t('dashboard.websitePerformance'),
+    proposal: [
+      { label: t('dashboard.highlyDissatisfied'), value: 'highlyDissatisfied' },
+      { label: t('dashboard.dissatisfied'), value: 'dissatisfied' },
+      { label: t('dashboard.neutral'), value: 'neutral' },
+      { label: t('dashboard.satisfied'), value: 'satisfied' },
+      { label: t('dashboard.highlySatisfied'), value: 'highlySatisfied' }
+    ],
     radio: ''
   }
 ]
@@ -184,23 +223,23 @@ const OpenScoring = (item: any) => {
   InnerTitle.value = item.itemtext
   InnerTileQues.value = item.proposal
   if (item.expression == 'angry') {
-    SubmitText.value = 'Apologize once again for your experience.'
+    SubmitText.value = t('dashboard.apologizeAgain')
     checkexpression.value = 'angry'
     isShowAngry.value = true
   } else if (item.expression == 'sad') {
-    SubmitText.value = 'Apologize once again for your experience.'
+    SubmitText.value = t('dashboard.apologizeAgain')
     checkexpression.value = 'sad'
     isShowAngry.value = true
   } else if (item.expression == 'smile') {
-    SubmitText.value = 'We greatly appreciate your valuable time and feedback.'
+    SubmitText.value = t('dashboard.appreciateFeedback')
     checkexpression.value = 'smile'
     isShowSmile.value = true
   } else if (item.expression == 'hhh') {
-    SubmitText.value = 'Once again, thank you for your positive evaluation.'
+    SubmitText.value = t('dashboard.thankYouPositive')
     checkexpression.value = 'hhh'
     isShowHappy.value = true
   } else if (item.expression == 'happy') {
-    SubmitText.value = 'Once again, thank you for your positive evaluation.'
+    SubmitText.value = t('dashboard.thankYouPositive')
     checkexpression.value = 'happy'
     isShowHappy.value = true
   }
@@ -230,20 +269,22 @@ const changeHappyDetails = () => {
 }
 let SmileObj: any = {}
 const smilecheckbox = ref()
-const changeSmileRadio = (title: any, value: any) => {
+const changeSmileRadio = (title: string, value: string) => {
   SmileObj[title] = value
   smilecheckbox.value = SmileObj
   if (Object.keys(SmileObj).length == Aspects.value.length) {
     buttonDisabled.value = false
   }
 }
+const getSelectedLabels = (options: ScoreOption[], selectedValues: string[]) =>
+  options.filter((item) => selectedValues.includes(item.value)).map((item) => item.label)
 // 提交details
 const submitDetails = (val: any) => {
   const username = userStore.userName
   if (checkboxGroup1.value.length) {
     $api
       .scoringgrade({
-        suggestion: checkboxGroup1.value,
+        suggestion: getSelectedLabels(evaluate, checkboxGroup1.value),
         proposal: val,
         expression: checkexpression.value,
         username: username
@@ -260,7 +301,7 @@ const submitDetails = (val: any) => {
   } else if (checkboxGroup2.value.length) {
     $api
       .scoringgrade({
-        suggestion: checkboxGroup2.value,
+        suggestion: getSelectedLabels(happyevaluate, checkboxGroup2.value),
         proposal: val,
         expression: checkexpression.value
       })
@@ -278,7 +319,9 @@ const submitDetails = (val: any) => {
       .scoringgrade({
         proposal: val,
         expression: checkexpression.value,
-        ...smilecheckbox.value
+        ...Object.fromEntries(
+          Object.entries(smilecheckbox.value).map(([key, value]) => [key, value])
+        )
       })
       .then((res: any) => {
         if (res.code == 200) {
@@ -297,8 +340,10 @@ const submitDetails = (val: any) => {
   <div class="scoring_dashboard" v-if="isShowScoring">
     <div class="scoring_left">
       <img class="scoring_img" src="../image/dashboard_scoring.png" />
-      <div>How satisfied are you with this system ?</div>
-      <div class="das_share" @click="dialogVisible = true">Share Your Feedback</div>
+      <div>{{ t('scoringGrade.satisfactionQuestionWithSpace') }}</div>
+      <div class="das_share" @click="dialogVisible = true">
+        {{ t('scoringGrade.shareYourFeedback') }}
+      </div>
       <el-dialog
         v-model="dialogVisible"
         :destroy-on-close="true"
@@ -308,7 +353,7 @@ const submitDetails = (val: any) => {
         custom-class="my-dialog"
         :close-on-click-modal="false"
       >
-        <div class="das_title">How satisfied are you with this system ?</div>
+        <div class="das_title">{{ t('scoringGrade.satisfactionQuestionWithSpace') }}</div>
         <div class="das_flex">
           <div class="das_score" v-for="(item, index) in avater" :key="index">
             <el-avatar
@@ -347,10 +392,10 @@ const submitDetails = (val: any) => {
               <el-checkbox-button
                 @change="changeAngryDetails"
                 v-for="item in evaluate"
-                :key="item"
-                :value="item"
+                :key="item.value"
+                :value="item.value"
               >
-                {{ item }}
+                {{ item.label }}
               </el-checkbox-button>
             </el-checkbox-group>
           </div>
@@ -358,13 +403,13 @@ const submitDetails = (val: any) => {
             <div class="smile_flex">
               <div class="smile_title_left"></div>
               <div class="smile_title_right">
-                <div class="smile_title" v-for="item in Aspects" :key="item">
-                  {{ item }}
+                <div class="smile_title" v-for="item in Aspects" :key="item.value">
+                  {{ item.label }}
                 </div>
               </div>
             </div>
             <div class="smile_content_flex" v-for="(item, index) in smileAspects" :key="index">
-              <div class="smile_title_left content_left">{{ item.title }}</div>
+              <div class="smile_title_left content_left">{{ item.titleLabel }}</div>
               <div class="smile_title_right content_right">
                 <el-radio-group
                   v-model="item.radio"
@@ -373,8 +418,8 @@ const submitDetails = (val: any) => {
                   <el-radio
                     class="smile_radio"
                     v-for="proposal in item.proposal"
-                    :value="proposal"
-                    :key="proposal"
+                    :value="proposal.value"
+                    :key="proposal.value"
                   ></el-radio>
                 </el-radio-group>
               </div>
@@ -385,15 +430,15 @@ const submitDetails = (val: any) => {
               <el-checkbox-button
                 @change="changeHappyDetails"
                 v-for="item in happyevaluate"
-                :key="item"
-                :value="item"
+                :key="item.value"
+                :value="item.value"
               >
-                {{ item }}
+                {{ item.label }}
               </el-checkbox-button>
             </el-checkbox-group>
           </div>
           <div class="das_in_ques" style="margin: 0">
-            Would you like to share more details with us?
+            {{ t('dashboard.satisfactionDetailsQuestion') }}
           </div>
           <div>
             <el-input
@@ -402,7 +447,7 @@ const submitDetails = (val: any) => {
               style="width: 592px"
               :rows="4"
               type="textarea"
-              placeholder="We look forward to hearing from you. Thank you for helping to build a better customer system."
+              :placeholder="t('scoringGrade.feedbackPlaceholder')"
             />
           </div>
           <div class="buttom">
@@ -412,14 +457,14 @@ const submitDetails = (val: any) => {
                   <use xlink:href="#icon-icon_back_b"></use>
                 </svg>
               </span>
-              Previous
+              {{ t('dashboard.previous') }}
             </div>
             <div class="button_submit">
               <el-button
                 :disabled="buttonDisabled"
                 class="submit_button el-button--dark"
                 @click="submitDetails(inputdetails)"
-                >Submit</el-button
+                >{{ t('dashboard.submit') }}</el-button
               >
             </div>
           </div>
@@ -428,7 +473,7 @@ const submitDetails = (val: any) => {
           class="result"
           v-if="isshowDetails_submit"
           icon="success"
-          title="Submit Successful"
+          :title="t('scoringGrade.submitSuccessful')"
         >
           <template #icon>
             <el-image :src="submitsucessful" />
@@ -436,7 +481,7 @@ const submitDetails = (val: any) => {
           <template #sub-title>
             <div class="sub_title_text">{{ SubmitText }}</div>
             <div class="sub_title_text">
-              We are committed to working hard to provide better services.
+              {{ t('dashboard.submitAndCommitment') }}
             </div>
           </template>
         </el-result>

+ 4 - 1
src/views/Dashboard/src/components/TopMap.vue

@@ -4,6 +4,9 @@ import OriginIcon from '../image/hhh_2.png'
 import L from 'leaflet'
 import { useThemeStore } from '@/stores/modules/theme'
 import { formatNumber } from '@/utils/tools'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 
 const themeStore = useThemeStore()
 const MapDataList = ref([])
@@ -39,7 +42,7 @@ const addResetZoomButton = (center: L.LatLng, zoom: number) => {
       // 创建重置缩放按钮
       const resetZoomButton = L.DomUtil.create('a', 'reset-zoom-button leaflet-bar-part', container)
       resetZoomButton.href = '#'
-      resetZoomButton.title = 'Reset Zoom'
+      resetZoomButton.title = t('dashboard.resetZoom')
       resetZoomButton.innerHTML = `<div class="outer-ring" style="height: 100%; padding: 4px;border: 1px solid #2b2f36;border-radius: 50%;">
         <div class="inner-ring" style=" height: 100%; width: 100%; background-color: #2b2f36; border-radius: 50%;"></div>
         </div>`

+ 7 - 5
src/views/DestinationDelivery/src/DestinationDelivery.vue

@@ -1,9 +1,11 @@
 <script lang="ts" setup>
+import { useI18n } from 'vue-i18n'
 import ListView from './components/ListView.vue'
 import CalendarView from './components/CalendarView.vue'
 import { useRouter } from 'vue-router'
 import { useUserStore } from '@/stores/modules/user'
 
+const { t } = useI18n()
 const router = useRouter()
 const userStore = useUserStore()
 
@@ -34,14 +36,14 @@ const changePageType = () => {
 }
 const pageType = ref('Calendar View')
 const directionOptions = [
-  { label: 'Calendar View', value: 'Calendar View', icon: 'icon_ratesheet_b' },
-  { label: 'List View', value: 'List View', icon: 'icon_date_b' }
+  { label: t('destinationDelivery.calendarView'), value: 'Calendar View', icon: 'icon_ratesheet_b' },
+  { label: t('destinationDelivery.listView'), value: 'List View', icon: 'icon_date_b' }
 ]
 </script>
 <template>
   <div class="destination-delivery">
     <div class="header">
-      <span>Destination Delivery</span>
+      <span>{{ t('destinationDelivery.title') }}</span>
       <div class="operator">
         <el-button
           style="height: 40px"
@@ -50,7 +52,7 @@ const directionOptions = [
           v-if="userStore.userInfo.user_type === 'employee'"
         >
           <span style="margin-right: 4px" class="font_family icon-icon_configurations_b"></span>
-          <span style="font-weight: 400">Configurations</span></el-button
+          <span style="font-weight: 400">{{ t('destinationDelivery.configurations') }}</span></el-button
         >
         <el-button
           style="height: 38px"
@@ -59,7 +61,7 @@ const directionOptions = [
           v-if="userStore.userInfo.user_type !== 'employee'"
         >
           <span style="margin-right: 4px" class="font_family icon-icon_add_b"></span>
-          <span style="font-weight: 400">Create New Booking</span>
+          <span style="font-weight: 400">{{ t('destinationDelivery.createNewBooking') }}</span>
         </el-button>
       </div>
     </div>

+ 15 - 7
src/views/DestinationDelivery/src/components/CalendarTagDetailDialog.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { autoWidth } from '@/utils/table'
 import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
 import { useRowClickStyle } from '@/hooks/rowClickStyle'
@@ -55,7 +58,7 @@ onMounted(() => {
 const handleColumns = (columns: any) => {
   const newColumns = columns.map((item: any) => {
     let curColumn: any = {
-      title: item.title,
+      title: t(item.cleaned_field_name),
       field: item.field,
       width: '150px'
     }
@@ -98,7 +101,9 @@ const openDialog = (type, date, data) => {
   pageData.value = data
   tagType.value = type
 
-  title.value = `Recommended Delivery Shipments for ${dayjs(date).format('YYYY-MM-DD')}`
+  title.value = t('destinationDelivery.recommendedDeliveryShipmentsFor', {
+    date: dayjs(date).format('YYYY-MM-DD')
+  })
   tableData.value.data = data['shipmentDetail']
   nextTick(() => {
     tableRef.value &&
@@ -179,18 +184,21 @@ defineExpose({
               class="font_family icon-icon_delay_b1"
               style="margin-right: 3px; font-size: 13px"
             ></span>
-            <span class="type">{{ pageData?.endingNumber }} Free Storage Period Ends</span>
+            <span class="type"
+              >{{ pageData?.endingNumber }}
+              {{ t('destinationDelivery.freeStoragePeriodEnds') }}</span
+            >
           </div>
         </div>
       </template>
       <span style="display: inline-block; color: var(--color-neutral-2)"
-        >Total:
+        >{{ t('destinationDelivery.total') }}:
         {{ pageData?.shipmentNumber || 0 }}
-        shipments</span
+        {{ t('destinationDelivery.shipments') }}</span
       >
       <span style="color: var(--color-neutral-2)"> | </span>
       <span style="color: var(--color-neutral-2)"
-        >Total Cartons: {{ pageData?.shipmentCtns || 0 }} ctns</span
+        >{{ t('destinationDelivery.totalCartons') }}: {{ pageData?.shipmentCtns || 0 }} ctns</span
       >
       <vxe-grid style="margin-top: 8px" ref="tableRef" v-bind="tableData" :row-style="rowStyle">
         <template #download="{ row, column }">
@@ -203,7 +211,7 @@ defineExpose({
           </div>
         </template>
         <template #empty>
-          <div class="empty">No data</div>
+          <div class="empty">{{ t('destinationDelivery.noData') }}</div>
         </template>
       </vxe-grid>
     </el-dialog>

+ 30 - 23
src/views/DestinationDelivery/src/components/CalendarView.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { ref, computed, onMounted } from 'vue'
 import type { Dayjs } from 'dayjs'
 import dayjs from 'dayjs'
@@ -6,6 +9,7 @@ import CalendarTagDetailDialog from './CalendarTagDetailDialog.vue'
 import { useUserStore } from '@/stores/modules/user'
 import { debounce } from 'lodash'
 import axios from 'axios'
+import DestinationDelivery from '../DestinationDelivery.vue'
 
 const userStore = useUserStore()
 
@@ -57,7 +61,7 @@ const remoteMethod = (query: string) => {
     .catch((err) => {
       consignessList.value = []
       if (!axios.isCancel(err) && !newController.signal.aborted) {
-        ElMessage.error('Failed to load options')
+        ElMessage.error(t('destinationDelivery.failedToLoadOptions'))
       }
     })
     .finally(() => {
@@ -134,13 +138,10 @@ const jumpListPage = (date) => {
 }
 
 const test = () => {
-  ElMessageBox.alert(
-    "This account's password has expired and is currently unavailable. Please select a different customer account to continue.",
-    {
-      confirmButtonText: 'OK',
-      confirmButtonClass: 'el-button--dark'
-    }
-  )
+  ElMessageBox.alert(t('destinationDelivery.accountPasswordExpiredUnavailable'), {
+    confirmButtonText: t('common.ok'),
+    confirmButtonClass: 'el-button--dark'
+  })
 }
 </script>
 
@@ -193,18 +194,18 @@ const test = () => {
             <a-select-option v-for="m in 12" :key="m" :value="m">
               {{
                 [
-                  'Jan',
-                  'Feb',
-                  'Mar',
-                  'Apr',
-                  'May',
-                  'Jun',
-                  'Jul',
-                  'Aug',
-                  'Sep',
-                  'Oct',
-                  'Nov',
-                  'Dec'
+                  t('destinationDelivery.jan'),
+                  t('destinationDelivery.feb'),
+                  t('destinationDelivery.mar'),
+                  t('destinationDelivery.apr'),
+                  t('destinationDelivery.may'),
+                  t('destinationDelivery.jun'),
+                  t('destinationDelivery.jul'),
+                  t('destinationDelivery.aug'),
+                  t('destinationDelivery.sep'),
+                  t('destinationDelivery.oct'),
+                  t('destinationDelivery.nov'),
+                  t('destinationDelivery.dec')
                 ][m - 1]
               }}
             </a-select-option>
@@ -220,7 +221,7 @@ const test = () => {
             filterable
             reserve-keyword
             clearable
-            placeholder="Consignee"
+            :placeholder="t('destinationDelivery.consignee')"
             :loading="consigneeLoading"
             style="width: 240px"
             popper-class="part-id-select-popper"
@@ -284,9 +285,15 @@ const test = () => {
               <!-- <div class="label">Destination Booking</div> -->
               <div class="tag-style">
                 <span class="font_family icon-icon_booking_order_b" style="font-size: 12px"></span>
-                <span class="type">{{ getDataByDate(current, 'bookingNumber') }} Bookings</span>
+                <span class="type"
+                  >{{ getDataByDate(current, 'bookingNumber') }}
+                  {{ t('destinationDelivery.bookings') }}</span
+                >
                 <div class="grid-lines"></div>
-                <span class="ctns-tag">{{ getDataByDate(current, 'bookingCtns') }} ctns</span>
+                <span class="ctns-tag"
+                  >{{ getDataByDate(current, 'bookingCtns') }}
+                  {{ t('destinationDelivery.ctns') }}</span
+                >
               </div>
               <div class="list">
                 <div class="item" v-for="size in getDataByDate(current, 'ctnrSize')">

+ 9 - 7
src/views/DestinationDelivery/src/components/ConfiguRations/src/ConfiguRations.vue

@@ -1,29 +1,31 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
 import ConfigurationsTable from './components/ConfigurationsTable.vue'
 import { useCalculatingHeight } from '@/hooks/calculatingHeight'
 import { useRouter } from 'vue-router'
 
 const filterRef: Ref<HTMLElement | null> = ref(null)
 const router = useRouter()
+const { t } = useI18n()
 
-const AddRulesTableColumns = ref([
+const AddRulesTableColumns: any = ref([
   {
     field: 'country',
-    title: 'Country',
+    title: t('common.country'),
     type: 'link',
     width: '10%',
     formatter: ''
   },
   {
     field: 'station',
-    title: 'Stations',
+    title: t('destinationDelivery.stations'),
     type: 'normal',
     width: '20%',
     formatter: ''
   },
   {
     field: 'booking_window_desc',
-    title: 'Booking Window',
+    title: t('destinationDelivery.bookingWindow'),
     type: 'normal',
     formatter: ''
   }
@@ -46,16 +48,16 @@ const ToCreateRule = () => {
 
 <template>
   <div>
-    <div class="Title">Configuration</div>
+    <div class="Title">{{ t('destinationDelivery.configuration') }}</div>
     <div class="AddRules">
       <div class="monitoring_flex">
-        <div class="subscribedTitle">Added Rules</div>
+        <div class="subscribedTitle">{{ t('destinationDelivery.addedRules') }}</div>
         <el-button
           class="el-button--main"
           style="height: 40px"
           v-if="tabledatalength != 0 && tabledatalength != null"
           @click="ToCreateRule"
-          >+ Add Rule</el-button
+          >+ {{ t('destinationDelivery.addRule') }}</el-button
         >
       </div>
       <ConfigurationsTable

+ 15 - 12
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/ConfigurationsTable.vue

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
 import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
 import { useRowClickStyle } from '@/hooks/rowClickStyle'
 import { formatNumber } from '@/utils/tools'
@@ -7,6 +8,7 @@ import { useRouter } from 'vue-router'
 import DefaultConfiguration from '../images/default_configuration@2x.png'
 
 const router = useRouter()
+const { t } = useI18n()
 
 interface ColumnConfig {
   field: string
@@ -45,7 +47,7 @@ const pageInfo = ref({ pageNo: 1, pageSize: 15, total: 0 })
 const handleColumns = (columns: any) => {
   const newColumns = columns.map((item: any) => {
     let curColumn: any = {
-      title: item.title,
+      title: t(item.cleaned_field_name),
       field: item.field,
       width: item.width
     }
@@ -64,7 +66,7 @@ const handleColumns = (columns: any) => {
 const getTableColumns = async () => {
   tableData.value.columns = handleColumns(columnstest.value)
   tableData.value.columns?.push({
-    title: 'Operation',
+    title: t('common.operation'),
     fixed: 'left',
     width: 100,
     slots: { default: 'action' }
@@ -145,14 +147,12 @@ onMounted(() => {
       <template #empty>
         <div>
           <img :src="DefaultConfiguration" width="100" />
-          <div class="empty-text">
-            Configure available destination delivery regions and time slots.
-          </div>
+          <div class="empty-text">{{ t('destinationDelivery.configureRegionsAndTimeSlots') }}</div>
           <el-button
             class="el-button--main"
             style="width: 117px; height: 40px"
             @click="clickAddNewRule"
-            >+ Add Rule</el-button
+            >+ {{ t('destinationDelivery.addRule') }}</el-button
           >
         </div>
       </template>
@@ -169,15 +169,18 @@ onMounted(() => {
         <el-popover trigger="click" :visible="row.visible" placement="left" :width="480">
           <div class="delete_title">
             <span class="font_family icon_alert icon-icon_tipsfilled_b"></span>
-            Delete Rule
+            {{ t('destinationDelivery.deleteRule') }}
           </div>
-          <p class="delete_content">Are you sure to delete this notification event?</p>
+          <p class="delete_content">{{ t('destinationDelivery.confirmDeleteRule') }}</p>
           <div style="text-align: right; margin: 0; padding: 8px">
-            <el-button style="width: 100px" class="el-button--default" @click="row.visible = false"
-              >cancel</el-button
+            <el-button
+              style="width: 100px"
+              class="el-button--default"
+              @click="row.visible = false"
+              >{{ t('common.cancel') }}</el-button
             >
             <el-button style="width: 100px" type="warning" @click="deleteMoniTable(row)">
-              OK
+              {{ t('common.ok') }}
             </el-button>
           </div>
           <template #reference>
@@ -190,7 +193,7 @@ onMounted(() => {
     </vxe-grid>
   </div>
   <div class="pagination">
-    <span>Total {{ formatNumber(pageInfo.total) }}</span>
+    <span>{{ t('destinationDelivery.total') }} {{ formatNumber(pageInfo.total) }}</span>
     <el-pagination
       v-model:current-page="pageInfo.pageNo"
       v-model:page-size="pageInfo.pageSize"

+ 23 - 20
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/CreateNewRule.vue

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
 import SelectStation from './SelectStation.vue'
 import SetBookingWindow from './SetBookingWindow.vue'
 import RecommendDate from './RecommendDate.vue'
@@ -6,6 +7,7 @@ import { useRouter } from 'vue-router'
 import submitsucessful from '../images/icon_success_big@2x.png'
 
 const router = useRouter()
+const { t } = useI18n()
 const { currentRoute } = router
 const { value } = currentRoute
 const { query } = value
@@ -302,8 +304,8 @@ onMounted(() => {
 <template>
   <div>
     <div class="Title">
-      <div v-if="a != undefined">Modify Rule</div>
-      <div v-else>Create New Rule</div>
+      <div v-if="a != undefined">{{ t('destinationDelivery.modifyRule') }}</div>
+      <div v-else>{{ t('destinationDelivery.createNewRule') }}</div>
       <div class="operator">
         <el-button
           @click="CancelRulesVisible = true"
@@ -311,7 +313,7 @@ onMounted(() => {
           type="default"
         >
           <span style="margin-right: 4px" class="font_family icon-icon_return_b"></span>
-          <span style="font-weight: 400">Cancel</span></el-button
+          <span style="font-weight: 400">{{ t('common.cancel') }}</span></el-button
         >
         <el-button
           style="height: 40px; width: 120px"
@@ -328,33 +330,33 @@ onMounted(() => {
             "
             class="font_family icon-icon_submit_b"
           ></span>
-          <span style="font-weight: 400">Save</span>
+          <span style="font-weight: 400">{{ t('common.save') }}</span>
         </el-button>
         <!-- 取消保存 -->
         <el-dialog v-model="CancelRulesVisible" width="480">
-          <div style="font-weight: 400">You have unsaved changes.</div>
-          <div style="font-weight: 400">Are you sure you want to leave this page?</div>
+          <div style="font-weight: 400">{{ t('destinationDelivery.unsavedChanges') }}</div>
+          <div style="font-weight: 400">{{ t('destinationDelivery.confirmLeavePage') }}</div>
           <template #footer>
             <div class="dialog-footer">
-              <el-button type="default" @click="CancelRulesVisible = false" style="width: 100px"
-                >Cancel</el-button
-              >
+              <el-button type="default" @click="CancelRulesVisible = false" style="width: 100px">{{
+                t('common.cancel')
+              }}</el-button>
               <el-button class="el-button--warning" @click="router.back()" style="width: 100px">
-                OK
+                {{ t('common.ok') }}
               </el-button>
             </div>
           </template>
           <template #header>
             <div class="dialog-header warning-header">
               <span class="font_family icon-icon_tipsfilled_b"></span>
-              Unsaved Changes
+              {{ t('destinationDelivery.unsavedChangesTitle') }}
             </div>
           </template>
         </el-dialog>
         <!-- 保存失败 -->
         <el-dialog v-model="UnableSaveVisible" width="480">
           <div>{{ missingmessage }}</div>
-          <div>Please complete all required fields.</div>
+          <div>{{ t('destinationDelivery.completeRequiredFields') }}</div>
           <template #footer>
             <div class="dialog-footer">
               <el-button
@@ -362,14 +364,14 @@ onMounted(() => {
                 @click="UnableSaveVisible = false"
                 style="width: 100px"
               >
-                OK
+                {{ t('common.ok') }}
               </el-button>
             </div>
           </template>
           <template #header>
             <div class="unable-save-header dialog-header">
               <span class="font_family icon-icon_fail_fill_b"></span>
-              Unable to Save
+              {{ t('destinationDelivery.unableToSave') }}
             </div>
           </template>
         </el-dialog>
@@ -378,12 +380,12 @@ onMounted(() => {
           <div style="text-align: center">
             <el-image :src="submitsucessful" style="width: 64px" />
           </div>
-          <div style="text-align: center; margin-top: 20px">Saved successfully</div>
+          <div style="text-align: center; margin-top: 20px">{{ t('common.saveSuccess') }}</div>
         </el-dialog>
       </div>
     </div>
     <div class="setting-content">
-      <div class="setting-top-title">Setting</div>
+      <div class="setting-top-title">{{ t('destinationDelivery.setting') }}</div>
       <el-collapse v-model="activeRules" @change="IsFirstActive = !IsFirstActive">
         <el-collapse-item name="SelectStation">
           <template #title>
@@ -395,7 +397,8 @@ onMounted(() => {
                   ></use>
                 </svg>
               </span>
-              <span class="stars_red">*</span>Select Station (Enable Booking)
+              <span class="stars_red">*</span
+              >{{ t('destinationDelivery.selectStationEnableBooking') }}
             </div>
           </template>
           <div>
@@ -420,7 +423,7 @@ onMounted(() => {
                   ></use>
                 </svg>
               </span>
-              <span class="stars_red">*</span>Set Booking Window
+              <span class="stars_red">*</span>{{ t('destinationDelivery.setBookingWindow') }}
             </div>
           </template>
           <div>
@@ -464,7 +467,7 @@ onMounted(() => {
                   ></use>
                 </svg>
               </span>
-              <span class="stars_red">*</span>KLN PIC
+              <span class="stars_red">*</span>{{ t('destinationDelivery.klnPic') }}
             </div>
           </template>
           <div>
@@ -473,7 +476,7 @@ onMounted(() => {
               filterable
               remote
               multiple
-              placeholder="Select Employee Account"
+              :placeholder="t('destinationDelivery.selectEmployeeAccount')"
               :remote-method="querySearchAsync"
               :loading="loading"
               style="width: 400px; margin-bottom: 5px"

+ 49 - 26
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/RecommendDate.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import SelectValue from './SelectValue.vue'
 import { ref } from 'vue'
 import { cloneDeep } from 'lodash'
@@ -45,11 +48,13 @@ const isAir = ref(false)
 const isSea = ref(false)
 const RecommendCheckedList = ref<string[]>([])
 // 选项配置
-const AirRuleTypeoptions = ref<RuleOption[]>([{ label: 'Specific Rule', value: 'Specific Rule' }])
+const AirRuleTypeoptions = ref<RuleOption[]>([
+  { label: t('destinationDelivery.specificRule'), value: 'Specific Rule' }
+])
 
 const RuleTypeoptions = ref<RuleOption[]>([
-  { label: 'Specific Rule', value: 'Specific Rule' },
-  { label: 'Single Dimension', value: 'Single Dimension' }
+  { label: t('destinationDelivery.specificRule'), value: 'Specific Rule' },
+  { label: t('destinationDelivery.singleDimension'), value: 'Single Dimension' }
 ])
 
 // 规则数据
@@ -410,15 +415,15 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
 <template>
   <div>
     <el-radio-group v-model="Recommendradio" @change="ChangeFrequency">
-      <el-radio :value="1">No Specific recommended time for choosing delivery date</el-radio>
+      <el-radio :value="1">{{ t('destinationDelivery.deliveryDateTips') }}</el-radio>
       <el-radio :value="2">
-        <div>Recommend Delivery Date (ETA/ATA)</div>
+        <div>{{ t('destinationDelivery.recommendDeliveryDate') }}</div>
         <div v-if="isRecommendETA" class="oceanCheckbox">
           <el-checkbox-group v-model="RecommendCheckedList" @change="CheckChange">
             <!-- Air 部分 -->
             <el-checkbox class="delayedType" value="Air" @click="handleCheckboxClick">
               <div class="titlecheckbox clickable-area">
-                <div>Air</div>
+                <div>{{ t('destinationDelivery.air') }}</div>
                 <span
                   class="icon_grey font_family"
                   :class="isAir ? 'icon-icon_dropdown_b' : 'icon-icon_up_b'"
@@ -426,9 +431,15 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
               </div>
               <div v-if="isAir" class="radiocheckbox" style="margin-top: 16px; padding-left: 8px">
                 <div class="AirCoulumn">
-                  <div class="AicoulumnTitile" style="width: 10%">priority</div>
-                  <div class="AicoulumnTitile" style="width: 20%">Rule Type</div>
-                  <div class="AicoulumnTitile" style="width: 40%">Air Port</div>
+                  <div class="AicoulumnTitile" style="width: 10%">
+                    {{ t('destinationDelivery.priority') }}
+                  </div>
+                  <div class="AicoulumnTitile" style="width: 20%">
+                    {{ t('destinationDelivery.ruleType') }}
+                  </div>
+                  <div class="AicoulumnTitile" style="width: 40%">
+                    {{ t('destinationDelivery.port') }}
+                  </div>
                   <div
                     style="
                       display: flex;
@@ -437,15 +448,17 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
                       width: 20%;
                     "
                   >
-                    <div class="AicoulumnTitile2">Recommended Delivery Date</div>
+                    <div class="AicoulumnTitile2">
+                      {{ t('destinationDelivery.recommendedDeliveryDate') }}
+                    </div>
                     <div style="display: flex; height: 24px; align-items: center">
                       <div
                         class="datetitle"
                         style="border-right: 1px solid var(--color-system-border)"
                       >
-                        From (ETA/ATA + Days)
+                        {{ t('destinationDelivery.fromEtaAtaDays') }}
                       </div>
-                      <div class="datetitle">To (ETA/ATA + Days)</div>
+                      <div class="datetitle">{{ t('destinationDelivery.toEtaAtaDays') }}</div>
                     </div>
                   </div>
                   <div
@@ -453,7 +466,7 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
                     style="width: 10%"
                     @click="AddRuleItem(AirContentList, 'Air')"
                   >
-                    + Add
+                    + {{ t('destinationDelivery.add') }}
                   </div>
                 </div>
                 <div class="AirContent" v-for="(item, index) in AirContentList" :key="index">
@@ -491,7 +504,7 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
                         (val) =>
                           handleInput(val, index, 'recommended_delivery_from', AirContentList)
                       "
-                      placeholder="Input"
+                      :placeholder="t('common.input')"
                       v-model="item.recommended_delivery_from"
                     />
                   </div>
@@ -500,7 +513,7 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
                       @input="
                         (val) => handleInput(val, index, 'recommended_delivery_to', AirContentList)
                       "
-                      placeholder="Input"
+                      :placeholder="t('common.input')"
                       v-model="item.recommended_delivery_to"
                     />
                   </div>
@@ -520,7 +533,7 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
             <!-- Sea 部分 -->
             <el-checkbox class="delayedType" value="Sea" @click="handleCheckboxClick">
               <div class="titlecheckbox clickable-area">
-                <div>Sea</div>
+                <div>{{ t('destinationDelivery.sea') }}</div>
                 <span
                   class="icon_grey font_family"
                   :class="isSea ? 'icon-icon_dropdown_b' : 'icon-icon_up_b'"
@@ -528,10 +541,18 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
               </div>
               <div v-if="isSea" style="margin-top: 16px; padding-left: 8px">
                 <div class="AirCoulumn">
-                  <div class="AicoulumnTitile" style="width: 10%">priority</div>
-                  <div class="AicoulumnTitile" style="width: 14%">Rule Type</div>
-                  <div class="AicoulumnTitile" style="width: 23%">Port</div>
-                  <div class="AicoulumnTitile" style="width: 23%">Carrier</div>
+                  <div class="AicoulumnTitile" style="width: 10%">
+                    {{ t('destinationDelivery.priority') }}
+                  </div>
+                  <div class="AicoulumnTitile" style="width: 14%">
+                    {{ t('destinationDelivery.ruleType') }}
+                  </div>
+                  <div class="AicoulumnTitile" style="width: 23%">
+                    {{ t('destinationDelivery.port') }}
+                  </div>
+                  <div class="AicoulumnTitile" style="width: 23%">
+                    {{ t('destinationDelivery.carrier') }}
+                  </div>
                   <div
                     style="
                       display: flex;
@@ -540,15 +561,17 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
                       width: 20%;
                     "
                   >
-                    <div class="AicoulumnTitile2">Recommended Delivery Date</div>
+                    <div class="AicoulumnTitile2">
+                      {{ t('destinationDelivery.recommendedDeliveryDate') }}
+                    </div>
                     <div style="display: flex; height: 24px; align-items: center">
                       <div
                         class="datetitle"
                         style="border-right: 1px solid var(--color-system-border)"
                       >
-                        From (ETA/ATA + Days)
+                        {{ t('destinationDelivery.fromEtaAtaDays') }}
                       </div>
-                      <div class="datetitle">To (ETA/ATA + Days)</div>
+                      <div class="datetitle">{{ t('destinationDelivery.toEtaAtaDays') }}</div>
                     </div>
                   </div>
                   <div
@@ -556,7 +579,7 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
                     style="width: 10%"
                     @click="AddRuleItem(SeaContentList, 'Sea')"
                   >
-                    + Add
+                    + {{ t('destinationDelivery.add') }}
                   </div>
                 </div>
                 <div class="AirContent" v-for="(item, index) in SeaContentList" :key="index">
@@ -606,7 +629,7 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
                         (val) =>
                           handleInput(val, index, 'recommended_delivery_from', SeaContentList)
                       "
-                      placeholder="Input"
+                      :placeholder="t('common.input')"
                       v-model="item.recommended_delivery_from"
                     />
                   </div>
@@ -615,7 +638,7 @@ const changeRuleType = (val: string, index: number, list: RuleItem[]) => {
                       @input="
                         (val) => handleInput(val, index, 'recommended_delivery_to', SeaContentList)
                       "
-                      placeholder="Input"
+                      :placeholder="t('common.input')"
                       v-model="item.recommended_delivery_to"
                     />
                   </div>

+ 6 - 3
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SelectStation.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 
 interface CountryItem {
   value: string
@@ -71,7 +74,7 @@ const querySearchAsync = (queryString: string, cb: (arg: any) => void) => {
     if(results.length == 0) {
       cb([{ 
         isNoData: true, 
-        value: 'No Data' 
+        value: t('destinationDelivery.noData') 
       }]);
     } else {
       cb(results)
@@ -116,7 +119,7 @@ onMounted(() => {
     <el-autocomplete
       v-model="SelectCountry"
       style="width: 400px;margin-bottom: 5px;"
-      placeholder="Select Country"
+      :placeholder="t('destinationDelivery.selectCountry')"
       :popper-class="popperClass"
       :fetch-suggestions="querySearchAsync"
       @select="handleSelect"
@@ -132,7 +135,7 @@ onMounted(() => {
       </template>
     </el-autocomplete>
     <div class="station-list">
-      <div class="station-list-title">Station List</div>
+      <div class="station-list-title">{{ t('destinationDelivery.stationList') }}</div>
       <div class="oceanCheckbox">
         <el-checkbox-group v-model="CheckedList" :class="{isEmpty :  CheckboxList.length === 0}" @change="handlechangestation">
           <el-checkbox

+ 4 - 1
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SelectValue.vue

@@ -1,5 +1,8 @@
 <script setup lang="ts">
 import { ref, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 
 interface PortOption {
   value: string
@@ -185,7 +188,7 @@ const hiddenTagsCount = computed(() => {
       filterable
       remote
       :disabled="typeisDisabled  == '*Default Rule'"
-      placeholder="Select"
+      :placeholder="t('destinationDelivery.select')"
       :remote-method="remoteMethod"
       popper-class="custom-sheader-select"
       @change="handelchangeSelect"

+ 39 - 37
src/views/DestinationDelivery/src/components/ConfiguRations/src/components/SetBookingWindow.vue

@@ -1,4 +1,6 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 const isBookingETD = ref(false)
 const isBookingETA = ref(false)
 const beforeETDdays = ref('')
@@ -16,17 +18,17 @@ const props = defineProps({
 const windowradio = ref()
 const setbookingdata = ref(props.setbookingdata)
 const initBookingWindowData = () => {
-  if(setbookingdata.value) {
+  if (setbookingdata.value) {
     windowradio.value = setbookingdata.value.windowradio
-  if(windowradio.value == 2) {
-    isBookingETD.value = true
-    beforeETDdays.value = setbookingdata.value.windowBeforeDays
-    afterETDdays.value = setbookingdata.value.windowAfterDays
-  } else if (windowradio.value == 3) {
-    isBookingETA.value = true
-    beforeETAdays.value = setbookingdata.value.windowBeforeDays
-    afterETAdays.value = setbookingdata.value.windowAfterDays
-  }
+    if (windowradio.value == 2) {
+      isBookingETD.value = true
+      beforeETDdays.value = setbookingdata.value.windowBeforeDays
+      afterETDdays.value = setbookingdata.value.windowAfterDays
+    } else if (windowradio.value == 3) {
+      isBookingETA.value = true
+      beforeETAdays.value = setbookingdata.value.windowBeforeDays
+      afterETAdays.value = setbookingdata.value.windowAfterDays
+    }
   }
 }
 
@@ -71,63 +73,63 @@ const ChangeFrequency = (val: any) => {
 // 处理输入值,确保只能是正整数
 const validatePositiveInteger = (value: string) => {
   // 移除非数字字符
-  let numStr = value.replace(/[^\d]/g, '');
+  let numStr = value.replace(/[^\d]/g, '')
   // 如果为空字符串,直接返回
-  if (numStr === '') return '';
+  if (numStr === '') return ''
   // 转换为整数
-  let num = parseInt(numStr, 10);
+  let num = parseInt(numStr, 10)
   // 处理NaN情况,确保最小值为1
   if (isNaN(num) || num < 1) {
-    return '1';
+    return '1'
   }
   // 返回数字字符串(去掉前导零)
-  return num.toString();
-};
+  return num.toString()
+}
 
 // 处理输入变化
 const handleInput = (val: string, target: 'ETD_BEFORE' | 'ETD_AFTER' | 'ETA_BEFORE' | 'ETA_AFTER') => {
-  const validatedValue = validatePositiveInteger(val);
+  const validatedValue = validatePositiveInteger(val)
   switch (target) {
     case 'ETD_BEFORE':
-      beforeETDdays.value = validatedValue;
-      ChangeFrequency(2);
-      break;
+      beforeETDdays.value = validatedValue
+      ChangeFrequency(2)
+      break
     case 'ETD_AFTER':
-      afterETDdays.value = validatedValue;
-      ChangeFrequency(2);
-      break;
+      afterETDdays.value = validatedValue
+      ChangeFrequency(2)
+      break
     case 'ETA_BEFORE':
-      beforeETAdays.value = validatedValue;
-      ChangeFrequency(3);
-      break;
+      beforeETAdays.value = validatedValue
+      ChangeFrequency(3)
+      break
     case 'ETA_AFTER':
-      afterETAdays.value = validatedValue;
-      ChangeFrequency(3);
-      break;
+      afterETAdays.value = validatedValue
+      ChangeFrequency(3)
+      break
   }
-};
+}
 
 </script>
 <template>
   <div>
     <el-radio-group v-model="windowradio" @change="ChangeFrequency">
-      <el-radio :value="1">No Specific time restrictions for creating booking</el-radio>
+      <el-radio :value="1">{{ t('destinationDelivery.noTimeRestrictionsForBooking') }}</el-radio>
       <el-radio :value="2">
-        <div>Booking Window (ETD/ATD)</div>
+        <div>{{ t('destinationDelivery.bookingWindowEtdAtd') }}</div>
         <div class="ETDWindow" v-if="isBookingETD">
           <el-input style="width: 80px;height: 32px;" v-model="beforeETDdays" @input="val => handleInput(val, 'ETD_BEFORE')"></el-input>
-          days before to 
+          {{ t('destinationDelivery.daysBeforeTo') }}
           <el-input style="width: 80px;height: 32px;" v-model="afterETDdays" @input="val => handleInput(val, 'ETD_AFTER')"></el-input>
-          days after
+          {{ t('destinationDelivery.daysAfter') }}
         </div>
       </el-radio>
       <el-radio :value="3">
-        <div>Booking Window (ETA/ATA)</div>
+        <div>{{ t('destinationDelivery.bookingWindowEtaAta') }}</div>
         <div class="ETDWindow" v-if="isBookingETA">
           <el-input style="width: 80px;height: 32px;" v-model="beforeETAdays" @input="val => handleInput(val, 'ETA_BEFORE')"></el-input>
-          days before to 
+          {{ t('destinationDelivery.daysBeforeTo') }}
           <el-input style="width: 80px;height: 32px;" v-model="afterETAdays" @input="val => handleInput(val, 'ETA_AFTER')"></el-input>
-          days after
+          {{ t('destinationDelivery.daysAfter') }}
         </div>
       </el-radio>
     </el-radio-group>

+ 156 - 118
src/views/DestinationDelivery/src/components/CreateNewBooking/src/CreateNewbooking.vue

@@ -9,6 +9,9 @@ import { useUserStore } from '@/stores/modules/user'
 import { useRouter, useRoute } from 'vue-router'
 import { ElMessage } from 'element-plus'
 import dayjs from 'dayjs'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 
 const userStore = useUserStore()
 const router = useRouter()
@@ -72,11 +75,11 @@ const checkShipmentsSubmitInfo = ref({})
 const RecommendateList = ref([])
 const ModeType = ref([
   {
-    label: 'Truck',
+    label: t('destinationDelivery.truck'),
     value: 'Truck'
   },
   {
-    label: 'Rail',
+    label: t('destinationDelivery.rail'),
     value: 'Rail'
   }
 ])
@@ -361,7 +364,7 @@ const SaveNewAddress = () => {
     currentEditAddress.value = null
   } else {
     ElMessage({
-      message: 'Required fields not entered.',
+      message: t('destinationDelivery.requiredFieldsNotEntered'),
       type: 'warning'
     })
   }
@@ -572,7 +575,7 @@ const handleClickAddress = () => {
 
 //选择日期
 const changeDatePicker = (val: any) => {
-  if (val === null) {
+  if (val === null || !specialDates.value.length) {
     isRecommendDate.value = false
     recommendateWarning.value = ''
     isConsistent.value = false
@@ -581,18 +584,18 @@ const changeDatePicker = (val: any) => {
   if (specialDates.value.length != 0) {
     if (isConsistent.value) {
       isRecommendDate.value = true
-      recommendateWarning.value = 'This date for following shipments is outside recommended period.'
+      recommendateWarning.value = t('destinationDelivery.outsideRecommendedPeriodForFollowing')
       isConsistent.value = false
     } else {
       if (!specialDates.value.includes(val)) {
         isRecommendDate.value = true
-        recommendateWarning.value = 'This date is outside our recommended period.'
+        recommendateWarning.value = t('destinationDelivery.outsideRecommendedPeriod')
         isConsistent.value = false
       }
     }
   } else {
     isRecommendDate.value = true
-    recommendateWarning.value = 'This date for following shipments is outside recommended period.'
+    recommendateWarning.value = t('destinationDelivery.outsideRecommendedPeriodForFollowing')
     isConsistent.value = false
   }
 }
@@ -720,32 +723,35 @@ onMounted(() => {
 <template>
   <div>
     <div class="Title">
-      <div v-if="a == undefined">Create New Booking</div>
-      <div v-else>Modify Booking</div>
+      <div v-if="a == undefined">{{ t('destinationDelivery.createNewBooking') }}</div>
+      <div v-else>{{ t('destinationDelivery.modifyBooking') }}</div>
       <div class="flex">
         <div class="select-info">
-          <span style="color: var(--color-neutral-2)">Selected: </span>
+          <span style="color: var(--color-neutral-2)"
+            >{{ t('destinationDelivery.selected') }}:
+          </span>
           <span
-            >{{ bookingTableRef?.getTableCheckedRows().length }} Shipments|{{
-              ctnsCount
-            }}
-            ctns</span
+            >{{ bookingTableRef?.getTableCheckedRows().length }}
+            {{ t('destinationDelivery.shipments') }}|{{ ctnsCount }}
+            {{ t('destinationDelivery.ctns') }}</span
           >
         </div>
-        <el-button @click="CancelRulesVisible = true" class="el-button--default create-button"
-          ><span class="font_family icon-icon_return_b"></span> Cancel</el-button
-        >
+        <el-button @click="CancelRulesVisible = true" class="el-button--default create-button">
+          <span class="font_family icon-icon_return_b"></span> {{ t('common.cancel') }}
+        </el-button>
         <el-button
           :disabled="isNotSubmit"
           @click="SubmitBooking"
           class="el-button--main create-button"
-          ><span class="font_family icon-icon_submit_b"></span> Submit</el-button
+          ><span class="font_family icon-icon_submit_b"></span>
+          {{ t('destinationDelivery.submit') }}</el-button
+        >
         >
       </div>
     </div>
     <div class="booking-info" v-if="a != undefined">
       <div class="booking-no">
-        <span class="no">Booking No.{{ booking }}</span>
+        <span class="no">{{ t('booking.bookingNo') }}{{ booking }}</span>
         <v-tag class="tag" type="Pending Approval">{{ status }}</v-tag>
       </div>
     </div>
@@ -753,19 +759,18 @@ onMounted(() => {
     <!-- Select Shipments -->
     <div class="Delivery" style="font-weight: bold">
       <span class="serial-number">1</span>
-      <span class="stars_red">*</span>Select Shipments<span class="title_warning"
-        >*Please select items with the same consignee.</span
-      >
+      <span class="stars_red">*</span>{{ t('destinationDelivery.selectShipments')
+      }}<span class="title_warning">*{{ t('destinationDelivery.selectSameConsigneeTip') }}</span>
     </div>
     <div class="select_shipments">
       <div v-if="isNotSameConfiguration" class="alertIndormation">
-        Please select same consignee with same delivery information
+        {{ t('destinationDelivery.selectSameConsigneeWarning') }}
       </div>
 
       <div class="shipments_search" v-if="a == undefined">
         <div class="top-filter-search">
           <el-input
-            placeholder="Enter Booking/HBL/PO/Carrier Booking No. "
+            :placeholder="t('destinationDelivery.searchBookingPlaceholder')"
             v-model="CreateNewBOokingSearch"
             class="log_input"
           >
@@ -776,26 +781,30 @@ onMounted(() => {
           <div class="input-with-label">
             <!-- <AutoSelect ASPlaceholder="Input Vessel Name" :ASValue="VesselName" @changeFocus="changeFocus"></AutoSelect> -->
             <el-input
-              placeholder="Shipper"
+              :placeholder="t('destinationDelivery.shipper')"
               v-model="ShipperValue"
               @focus="changeFocustest('Shipper', true)"
               @blur="changeFocustest('Shipper', false)"
               class="log_input"
             >
             </el-input>
-            <span v-if="showLabelShipper" class="border-label">Shipper</span>
+            <span v-if="showLabelShipper" class="border-label">{{
+              t('destinationDelivery.shipper')
+            }}</span>
           </div>
           <div class="input-with-label">
             <!-- <AutoSelect ASPlaceholder="Input Vessel Name" :ASValue="VesselName" @changeFocus="changeFocus"></AutoSelect> -->
             <el-input
-              placeholder="Consignee"
+              :placeholder="t('destinationDelivery.consignee')"
               v-model="ConsigneeValue"
               @focus="changeFocustest('Consignee', true)"
               @blur="changeFocustest('Consignee', false)"
               class="log_input"
             >
             </el-input>
-            <span v-if="showLabelConsignee" class="border-label">Consignee</span>
+            <span v-if="showLabelConsignee" class="border-label">{{
+              t('destinationDelivery.consignee')
+            }}</span>
           </div>
 
           <div class="input-with-label">
@@ -806,7 +815,7 @@ onMounted(() => {
               @DateRangeChange="DateRangeChangeETA"
               @DateChangeFocus="DateChangeFocusETA"
             ></CalendarDate>
-            <span v-if="showETAlabel" class="border-label">ETA</span>
+            <span v-if="showETAlabel" class="border-label">{{ t('destinationDelivery.eta') }}</span>
           </div>
           <div class="input-with-label">
             <CalendarDate
@@ -815,7 +824,7 @@ onMounted(() => {
               @DateRangeChange="DateRangeChangeATA"
               @DateChangeFocus="DateChangeFocusATA"
             ></CalendarDate>
-            <span v-if="showataLabel" class="border-label">ATA</span>
+            <span v-if="showataLabel" class="border-label">{{ t('destinationDelivery.ata') }}</span>
           </div>
 
           <div class="input-with-label">
@@ -826,20 +835,20 @@ onMounted(() => {
               @focus="isFocused.deliveryDate = true"
               @blur="isFocused.deliveryDate = false"
               :format="userStore.dateFormat"
-              placeholder="Recommended Delivery Date"
+              :placeholder="t('destinationDelivery.recommendedDeliveryDate')"
               valueFormat="MM/DD/YYYY"
               style="width: 100%; height: 40px"
             >
             </a-date-picker>
-            <span v-if="isFocused.deliveryDate" class="border-label"
-              >Recommended Delivery Date</span
-            >
+            <span v-if="isFocused.deliveryDate" class="border-label">{{
+              t('destinationDelivery.recommendedDeliveryDate')
+            }}</span>
           </div>
 
           <div class="input-with-label">
             <!-- <AutoSelect ASPlaceholder="Input Vessel Name" :ASValue="VesselName" @changeFocus="changeFocus"></AutoSelect> -->
             <el-input
-              placeholder="Input Vessel Name"
+              :placeholder="t('destinationDelivery.inputVesselName')"
               v-model="VesselNametest"
               @focus="changeFocustest('Vessel', true)"
               @blur="changeFocustest('Vessel', false)"
@@ -847,14 +856,16 @@ onMounted(() => {
               style="width: 100%"
             >
             </el-input>
-            <span v-if="showLabelVessel" class="border-label">Vessel Name</span>
+            <span v-if="showLabelVessel" class="border-label">{{
+              t('destinationDelivery.vesselName')
+            }}</span>
           </div>
           <div class="right-btn">
             <el-button
               style="width: 108px"
               class="el-button--dark create-button"
               @click="SearchShipment"
-              >Search</el-button
+              >{{ t('common.search') }}</el-button
             >
           </div>
         </div>
@@ -867,16 +878,17 @@ onMounted(() => {
     <!-- Delivery Information -->
     <div class="Delivery">
       <span class="serial-number">2</span>
-      <span>Delivery Information</span>
+      <span>{{ t('destinationDelivery.deliveryInformation') }}</span>
     </div>
     <div class="delivery_address">
       <div class="deliverty_flex">
-        <div><span class="stars_red">*</span>Delivery Address</div>
+        <div><span class="stars_red">*</span>{{ t('destinationDelivery.deliveryAddress') }}</div>
         <el-button
           :disabled="isNotClickAddress"
           @click="handleClickAddress"
           class="el-button--noborder--configuration"
-          ><span class="font_family icon-icon_location_b"></span> Address Book</el-button
+          ><span class="font_family icon-icon_location_b"></span>
+          {{ t('destinationDelivery.addressBook') }}</el-button
         >
       </div>
       <div class="empty_address" v-if="isselectedAddress == null">
@@ -886,7 +898,7 @@ onMounted(() => {
           class="el-button--main"
           @click="AddNewAddressDelivery"
         >
-          + Add New Address</el-button
+          + {{ t('destinationDelivery.addNewAddress') }}</el-button
         >
       </div>
       <div v-else class="addressradio" style="padding-bottom: 4px">
@@ -933,10 +945,12 @@ onMounted(() => {
       </div>
       <div class="delivery_type">
         <div>
-          <div class="delivery_type_title"><span class="stars_red">*</span>Mode Type</div>
+          <div class="delivery_type_title">
+            <span class="stars_red">*</span>{{ t('destinationDelivery.modeType') }}
+          </div>
           <el-select
             v-model="modetypeValue"
-            placeholder="Select"
+            :placeholder="t('destinationDelivery.select')"
             :disabled="isNotClickAddress"
             style="width: 240px"
           >
@@ -950,23 +964,22 @@ onMounted(() => {
         </div>
         <div style="margin: 0 12px">
           <div class="delivery_type_title">
-            <span class="stars_red">*</span>Preferred Delivery Date
+            <span class="stars_red">*</span>{{ t('destinationDelivery.preferredDeliveryDate') }}
           </div>
           <a-date-picker
             v-model:value="DateValue"
             :disabled="isNotClickAddress"
-            @change="changeDatePicker"
             :showToday="false"
             style="width: 240px"
             :format="userStore.dateFormat"
             valueFormat="YYYY.MM.DD"
-            placeholder="Please Select Date"
+            :placeholder="t('destinationDelivery.pleaseSelectDate')"
             class="delivery-date-picker"
           >
             <template #renderExtraFooter>
               <div class="recommended">
                 <div class="recommend_color"></div>
-                Recommended delivery date
+                {{ t('destinationDelivery.recommendedDeliveryDate') }}
               </div>
             </template>
             <template #dateRender="{ current }">
@@ -977,7 +990,9 @@ onMounted(() => {
           </a-date-picker>
         </div>
         <div>
-          <div class="delivery_type_title"><span class="stars_red">*</span>Delivery Time</div>
+          <div class="delivery_type_title">
+            <span class="stars_red">*</span>{{ t('destinationDelivery.deliveryTime') }}
+          </div>
           <el-time-select
             v-model="DeliveryTime"
             :disabled="isNotClickAddress"
@@ -986,64 +1001,64 @@ onMounted(() => {
             end="23:30"
             style="width: 240px"
             prefix-icon=""
-            placeholder="Please Select Time"
+            :placeholder="t('destinationDelivery.pleaseSelectTime')"
           ></el-time-select>
         </div>
         <div style="margin-left: 12px">
-          <div class="delivery_type_title">Delivery Reference</div>
+          <div class="delivery_type_title">{{ t('destinationDelivery.deliveryReference') }}</div>
           <el-tooltip
             class="item"
             effect="dark"
-            content="Reference to be quoted on arrival at the Warehouse/DC"
+            :content="t('destinationDelivery.deliveryReferenceTooltip')"
             placement="bottom"
           >
             <el-input v-model="DeliveryReference" class="delivery_reference"></el-input>
           </el-tooltip>
         </div>
       </div>
-      <div class="delivery_type_title">Special Requirements</div>
+      <div class="delivery_type_title">{{ t('destinationDelivery.specialRequirements') }}</div>
       <div class="flex" style="margin-top: 8px">
         <el-button
           :disabled="isNotClickAddress"
           class="el-button--grey"
-          @click="handleclickbutton('Tail Lift Required')"
-          >Tail Lift Required</el-button
+          @click="handleclickbutton(t('destinationDelivery.tailLiftRequired'))"
+          >{{ t('destinationDelivery.tailLiftRequired') }}</el-button
         >
         <el-button
           :disabled="isNotClickAddress"
           class="el-button--grey"
-          @click="handleclickbutton('Side Loading')"
-          >Side Loading</el-button
+          @click="handleclickbutton(t('destinationDelivery.sideLoading'))"
+          >{{ t('destinationDelivery.sideLoading') }}</el-button
         >
         <el-button
           :disabled="isNotClickAddress"
           class="el-button--grey"
-          @click="handleclickbutton('Forklift Required')"
-          >Forklift Required</el-button
+          @click="handleclickbutton(t('destinationDelivery.forkliftRequired'))"
+          >{{ t('destinationDelivery.forkliftRequired') }}</el-button
         >
         <el-button
           :disabled="isNotClickAddress"
           class="el-button--grey"
-          @click="handleclickbutton('Special Equipment')"
-          >Special Equipment</el-button
+          @click="handleclickbutton(t('destinationDelivery.specialEquipment'))"
+          >{{ t('destinationDelivery.specialEquipment') }}</el-button
         >
       </div>
       <el-input
         :disabled="isNotClickAddress"
         v-model="Requirements"
-        placeholder="Eenter any additional requirements or notes..."
+        :placeholder="t('destinationDelivery.additionalRequirementsPlaceholder')"
         type="textarea"
         style="margin-top: 8px"
         :rows="4"
       ></el-input>
       <div v-if="a != undefined" class="delivery_type_title" style="margin-top: 16px">
-        <span class="stars_red">*</span>Modification Reason
+        <span class="stars_red">*</span>{{ t('destinationDelivery.modificationReason') }}
       </div>
       <el-input
         v-if="a != undefined"
         :disabled="isNotClickAddress"
         v-model="Modification"
-        placeholder="Eenter any additional requirements or notes..."
+        :placeholder="t('destinationDelivery.additionalRequirementsPlaceholder')"
         type="textarea"
         style="margin-top: 8px"
         :rows="4"
@@ -1064,7 +1079,7 @@ onMounted(() => {
             class="el-button--main"
             @click="AddNewAddressDelivery"
           >
-            + Add New Address</el-button
+            + {{ t('destinationDelivery.addNewAddress') }}</el-button
           >
         </div>
         <el-radio-group v-model="Addressradio" style="max-height: 50vh; overflow: auto">
@@ -1116,23 +1131,23 @@ onMounted(() => {
       </div>
       <template #header>
         <div class="my-header">
-          <div class="header_Title">Manage Address</div>
+          <div class="header_Title">{{ t('destinationDelivery.manageAddress') }}</div>
           <el-button @click="handleClickAddNewAddress" class="el-button--noborder--configuration"
-            >+ Add New Address</el-button
+            >+ {{ t('destinationDelivery.addNewAddress') }}</el-button
           >
         </div>
       </template>
       <template #footer>
         <div class="dialog-footer">
-          <el-button class="el-button--default dialog-button" @click="ManageVisible = false"
-            >Cancel</el-button
-          >
+          <el-button class="el-button--default dialog-button" @click="ManageVisible = false">{{
+            t('common.cancel')
+          }}</el-button>
           <el-button
             :disabled="ManageAddressList.length == 0"
             class="el-button--dark dialog-button"
             @click="changeAddressRadio"
           >
-            OK
+            {{ t('common.ok') }}
           </el-button>
         </div>
       </template>
@@ -1140,21 +1155,23 @@ onMounted(() => {
     <el-dialog
       v-model="AddNewAddressVisible"
       width="640"
-      title="Add New Delivery Address"
+      :title="t('destinationDelivery.addNewDeliveryAddressTitle')"
       :show-close="false"
     >
-      <div class="diaolog_add_title"><span class="stars_red">*</span>Address</div>
+      <div class="diaolog_add_title">
+        <span class="stars_red">*</span>{{ t('destinationDelivery.address') }}
+      </div>
       <el-tooltip
         class="box-item"
         :visible="isShowLimitLine1"
         effect="dark"
-        content="Maximum character limit reached (45 characters)"
+        :content="t('destinationDelivery.maximumCharacterLimitReached')"
         placement="bottom"
       >
         <el-input
           @input="handleAddressLine1"
           class="inputmargin2"
-          placeholder="Line 1"
+          :placeholder="t('destinationDelivery.addressLine1Placeholder')"
           v-model="AddressLine1"
         >
           <template #suffix>
@@ -1168,13 +1185,13 @@ onMounted(() => {
         class="box-item"
         :visible="isShowLimitLine2"
         effect="dark"
-        content="Maximum character limit reached (45 characters)"
+        :content="t('destinationDelivery.maximumCharacterLimitReached')"
         placement="bottom"
       >
         <el-input
           @input="handleAddressLine2"
           class="inputmargin2"
-          placeholder="Line 2"
+          :placeholder="t('destinationDelivery.addressLine2Placeholder')"
           v-model="AddressLine2"
         >
           <template #suffix>
@@ -1188,13 +1205,13 @@ onMounted(() => {
         class="box-item"
         :visible="isShowLimitLine3"
         effect="dark"
-        content="Maximum character limit reached (45 characters)"
+        :content="t('destinationDelivery.maximumCharacterLimitReached')"
         placement="bottom"
       >
         <el-input
           @input="handleAddressLine3"
           class="inputmargin2"
-          placeholder="Line 3"
+          :placeholder="t('destinationDelivery.addressLine3Placeholder')"
           v-model="AddressLine3"
         >
           <template #suffix>
@@ -1208,14 +1225,14 @@ onMounted(() => {
         class="box-item"
         :visible="isShowLimitLine4"
         effect="dark"
-        content="Maximum character limit reached (45 characters)"
+        :content="t('destinationDelivery.maximumCharacterLimitReached')"
         placement="bottom"
       >
         <el-input
           style="margin-bottom: 16px"
           @input="handleAddressLine4"
           class="inputmargin2"
-          placeholder="Line 4"
+          :placeholder="t('destinationDelivery.addressLine4Placeholder')"
           v-model="AddressLine4"
         >
           <template #suffix>
@@ -1227,12 +1244,14 @@ onMounted(() => {
       </el-tooltip>
       <div class="flex">
         <div style="width: 33%">
-          <div class="diaolog_add_title"><span class="stars_red">*</span>Country Code</div>
+          <div class="diaolog_add_title">
+            <span class="stars_red">*</span>{{ t('destinationDelivery.countryCode') }}
+          </div>
           <el-select
             v-model="CountryCode"
             filterable
             remote
-            placeholder="Select Country"
+            :placeholder="t('destinationDelivery.selectCountry')"
             :remote-method="querySearchCountry"
             :loading="Countryloading"
             class="inputmargin"
@@ -1246,12 +1265,14 @@ onMounted(() => {
           </el-select>
         </div>
         <div style="margin: 0 9px; width: 33%">
-          <div class="diaolog_add_title"><span class="stars_red">*</span>City Code</div>
+          <div class="diaolog_add_title">
+            <span class="stars_red">*</span>{{ t('destinationDelivery.cityCode') }}
+          </div>
           <el-select
             v-model="CityCode"
             filterable
             remote
-            placeholder="Select City"
+            :placeholder="t('destinationDelivery.selectCity')"
             :remote-method="querySearchCity"
             :loading="cityloading"
             class="inputmargin"
@@ -1265,36 +1286,48 @@ onMounted(() => {
           </el-select>
         </div>
         <div style="width: 33%">
-          <div class="diaolog_add_title"><span class="stars_red">*</span>Postal Code</div>
+          <div class="diaolog_add_title">
+            <span class="stars_red">*</span>{{ t('destinationDelivery.postalCode') }}
+          </div>
           <el-input
             class="inputmargin"
-            placeholder="Enter postal code"
+            :placeholder="t('destinationDelivery.enterPostalCode')"
             v-model="PostalCode"
           ></el-input>
         </div>
       </div>
-      <div class="diaolog_add_title_bold">Contact Information</div>
+      <div class="diaolog_add_title_bold">{{ t('destinationDelivery.contactInformation') }}</div>
       <div class="flex">
         <div style="margin-right: 9px; width: 50%">
-          <div class="diaolog_add_title"><span class="stars_red">*</span>Contact Person</div>
-          <el-input style="margin-top: 4px" placeholder="Name" v-model="ContactPerson"></el-input>
+          <div class="diaolog_add_title">
+            <span class="stars_red">*</span>{{ t('destinationDelivery.contactPerson') }}
+          </div>
+          <el-input
+            style="margin-top: 4px"
+            :placeholder="t('destinationDelivery.contactNamePlaceholder')"
+            v-model="ContactPerson"
+          ></el-input>
         </div>
         <div style="width: 50%">
-          <div class="diaolog_add_title"><span class="stars_red">*</span>Contact Number</div>
+          <div class="diaolog_add_title">
+            <span class="stars_red">*</span>{{ t('destinationDelivery.contactNumber') }}
+          </div>
           <el-input
             style="margin-top: 4px"
-            placeholder="Mobile Numer"
+            :placeholder="t('destinationDelivery.contactMobilePlaceholder')"
             v-model="ContactNumber"
           ></el-input>
         </div>
       </div>
       <template #footer>
         <div class="dialog-footer" style="justify-content: end">
-          <el-button class="el-button--default dialog-button" @click="AddNewAddressVisible = false"
-            >Cancel</el-button
+          <el-button
+            class="el-button--default dialog-button"
+            @click="AddNewAddressVisible = false"
+            >{{ t('common.cancel') }}</el-button
           >
           <el-button class="el-button--dark dialog-button" @click="SaveNewAddress">
-            Save Address
+            {{ t('destinationDelivery.saveAddress') }}
           </el-button>
         </div>
       </template>
@@ -1309,10 +1342,9 @@ onMounted(() => {
     >
       <div class="flex_center">
         <img :src="NotAvailable" width="100" />
-        <div class="alert_title">Destination Delivery Service Not Available</div>
+        <div class="alert_title">{{ t('destinationDelivery.serviceNotAvailableTitle') }}</div>
         <div class="alert_text">
-          Destination delivery service is not yet available for your shipment destinations. To
-          request this service, please contact the destination office first.
+          {{ t('destinationDelivery.serviceNotAvailableText') }}
         </div>
       </div>
       <template #footer>
@@ -1321,7 +1353,7 @@ onMounted(() => {
             style="width: 80px"
             class="el-button--dark"
             @click="NoPermissionVisible = false"
-            >OK</el-button
+            >{{ t('common.ok') }}</el-button
           >
         </div>
       </template>
@@ -1336,16 +1368,18 @@ onMounted(() => {
     >
       <div class="flex_center">
         <img :src="NotShipment" width="100" />
-        <div class="alert_title">No Eligible Shipments for Booking</div>
+        <div class="alert_title">{{ t('destinationDelivery.noEligibleShipmentsTitle') }}</div>
         <div class="alert_text">
-          Your shipments are currently outside the booking window. Eligible shipments will appear
-          here when booking window opens.
+          {{ t('destinationDelivery.noEligibleShipmentsText') }}
         </div>
       </div>
       <template #footer>
         <div class="dialog-footer">
-          <el-button style="width: 80px" class="el-button--dark" @click="NoEligibleVisible = false"
-            >OK</el-button
+          <el-button
+            style="width: 80px"
+            class="el-button--dark"
+            @click="NoEligibleVisible = false"
+            >{{ t('common.ok') }}</el-button
           >
         </div>
       </template>
@@ -1353,12 +1387,14 @@ onMounted(() => {
     <!-- 保存成功 -->
     <el-dialog v-model="SaveedVisible" width="320" style="height: 212px">
       <div style="text-align: center"><el-image :src="submitsucessful" style="width: 64px" /></div>
-      <div style="text-align: center; margin-top: 20px">Saved successfully</div>
+      <div style="text-align: center; margin-top: 20px">
+        {{ t('destinationDelivery.savedSuccessfully') }}
+      </div>
     </el-dialog>
     <!-- 保存失败 -->
     <el-dialog v-model="UnableSaveVisible" width="480">
       <div>{{ missingmessage }}</div>
-      <div>Please complete all required fields.</div>
+      <div>{{ t('destinationDelivery.completeRequiredFields') }}</div>
       <template #footer>
         <div class="dialog-footer">
           <el-button
@@ -1366,45 +1402,47 @@ onMounted(() => {
             @click="UnableSaveVisible = false"
             style="width: 100px"
           >
-            OK
+            {{ t('common.ok') }}
           </el-button>
         </div>
       </template>
       <template #header>
         <div class="unable-save-header dialog-header">
           <span class="font_family icon-icon_fail_fill_b"></span>
-          Unable to Save
+          {{ t('destinationDelivery.unableToSave') }}
         </div>
       </template>
     </el-dialog>
     <!-- 取消保存 -->
     <el-dialog v-model="CancelRulesVisible" width="480">
-      <div style="font-weight: 400">You have unsaved changes.</div>
-      <div style="font-weight: 400">Are you sure you want to leave this page?</div>
+      <div style="font-weight: 400">{{ t('destinationDelivery.unsavedChangesMessageLine1') }}</div>
+      <div style="font-weight: 400">{{ t('destinationDelivery.unsavedChangesMessageLine2') }}</div>
       <template #footer>
         <div class="dialog-footer">
-          <el-button type="default" @click="CancelRulesVisible = false" style="width: 100px"
-            >Cancel</el-button
-          >
+          <el-button type="default" @click="CancelRulesVisible = false" style="width: 100px">{{
+            t('common.cancel')
+          }}</el-button>
           <el-button class="el-button--warning" @click="router.back()" style="width: 100px">
-            OK
+            {{ t('common.ok') }}
           </el-button>
         </div>
       </template>
       <template #header>
         <div class="warning-header dialog-header">
           <span class="font_family icon-icon_fail_fill_b"></span>
-          Unsaved Changes
+          {{ t('destinationDelivery.unsavedChangesTitle') }}
         </div>
       </template>
     </el-dialog>
     <!-- 當選擇非建議日期内的date時給出的提示 -->
     <el-dialog v-model="isRecommendDate" width="480">
       <div>{{ recommendateWarning }}</div>
-      <div style="margin-top: 4px">Additional storage fees may apply.</div>
+      <div style="margin-top: 4px">
+        {{ t('destinationDelivery.additionalStorageFeesMayApply') }}
+      </div>
       <div style="margin-top: 8px">
         <div class="Notice" v-for="(item, index) in RecommendateList" :key="index">
-          HOBL: {{ item.Hbol }}
+          {{ t('destinationDelivery.hobl') }}: {{ item.Hbol }}
         </div>
       </div>
       <template #footer>
@@ -1414,14 +1452,14 @@ onMounted(() => {
             @click="isRecommendDate = false"
             style="width: 100px"
           >
-            OK
+            {{ t('common.ok') }}
           </el-button>
         </div>
       </template>
       <template #header>
         <div class="warning-header dialog-header">
           <span class="font_family icon-icon_warning_fill_b"></span>
-          Additional Fees Notice
+          {{ t('destinationDelivery.additionalFeesNotice') }}
         </div>
       </template>
     </el-dialog>

+ 13 - 11
src/views/DestinationDelivery/src/components/CreateNewBooking/src/components/NewbookingTable.vue

@@ -5,7 +5,9 @@ import { formatTimezone } from '@/utils/tools'
 import { ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { autoWidth } from '@/utils/table'
+import { useI18n } from 'vue-i18n'
 
+const { t } = useI18n()
 const router = useRouter()
 const { currentRoute } = router
 const { value } = currentRoute
@@ -39,7 +41,7 @@ const tableRef = ref<VxeGridInstance | null>(null)
 const handleColumns = (columns: any) => {
   const newColumns = columns.map((item: any) => {
     let curColumn: any = {
-      title: item.title,
+      title: t(item.cleaned_field_name),
       field: item.field,
       width: '150px'
     }
@@ -150,7 +152,7 @@ const selectRowsWithSameMbol = ({ row, checked }) => {
   const key = row.same_mbol
   if (!key) return
   const tableRowData = tableRef.value?.getTableData().fullData || []
-  tableRowData.forEach((item, index) => {
+  tableRowData.forEach((item) => {
     if (item.same_mbol === key) {
       tableRef.value?.setCheckboxRow(item, checked)
     }
@@ -290,11 +292,7 @@ const handleCustomizeColumns = () => {
       model_name: 'destination_delivery_shipment_search'
     }
   }
-  CustomizeColumnsRef.value.openDialog(
-    params,
-    -220,
-    'Drag item over to this selection or click "add" icon to show the field on delivery booking list'
-  )
+  CustomizeColumnsRef.value.openDialog(params, -220, t('destinationDelivery.customizeColumnsTip'))
 }
 // 定制表格
 const customizeColumns = async () => {
@@ -328,14 +326,16 @@ defineExpose({
 <template>
   <div class="new-booking-table">
     <div class="table-tools">
-      <div class="table-total-info">{{ tableData.data.length }} result</div>
+      <div class="table-total-info">
+        {{ tableData.data.length }} {{ t('destinationDelivery.result') }}
+      </div>
       <el-button
         style="width: 170px; align-self: flex-end"
         type="default"
         @click="handleCustomizeColumns"
       >
         <span style="margin-right: 6px" class="font_family icon-icon_column_b"></span>
-        Customize Columns
+        {{ t('destinationDelivery.customizeColumns') }}
       </el-button>
     </div>
     <vxe-grid
@@ -358,9 +358,11 @@ defineExpose({
       </template>
       <template #empty>
         <div v-if="isNotActivated" class="empty-text">
-          This service isn't activated yet. Please contact our team to enable it.
+          {{ t('destinationDelivery.serviceNotActivatedYet') }}
+        </div>
+        <div v-else class="empty-text">
+          {{ t('destinationDelivery.noEligibleShipmentsFoundToCreateNewBooking') }}
         </div>
-        <div v-else class="empty-text">No eligible shipments found to create a new booking.</div>
       </template>
     </vxe-grid>
 

+ 15 - 7
src/views/DestinationDelivery/src/components/DeliveryDate.vue

@@ -2,7 +2,9 @@
 import dayjs from 'dayjs'
 import { ref, watch } from 'vue'
 import { useUserStore } from '@/stores/modules/user'
+import { useI18n } from 'vue-i18n'
 
+const { t } = useI18n()
 const userStore = useUserStore()
 const valueFormatDate = 'MM/DD/YYYY'
 // type RangeValue = [Dayjs, Dayjs]
@@ -137,7 +139,7 @@ const handlePanelChange = (value: any, mode: any) => {
       :open="open"
       :disabled="Disabled"
       @change="changeRangeData"
-      :placeholder="['Start Time', 'End Time']"
+      :placeholder="[t('dateRange.startTime'), t('dateRange.endTime')]"
       :format="userStore.dateFormat"
       valueFormat="MM/DD/YYYY"
       @openChange="handleCalendarOpen(ETDDate)"
@@ -173,14 +175,20 @@ const handlePanelChange = (value: any, mode: any) => {
       <template #renderExtraFooter v-if="isShowExtra">
         <div class="calender_flex">
           <div class="footer_left">
-            <el-button class="el-button--noborder" @click="Earliest">Earliest Time</el-button>
-            <el-button class="el-button--noborder" @click="ChangeToday('Earliest')"
-              >Today</el-button
-            >
+            <el-button class="el-button--noborder" @click="Earliest">{{
+              t('dateRange.earliestTime')
+            }}</el-button>
+            <el-button class="el-button--noborder" @click="ChangeToday('Earliest')">{{
+              t('dateRange.today')
+            }}</el-button>
           </div>
           <div class="footer_left footer_right">
-            <el-button @click="Latest" class="el-button--noborder">Latest Time</el-button>
-            <el-button class="el-button--noborder" @click="ChangeToday('Latest')">Today</el-button>
+            <el-button @click="Latest" class="el-button--noborder">{{
+              t('dateRange.latestTime')
+            }}</el-button>
+            <el-button class="el-button--noborder" @click="ChangeToday('Latest')">{{
+              t('dateRange.today')
+            }}</el-button>
           </div>
         </div>
       </template>

+ 26 - 17
src/views/DestinationDelivery/src/components/ListView.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { useCalculatingHeight } from '@/hooks/calculatingHeight'
 import TableView from './TableView'
 import dayjs from 'dayjs'
@@ -22,48 +25,53 @@ const isEmployeeRole = computed(() => {
 
 const modeList = [
   {
-    label: 'Truck',
+    label: t('destinationDelivery.truck'),
     value: 'Truck'
   },
   {
-    label: 'Rail',
+    label: t('destinationDelivery.rail'),
     value: 'Rail'
   }
 ]
 
 const numberCards = ref([
   {
-    label: 'Total Bookings',
+    label: t('destinationDelivery.totalBookings'),
     key: 'ALL',
     value: 0,
     color: '#2b2f36'
   },
   {
-    label: 'Pending Approval',
+    label: t('destinationDelivery.Pending Approval'),
+    key: 'Pending Approval',
     value: 0,
     color: '#edb82f',
     icon: 'icon_time_b'
   },
   {
-    label: 'Approved',
+    label: t('destinationDelivery.Approved'),
+    key: 'Approved',
     value: 0,
     color: '#00a870',
     icon: 'icon_confirm_b'
   },
   {
-    label: 'Rejected',
+    label: t('destinationDelivery.Rejected'),
+    key: 'Rejected',
     value: 0,
     color: '#c9353f',
     icon: 'icon_reject_b'
   },
   {
-    label: 'Cancelled',
+    label: t('destinationDelivery.Cancelled'),
+    key: 'Cancelled',
     value: 0,
     color: '#243041',
     icon: 'icon_cancelled_b'
   }
 ])
 const clickCard = (filterTagItem: string) => {
+  console.log(filterTagItem, 'tiem')
   queryData.value.filterTag.pop()
   queryData.value.filterTag.push(filterTagItem)
   handleSearch()
@@ -110,12 +118,12 @@ defineExpose({
   <div class="display">
     <div class="header_top">
       <div class="date-tips_filter filter-item">
-        <span class="label">Delivery Date:</span>
+        <span class="label">{{ t('destinationDelivery.deliveryDate') }}:</span>
         <DeliveryDate @DateChange="deliveryDataChange" :Date="DateStart" />
       </div>
 
       <div class="tips_filter filter-item">
-        <span class="label">Delivery Mode:</span>
+        <span class="label">{{ t('destinationDelivery.deliveryMode') }}:</span>
         <el-select v-model="queryData.delivery_mode" placeholder="" clearable>
           <el-option
             v-for="item in modeList"
@@ -126,12 +134,12 @@ defineExpose({
         </el-select>
       </div>
       <div class="date-tips_filter filter-item">
-        <span class="label">Creation Date</span>
+        <span class="label">{{ t('destinationDelivery.creationDate') }}</span>
         <CalendarDate :isShowPopupClass="true" @DateChange="DateChange"></CalendarDate>
       </div>
       <div class="input-tips_filter filter-item">
         <el-input
-          placeholder="Search Question Booking No、HBOL No、MBL No、Container No、Consignee"
+          :placeholder="t('destinationDelivery.searchQuestionPlaceholder')"
           v-model="queryData.text_search"
           class="log_input"
         >
@@ -144,17 +152,15 @@ defineExpose({
           </template>
         </el-input>
       </div>
-      <el-button class="el-button--dark" @click="handleSearch">Search</el-button>
+      <el-button class="el-button--dark" @click="handleSearch">{{ t('common.search') }}</el-button>
     </div>
     <div class="number-cards">
       <div
         class="card"
         :class="{
-          'is-active':
-            queryData.filterTag.includes(item.key as string | undefined) ||
-            queryData.filterTag.includes(item.label)
+          'is-active': queryData.filterTag.includes(item.key as string | undefined)
         }"
-        @click="clickCard((item.key || item.label) as string)"
+        @click="clickCard(item.key)"
         v-for="(item, index) in numberCards"
         :key="index"
       >
@@ -162,7 +168,10 @@ defineExpose({
         <div
           class="card-value"
           :style="{
-            color: item.label === 'Cancelled' ? `var(--color-card-number-cancelled)` : item.color
+            color:
+              item.label === t('destinationDelivery.Cancelled')
+                ? `var(--color-card-number-cancelled)`
+                : item.color
           }"
         >
           {{ item.value }}

+ 23 - 16
src/views/DestinationDelivery/src/components/TableView/src/TableView.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import { ref, nextTick, onMounted } from 'vue'
 import { type VxeGridInstance, type VxeGridProps } from 'vxe-table'
 import { autoWidth } from '@/utils/table'
@@ -29,7 +32,7 @@ const tableOriginColumnsField = ref()
 const handleColumns = (columns: any, status?: string) => {
   const newColumns = columns.map((item: any) => {
     let curColumn: any = {
-      title: item.title,
+      title: t(item.cleaned_field_name),
       field: item.field,
       sortable: true,
       minWidth: 120
@@ -124,31 +127,35 @@ const emit = defineEmits(['getNumberCards'])
 const rtnNumberCards = (data) => {
   const cards = [
     {
-      label: 'Total Bookings',
+      label: t('destinationDelivery.totalBookings'),
       value: data.All || 0,
       color: '#2b2f36',
       key: 'ALL'
     },
     {
-      label: 'Pending Approval',
+      label: t("destinationDelivery['Pending Approval']"),
+      key: 'Pending Approval',
       value: data.pending_approval_rc || 0,
       color: '#edb82f',
       icon: 'icon_time_b'
     },
     {
-      label: 'Approved',
+      label: t('destinationDelivery.Approved'),
+      key: 'Approved',
       value: data.approved_rc || 0,
       color: '#00a870',
       icon: 'icon_confirm_b'
     },
     {
-      label: 'Rejected',
+      label: t('destinationDelivery.Rejected'),
+      key: 'Rejected',
       value: data.rejected_rc || 0,
       color: '#c9353f',
       icon: 'icon_reject_b'
     },
     {
-      label: 'Cancelled',
+      label: t('destinationDelivery.Cancelled'),
+      key: 'Cancelled',
       value: data.cancelled_rc || 0,
       color: '#243041',
       icon: 'icon_cancelled_b'
@@ -347,12 +354,12 @@ defineExpose({
   <div
     style="padding: 0px 20px"
     class="table-box"
-    element-loading-text="Loading..."
+    :element-loading-text="t('common.loading')"
     element-loading-custom-class="element-loading"
     element-loading-background="rgb(43, 47, 54, 0.7)"
   >
     <div class="table-tools">
-      <div class="left-total-records">Booking List</div>
+      <div class="left-total-records">{{ t('destinationDelivery.bookingList') }}</div>
     </div>
     <vxe-grid
       ref="tableRef"
@@ -365,9 +372,9 @@ defineExpose({
       <template #empty v-if="!tableLoadingTableData && tableData.data.length === 0">
         <div class="empty-box">
           <img class="empty-img" src="./img/table-empty-img.png" alt="" />
-          <p>You haven't created any destination delivery bookings yet.</p>
-          <p>Book truck or rail delivery for your shipments to save time and</p>
-          <p>ensure smooth last-mile delivery.</p>
+          <p>{{ t('destinationDelivery.emptyBookingTitle') }}</p>
+          <p>{{ t('destinationDelivery.emptyBookingLine1') }}</p>
+          <p>{{ t('destinationDelivery.emptyBookingLine2') }}</p>
           <el-button
             style="height: 40px"
             v-if="isEmployeeRole === false"
@@ -375,7 +382,7 @@ defineExpose({
             @click="handleCreate"
           >
             <span style="margin-right: 4px" class="font_family icon-icon_add_b"></span>
-            <span style="font-weight: 400">Create New Booking</span>
+            <span style="font-weight: 400">{{ t('destinationDelivery.createNewBooking') }}</span>
           </el-button>
         </div>
       </template>
@@ -435,7 +442,7 @@ defineExpose({
           v-if="
             isEmployeeRole &&
             row.status === 'Pending Approval' &&
-            row.kln_pic.includes(userStore.userInfo.employee_email)
+            row.kln_pic.includes(userStore.userInfo.employee_email?.toUpperCase())
           "
           style="height: 24px; width: 24px"
         >
@@ -446,7 +453,7 @@ defineExpose({
           v-if="
             isEmployeeRole &&
             row.status === 'Pending Approval' &&
-            row.kln_pic.includes(userStore.userInfo.employee_email)
+            row.kln_pic.includes(userStore.userInfo.employee_email?.toUpperCase())
           "
           @click="handleTips('reject', row)"
           class="action-btn el-button--blue"
@@ -457,7 +464,7 @@ defineExpose({
       </template>
       <!-- Status字段的插槽 -->
       <template #status="{ row, column }">
-        <VTag :type="row[column.field]">{{ row[column.field] }}</VTag>
+        <VTag :type="row[column.field]">{{ t(`destinationDelivery.${row[column.field]}`) }}</VTag>
       </template>
       <!-- Booking No字段的插槽 -->
       <template #multLink="{ row, column }">
@@ -466,7 +473,7 @@ defineExpose({
           v-for="(item, index) in row[column.field]"
           :key="item.key"
         >
-          <span v-if="index > 0">、</span>
+          <span v-if="(index as number) > 0">、</span>
           <span
             style="color: var(--color-theme); cursor: pointer"
             @click="handleMultLinkClick(item)"

+ 18 - 17
src/views/DestinationDelivery/src/components/TableView/src/components/BookingDetailDialog.vue

@@ -1,9 +1,11 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
 import DetailStep from './DetailStep.vue'
 import ShipmentInforTable from './ShipmentInforTable.vue'
 import OperationLogProcess from './OperationLogProcess.vue'
 import { formatTimezone } from '@/utils/tools'
 
+const { t } = useI18n()
 const visible = ref(false)
 const loading = ref(false)
 
@@ -35,49 +37,48 @@ const stepList: any = ref([])
 
 const handleStepData = (status, data) => {
   stepList.value.push({
-    label: 'Created',
+    label: t('destinationDelivery.created'),
     date: data.created_time || '--',
     icon: 'icon_submit_b'
   })
   if (status === 'Modified') {
     stepList.value.push({
-      label: 'Modified',
+      label: t('destinationDelivery.modified'),
       date: data.update_time || '--',
       icon: 'icon_edit_b'
     })
   }
   if (status === 'Pending Approval' || status === 'Modified') {
     stepList.value.push({
-      label: 'Pending',
-      date: 'Current',
+      label: t('destinationDelivery.pending'),
+      date: t('destinationDelivery.current'),
       icon: 'icon_time_b',
       status: 'current'
     })
     stepList.value.push({
-      label: 'Approved',
+      label: t('destinationDelivery.Approved'),
       date: '--',
       icon: 'icon_confirm_b',
       status: 'unfinished'
     })
   } else if (status === 'Approved') {
     stepList.value.push({
-      label: 'Approved',
+      label: t('destinationDelivery.Approved'),
       date: data.update_time || '--',
       icon: 'icon_confirm_b',
       status: ''
     })
   } else if (status === 'Rejected') {
     stepList.value.push({
-      label: 'Rejected',
+      label: t('destinationDelivery.Rejected'),
       date: data.update_time || '--',
       icon: 'icon_reject_b',
       status: 'rejected',
-      description:
-        'This is some description information about the pending task, if there is too much content.'
+      description: t('destinationDelivery.pendingTaskTip')
     })
   } else if (status === 'Cancelled') {
     stepList.value.push({
-      label: 'Cancelled',
+      label: t('destinationDelivery.Cancelled'),
       date: data.update_time || '--',
       icon: 'icon_cancelled_b',
       status: 'cancelled'
@@ -97,7 +98,7 @@ defineExpose({
 
 <template>
   <el-dialog
-    title="Booking Detail"
+    :title="t('destinationDelivery.bookingDetail')"
     class="booking-detail-dialog"
     v-model="visible"
     :close-on-click-modal="false"
@@ -110,35 +111,35 @@ defineExpose({
       <DetailStep :stepList="stepList" />
       <div class="booking-info">
         <div class="booking-no">
-          <span class="no">Booking No.{{ rowData?.booking_no }}</span>
+          <span class="no">{{ t('booking.bookingNo') }}{{ rowData?.booking_no }}</span>
           <v-tag class="tag" :type="rowData?.status">{{ rowData?.status }}</v-tag>
         </div>
         <div class="created-time">{{ formatTimezone(rowData?.created_time) }}</div>
       </div>
       <ShipmentInforTable :data="shipmentInfoTableData" />
       <div class="delivery-information">
-        <div class="label">Delivery Information</div>
+        <div class="label">{{ t('destinationDelivery.deliveryInformation') }}</div>
         <div class="delivery-info">
           <div class="delivery-item inline-flex" style="width: 200px">
-            <span class="item-label">Mode Type</span>
+            <span class="item-label">{{ t('destinationDelivery.modeType') }}</span>
             <span class="item-value">{{ rowData?.delivery_mode || '--' }}</span>
           </div>
           <div class="delivery-item inline-flex">
-            <span class="item-label">Delivery Date</span>
+            <span class="item-label">{{ t('destinationDelivery.deliveryDate') }}</span>
             <span class="item-value">
               <span class="font_family icon-icon_date_b" style="margin-right: 4px"></span>
               <span style="margin-top: 1px">{{ formatTimezone(rowData?.delivery_date) }}</span>
             </span>
           </div>
           <div class="delivery-item">
-            <span class="item-label">Delivery Address</span>
+            <span class="item-label">{{ t('destinationDelivery.deliveryAddress') }}</span>
             <span class="item-value">
               <span class="font_family icon-icon_location_b" style="margin-right: 2px"></span>
               <span style="margin-top: 1px">{{ rowData?.delivery_address || '--' }}</span>
             </span>
           </div>
           <div class="delivery-item">
-            <span class="item-label">Special Requirements</span>
+            <span class="item-label">{{ t('destinationDelivery.specialRequirements') }}</span>
             <span class="item-value">
               <span class="font_family icon-icon_paragraph_b" style="margin-right: 2px"></span>
               <span style="margin-top: 1px">{{ rowData?.special_requirements || '--' }}</span>

+ 12 - 9
src/views/DestinationDelivery/src/components/TableView/src/components/DownloadDialog.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 const dialogVisible = ref(false)
 
 const openDialog = (selectedColumns: string[], slectedDataNumber: number) => {
@@ -32,11 +35,11 @@ defineExpose({
 
 <template>
   <div>
-    <el-dialog @close="clearData" v-model="dialogVisible" title="Download File" width="540">
+    <el-dialog @close="clearData" v-model="dialogVisible" :title="t('common.downloadFile')" width="540">
       <div class="download-dialog">
         <div class="select-data">
           <div style="display: inline-block">
-            Select data on your Opeartion Log list:<span style="color: var(--color-theme)">{{
+            {{ t('destinationDelivery.selectDataOnOperationLogList') }}<span style="color: var(--color-theme)">{{
               selectedDataNumber
             }}</span>
           </div>
@@ -44,7 +47,7 @@ defineExpose({
         <div class="download-filter">
           <el-radio-group v-model="downloadFilter">
             <el-radio :value="1"
-              >Download with selected columns
+              >{{ t('destinationDelivery.downloadWithSelectedColumns') }}
               <span class="column-number">{{ columns.length }}</span>
               <SeeAllIcon v-model="isShowSelectColumn" />
             </el-radio>
@@ -53,23 +56,23 @@ defineExpose({
               class="select-columns"
               :class="{ show: isShowSelectColumn }"
             >
-              <div class="title">Selected columns</div>
+              <div class="title">{{ t('common.selectedColumns') }}</div>
               <div class="content">
                 <div class="column-item" v-for="item in columns" :key="item">{{ item }}</div>
               </div>
             </div>
-            <el-radio :value="2">Download with all columns</el-radio>
+            <el-radio :value="2">{{ t('common.downloadWithAllColumns') }}</el-radio>
           </el-radio-group>
         </div>
       </div>
       <template #footer>
         <div class="dialog-footer">
-          <el-button class="cancel-btn" type="default" @click="dialogVisible = false"
-            >Cancel</el-button
-          >
+          <el-button class="cancel-btn" type="default" @click="dialogVisible = false">
+            {{ t('common.cancel') }}
+          </el-button>
           <el-button class="download-btn el-button--dark" @click="handleDownload"
             ><span style="margin-right: 8px" class="font_family icon-icon_download_b"></span>
-            Download</el-button
+            {{ t('tracking.download') }}</el-button
           >
         </div>
       </template>

+ 11 - 8
src/views/DestinationDelivery/src/components/TableView/src/components/EmailDialog.vue

@@ -1,4 +1,7 @@
 <script setup lang="ts">
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
 import '@wangeditor/editor/dist/css/style.css'
 import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
 import { i18nChangeLanguage, DomEditor } from '@wangeditor/editor'
@@ -152,7 +155,7 @@ const sendEmail = () => {
   const html = editorRef.value.getHtml()
   const text = editorRef.value.getText()
   if (!text) {
-    ElMessage.warning('Please enter the email content')
+    ElMessage.warning(t('destinationDelivery.pleaseEnterEmailContent'))
     return
   }
   $api
@@ -166,12 +169,12 @@ const sendEmail = () => {
     })
     .then((res: any) => {
       if (res.code === 200) {
-        ElMessage.success('Email sent successfully')
+        ElMessage.success(t('destinationDelivery.emailSentSuccessfully'))
         emailRecords.value = res.data.emailRecords
       }
     })
     .catch(() => {
-      ElMessage.error('Failed to send email')
+      ElMessage.error(t('destinationDelivery.failedToSendEmail'))
     })
 }
 
@@ -191,7 +194,7 @@ defineExpose({
 
 <template>
   <el-dialog
-    title="Communication"
+    :title="t('destinationDelivery.communication')"
     class="delivery-email-dialog"
     v-model="visible"
     :close-on-click-modal="false"
@@ -203,7 +206,7 @@ defineExpose({
     <div class="email-view">
       <div class="email-path">
         <span class="font_family icon-icon_email_b" style="font-size: 18px"></span>
-        <span class="label">Communicate with us:&nbsp;</span>
+        <span class="label">{{ t('destinationDelivery.communicateWithUs') }}:&nbsp;</span>
         <span class="content">{{ emailData.email }}</span>
       </div>
       <div class="separated-by">
@@ -221,7 +224,7 @@ defineExpose({
               <el-tooltip
                 class="box-item"
                 effect="dark"
-                content="Separated by;"
+                :content="t('destinationDelivery.separatedBySemicolon')"
                 placement="top-start"
                 :offset="-8"
               >
@@ -251,8 +254,8 @@ defineExpose({
           @click="sendEmail"
           class="el-button--dark"
           style="float: right; margin: 8px 0 14px 0; height: 40px"
-          ><span class="font_family icon-icon_submit_b" style="margin-right: 4px"></span> Send
-          Email</el-button
+          ><span class="font_family icon-icon_submit_b" style="margin-right: 4px"></span>
+          {{ t('destinationDelivery.sendEmail') }}</el-button
         >
       </div>
     </div>

+ 4 - 1
src/views/DestinationDelivery/src/components/TableView/src/components/OperationLogProcess.vue

@@ -1,5 +1,8 @@
 <script setup lang="ts">
 import { formatTimezone } from '@/utils/tools'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
+
 const props = withDefaults(
   defineProps<{
     processList: Array<any>
@@ -28,7 +31,7 @@ const props = withDefaults(
 
 <template>
   <div class="operation-log">
-    <div class="label">Operation Log</div>
+    <div class="label">{{ t('destinationDelivery.operationLogTitle') }}</div>
     <div class="process">
       <!-- <div class="left-line"></div> -->
       <div class="right-process-box">

Некоторые файлы не были показаны из-за большого количества измененных файлов