feat: 新增多语言文案并修复动态国际化调用问题
1. 新增多语言文件覆盖组件、页面、公共模块的翻译文案 2. 修复多处动态拼接lang key的调用,增加兜底逻辑避免空值或原始key暴露 3. 补全DIY组件、业务页面的国际化适配 4. 新增P2动态key国际化处理方案文档
This commit is contained in:
@@ -33,8 +33,8 @@ export default {
|
||||
} else {
|
||||
fallbackFunc && fallbackFunc(res);
|
||||
this.$util.showToast({
|
||||
title: res.message ? res.message : '小程序配置错误'
|
||||
});
|
||||
title: res.message ? res.message : this.$lang('common.auth.miniProgramConfigError')
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -74,8 +74,8 @@ export default {
|
||||
} else {
|
||||
fallbackFunc && fallbackFunc(res);
|
||||
this.$util.showToast({
|
||||
title: res.message ? res.message : '小程序配置错误'
|
||||
});
|
||||
title: res.message ? res.message : this.$lang('common.auth.miniProgramConfigError')
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -141,7 +141,7 @@ export default {
|
||||
});
|
||||
this.goodsSkuDetail.swiperHeight = maxHeight + 'px';
|
||||
|
||||
this.goodsSkuDetail.unit = this.goodsSkuDetail.unit || "件";
|
||||
this.goodsSkuDetail.unit = this.goodsSkuDetail.unit || this.$lang('unit.piece');
|
||||
|
||||
// 当前商品SKU规格
|
||||
if (this.goodsSkuDetail.sku_spec_format) this.goodsSkuDetail.sku_spec_format = JSON.parse(this.goodsSkuDetail.sku_spec_format);
|
||||
@@ -199,7 +199,7 @@ export default {
|
||||
if (this.$refs.goodsDetailView) {
|
||||
|
||||
// 初始化商品详情视图数据
|
||||
this.goodsSkuDetail.unit = this.goodsSkuDetail.unit || "件";
|
||||
this.goodsSkuDetail.unit = this.goodsSkuDetail.unit || this.$lang('unit.piece');
|
||||
|
||||
// 解决轮播图数量不一致时,切换到第一个
|
||||
if (this.swiperCurrent > this.goodsSkuDetail.sku_images.length) {
|
||||
|
||||
@@ -42,6 +42,12 @@ const SHARED_LANG_PACKS = [
|
||||
'components/register-reward',
|
||||
'components/wxwork-contact',
|
||||
'components/yuyue-date',
|
||||
'components/chat-message',
|
||||
'components/pick-regions',
|
||||
'components/ns-goods-sku',
|
||||
'components/ns-chat-receiveGoods',
|
||||
'components/ns-chat-goods',
|
||||
'components/ns-video-player-popup',
|
||||
'components-diy/diy-article',
|
||||
'components-diy/diy-bargain',
|
||||
'components-diy/diy-category',
|
||||
@@ -58,6 +64,16 @@ const SHARED_LANG_PACKS = [
|
||||
'components-diy/diy-seckill',
|
||||
'components-diy/diy-store',
|
||||
'components-diy/diy-video',
|
||||
'components-diy/diy-search',
|
||||
'components-diy/diy-map',
|
||||
'components-diy/diy-kefu',
|
||||
'components-diy/diy-channel-video',
|
||||
'components-diy/diy-live',
|
||||
'components-diy/diy-listmenu',
|
||||
'components-diy/diy-text',
|
||||
'components-diy/diy-goods-recommend',
|
||||
'components-diy/diy-notes',
|
||||
'member/public',
|
||||
];
|
||||
|
||||
function toCamelCaseName(name) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import util from '@/common/js/util.js'
|
||||
import TransformCoordinate from './transformCoordinate.js'
|
||||
import Lang from '@/common/js/lang.js'
|
||||
|
||||
function openMapByDefault(latitude, longitude, name) {
|
||||
uni.openLocation({
|
||||
@@ -8,7 +9,7 @@ function openMapByDefault(latitude, longitude, name) {
|
||||
name: name,
|
||||
fail: (e) => {
|
||||
uni.showModal({
|
||||
content: '打开地图失败,请稍后重试'
|
||||
content: Lang.lang('common.map.openMapFailed')
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<view class="video-title" :style="[{ '--title-line-clamp': titleLineClamp }]">{{ value.videoTitle }}
|
||||
</view>
|
||||
<!-- 视频统计信息 -->
|
||||
<view class="video-stats" v-if="showViewCount && value.showViewCount">{{ value.viewCount }}次观看</view>
|
||||
<view class="video-stats" v-if="showViewCount && value.showViewCount">{{ $lang('components-diy.diyChannelVideo.viewCountSuffix', { count: value.viewCount }) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<view class="play-icon-css"></view>
|
||||
</view>
|
||||
<view class="video-stats-overlay" v-if="showViewCount && value.showViewCount">
|
||||
{{ value.viewCount }}次观看
|
||||
{{ $lang('components-diy.diyChannelVideo.viewCountSuffix', { count: value.viewCount }) }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="video-info">
|
||||
@@ -37,7 +37,7 @@
|
||||
<view class="video-title" :style="[{ '--title-line-clamp': titleLineClamp }]">{{ value.videoTitle }}
|
||||
</view>
|
||||
<!-- 视频统计信息,非列表模式下显示 -->
|
||||
<view class="video-stats" v-if="showViewCount && value.showViewCount && !listMode">{{ value.viewCount }}次观看</view>
|
||||
<view class="video-stats" v-if="showViewCount && value.showViewCount && !listMode">{{ $lang('components-diy.diyChannelVideo.viewCountSuffix', { count: value.viewCount }) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -50,19 +50,19 @@
|
||||
<view v-if="goodsValue.priceStyle.lineControl && showMarketPrice(dataItem)"
|
||||
class="delete-price price-font"
|
||||
:style="{ color: goodsValue.theme == 'diy' ? goodsValue.priceStyle.lineColor : '' }">
|
||||
¥{{ showMarketPrice(dataItem) }}</view>
|
||||
<view class="sale" v-if="goodsValue.saleStyle.control"
|
||||
:style="{ color: goodsValue.theme == 'diy' ? goodsValue.saleStyle.color : '' }">
|
||||
售{{ dataItem.sale_num }}{{ dataItem.unit ? dataItem.unit : '件' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</x-skeleton>
|
||||
¥{{ showMarketPrice(dataItem) }}</view>
|
||||
<view class="sale" v-if="goodsValue.saleStyle.control"
|
||||
:style="{ color: goodsValue.theme == 'diy' ? goodsValue.saleStyle.color : '' }">
|
||||
{{ $lang('components-diy.diyGoodsRecommend.soldPrefix') }}{{ dataItem.sale_num }}{{ dataItem.unit ? dataItem.unit : $lang('unit.piece') }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</x-skeleton>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</view>
|
||||
<view class="fui-remark jump" style="padding-right: 20rpx; text-align: center; line-height: 140rpx;">
|
||||
<span style="font-size:24rpx;padding: 14rpx 18rpx;border-radius:8rpx"
|
||||
:style="{ background: item.BtBgColor, color: item.BtColor }" @tap.stop="previewSqs()">立即添加</span>
|
||||
:style="{ background: item.BtBgColor, color: item.BtColor }" @tap.stop="previewSqs()">{{ $lang('components-diy.diyKefu.addNow') }}</span>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
style="border-radius:6rpx;width: 60rpx;"></image>
|
||||
</view>
|
||||
<view class="fui-cell-text" style="color:#333;">{{ item.title }}</view>
|
||||
<view class="fui-cell-remark" style="font-size: 24rpx;">{{ lang == 'en-us' ? 'view' : '查看' }}</view>
|
||||
<view class="fui-cell-remark" style="font-size: 24rpx;">{{ $lang('components-diy.diyListmenu.view') }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<ns-login ref="login"></ns-login>
|
||||
@@ -33,7 +33,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
lang: uni.getStorageSync("lang")//en-us 英文
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
||||
@@ -21,17 +21,17 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="room-info" v-if="value.isShowAnchorInfo || value.isShowLiveGood">
|
||||
<block v-if="value.isShowAnchorInfo">
|
||||
<image
|
||||
:src="liveInfo.anchor_img != '' ? $util.img(liveInfo.anchor_img) : $util.getDefaultImage().head"
|
||||
class="anchor-img" @error="liveInfo.anchor_img = $util.getDefaultImage().head" show-menu-by-longpress/>
|
||||
<text class="anchor-name">主播:{{ liveInfo.anchor_name }}</text>
|
||||
</block>
|
||||
<text class="separate" v-if="value.isShowAnchorInfo && value.isShowLiveGood">|</text>
|
||||
<block v-if="value.isShowLiveGood">
|
||||
<text class="goods-text">直播商品:{{ liveInfo.goods.length }}</text>
|
||||
</block>
|
||||
<view class="room-info" v-if="value.isShowAnchorInfo || value.isShowLiveGood">
|
||||
<block v-if="value.isShowAnchorInfo">
|
||||
<image
|
||||
:src="liveInfo.anchor_img != '' ? $util.img(liveInfo.anchor_img) : $util.getDefaultImage().head"
|
||||
class="anchor-img" @error="liveInfo.anchor_img = $util.getDefaultImage().head" show-menu-by-longpress/>
|
||||
<text class="anchor-name">{{ $lang('components-diy.diyLive.anchorPrefix') }}{{ liveInfo.anchor_name }}</text>
|
||||
</block>
|
||||
<text class="separate" v-if="value.isShowAnchorInfo && value.isShowLiveGood">|</text>
|
||||
<block v-if="value.isShowLiveGood">
|
||||
<text class="goods-text">{{ $lang('components-diy.diyLive.goodsPrefix') }}{{ liveInfo.goods.length }}</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</x-skeleton>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div
|
||||
style="position:absolute;right:12rpx;bottom:48rpx;z-index:1;background:#4390FF;padding:0rpx 20rpx;border-radius:8rpx;color: #fff;"
|
||||
@tap.stop="handlerClick(item)">
|
||||
<span style="font-size:24rpx;color: #fff;">一键导航</span>
|
||||
<span style="font-size:24rpx;color: #fff;">{{ $lang('components-diy.diyMap.navigate') }}</span>
|
||||
</div>
|
||||
</map>
|
||||
</view>
|
||||
@@ -71,7 +71,7 @@ export default {
|
||||
uni.openLocation({
|
||||
latitude: parseFloat(item.lat),
|
||||
longitude: parseFloat(item.lng),
|
||||
name: "一键导航",
|
||||
name: this.$lang('components-diy.diyMap.navigate'),
|
||||
})
|
||||
},
|
||||
async handlerClick(item) {
|
||||
|
||||
@@ -38,15 +38,15 @@
|
||||
<text v-show="value.uploadTime == 1 && item.is_show_release_time == 1">{{
|
||||
item.update_time_day }}</text>
|
||||
</view>
|
||||
<view class="notes-num">
|
||||
<text v-show="value.readNum == 1 && item.is_show_read_num == 1">阅读 {{
|
||||
item.initial_read_num + item.read_num }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="notes-num">
|
||||
<text v-show="value.readNum == 1 && item.is_show_read_num == 1">{{ $lang('components-diy.diyNotes.readPrefix') }} {{
|
||||
item.initial_read_num + item.read_num }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<ns-login ref="login"></ns-login>
|
||||
</view>
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
v-model="searchText" @confirm="handlerSearchClick" disabled="true"
|
||||
@tap.stop="handlerSearchClick"
|
||||
:placeholderStyle="placeholderStyle" />
|
||||
<text class="search-content-btn" @tap.stop="handlerSearchClick"
|
||||
:style="{ 'backgroundColor': value.pageBgColor ? value.pageBgColor : 'rgba(0,0,0,0)' }">搜索</text>
|
||||
<text class="search-content-btn" @tap.stop="handlerSearchClick"
|
||||
:style="{ 'backgroundColor': value.pageBgColor ? value.pageBgColor : 'rgba(0,0,0,0)' }"
|
||||
>{{ $lang('components-diy.diySearch.searchBtn') }}</text>
|
||||
</view>
|
||||
<view class="img" v-if="value.iconType == 'img'"
|
||||
@tap.stop="handlerRedirectToClick(value.searchLink)">
|
||||
|
||||
@@ -903,8 +903,8 @@ export default {
|
||||
|
||||
// 如果 title 是字符串,保持原有逻辑
|
||||
if (typeof title === 'string' && title.includes('.')) {
|
||||
// 包含点号的标题视为国际化键,使用全局挂载的 $lang 方法翻译
|
||||
return this.$lang ? this.$lang(title) : title;
|
||||
// 包含点号的标题视为国际化键,使用全局挂载的 $lang 方法翻译;缺失时回退 title 本身
|
||||
return this.$lang ? this.$lang(title, title) : title;
|
||||
}
|
||||
|
||||
// 不包含点号的标题直接返回
|
||||
|
||||
@@ -207,8 +207,8 @@
|
||||
:style="{ fontSize: value.fontSize * 2 + 'rpx', color: value.textColor, fontWeight: value.fontWeight }">
|
||||
{{ value.text }}
|
||||
</text>
|
||||
<text class="zone"
|
||||
:style="{ fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">专区</text>
|
||||
<text class="zone"
|
||||
:style="{ fontSize: value.fontSize * 2 + 'rpx', fontWeight: value.fontWeight }">{{ $lang('components-diy.diyText.zone') }}</text>
|
||||
</view>
|
||||
<text class="iconfont icon-danxuan-xuanzhong"
|
||||
:style="{ color: value.textColor, fontWeight: value.fontWeight }"></text>
|
||||
|
||||
@@ -57,10 +57,10 @@
|
||||
<view v-else-if="message.contentType == 'order'"><ns-chat-order :isCanSend="false" :orderId="message.order_id"></ns-chat-order></view>
|
||||
|
||||
<view class="no-connect-box" v-if="message.contentType == 'noline'">
|
||||
<view class="no-connect">客服不在线</view>
|
||||
<view class="no-connect">{{ $lang('components.chatMessage.customerOffline') }}</view>
|
||||
</view>
|
||||
<view class="no-connect-box" v-if="message.contentType == 'online'">
|
||||
<view class="no-connect">客服在线</view>
|
||||
<view class="no-connect">{{ $lang('components.chatMessage.customerOnline') }}</view>
|
||||
</view>
|
||||
<uni-popup ref="imgPopup" type="center">
|
||||
<view class="imagePop">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<text class="goods-price-sign color-base-text">¥</text>
|
||||
<text class="color-base-text">{{ goodsInfo.price }}</text>
|
||||
</view>
|
||||
<view class="goods-option font-size-goods-tag disabled">已发送</view>
|
||||
<view class="goods-option font-size-goods-tag disabled">{{ $lang('components.chatGoods.sent') }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -22,7 +22,7 @@
|
||||
<text class="goods-price-sign color-base-text">¥</text>
|
||||
<text class="color-base-text">{{ goodsDetail.price }}</text>
|
||||
</view>
|
||||
<view class="goods-option font-size-goods-tag color-base-bg" @click="sendMsg('goods')">发送</view>
|
||||
<view class="goods-option font-size-goods-tag color-base-bg" @click="sendMsg('goods')">{{ $lang('components.chatGoods.send') }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<view class="goods-item">
|
||||
<view class="title">{{ isEnEnv ? goodsInfo.en_goods_name : goodsInfo.goods_name }}</view>
|
||||
<view class="goods-sku">
|
||||
库存:{{ goodsInfo.stock }}
|
||||
<text>销量:{{ goodsInfo.sale_num }}</text>
|
||||
{{ $lang('components.chatReceiveGoods.stockPrefix') }}{{ goodsInfo.stock }}
|
||||
<text>{{ $lang('components.chatReceiveGoods.salesPrefix') }}{{ goodsInfo.sale_num }}</text>
|
||||
</view>
|
||||
<view class="goods-price">
|
||||
<view class="price color-base-text">
|
||||
@@ -14,7 +14,7 @@
|
||||
<text class="price-num">{{ goodsInfo.price }}</text>
|
||||
</view>
|
||||
<view class="see-shop color-base-text" @click="go_shop()">
|
||||
查看商品
|
||||
{{ $lang('components.chatReceiveGoods.viewGoods') }}
|
||||
<text class="iconfont icon-right"></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<block v-if="type == 'point'">
|
||||
<block v-if="goodsDetail.type == 1">
|
||||
<view class="price-wrap">
|
||||
<text class="price price-color font-size-toolbar">{{ goodsDetail.point }}积分</text>
|
||||
<text class="price price-color font-size-toolbar">{{ $lang('components.nsGoodsSku.pointSuffix', { point: goodsDetail.point }) }}</text>
|
||||
<block v-if="goodsDetail.exchange_price != '0.00' && goodsDetail.pay_type > 0">
|
||||
<text class="unit price-color font-size-tag">+</text>
|
||||
<text class="price price-color font-size-toolbar price-font">{{ $lang('common.currencySymbol') }}{{ goodsDetail.exchange_price }}</text>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
autoplay
|
||||
class="video-player"
|
||||
></video>
|
||||
<view v-else class="video-error">视频地址不存在</view>
|
||||
<view v-else class="video-error">{{ $lang('components.videoPlayerPopup.videoUrlNotExist') }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<block v-if="payTypeList.length">
|
||||
<view class="payment-item" v-for="(item, index) in payTypeList" :key="index" @click="payIndex = index">
|
||||
<view class="iconfont" :class="item.icon"></view>
|
||||
<text class="name">{{ $lang('components.payment.' + item.type) }}</text>
|
||||
<text class="name">{{ $lang('components.payment.' + item.type, item.name) }}</text>
|
||||
<text class="iconfont" :class="payIndex == index ? 'icon-yuan_checked color-base-text' : 'icon-checkboxblank'"></text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<uni-popup type="bottom" ref="regionsRef">
|
||||
<view class="regions-picker-container">
|
||||
<view class="picker-header">
|
||||
<view class="picker-action cancel" @click="$refs.regionsRef.close()">取消</view>
|
||||
<view class="picker-action confirm" @click="confirmRegions">完成</view>
|
||||
<view class="picker-action cancel" @click="$refs.regionsRef.close()">{{ $lang('components.pickRegions.cancel') }}</view>
|
||||
<view class="picker-action confirm" @click="confirmRegions">{{ $lang('components.pickRegions.confirm') }}</view>
|
||||
</view>
|
||||
<picker-view :value="multiIndex" @change="pickerViewColumnChange" class="picker-view">
|
||||
<picker-view-column v-if="multiArray[0]">
|
||||
|
||||
222
i18n-audit-report.md
Normal file
222
i18n-audit-report.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# 国际化(i18n)全量审计报告
|
||||
|
||||
> 审计范围:`z:\D\projects\shop-projects\frontend` 下所有 `.vue` / `.js` 文件(排除 `node_modules`、`uni_modules`、`unpackage`、`docs`、`scripts` 等)
|
||||
> 审计时间:2026-06-29
|
||||
> 当前静态校验:`node scripts/i18n-check.js` 通过(zh-cn / en-us key 完全一致)
|
||||
> 修复状态:P0 / P1 / P2(兜底 + 验证)已修复并验证 ✅
|
||||
|
||||
## 一、核心结论
|
||||
|
||||
项目已大规模使用 `$lang()`,但仍存在 **三类问题**:
|
||||
|
||||
1. **Resolver/代码不匹配导致的 raw key(用户可见)**:组件/页面调用的 key 与语言包实际结构不一致,英文环境下会直接显示 key 名。
|
||||
2. **硬编码中文文本(英文环境下仍显示中文)**:`.vue` 模板和 `.js` 脚本中存在未替换为 `$lang()` 的静态中文。
|
||||
3. **动态 key 无法静态校验**:9 处依赖运行时数据拼接 key,需人工确认对应 key 是否完整。
|
||||
|
||||
---
|
||||
|
||||
## 二、P0 - 会显示 raw key 的调用错误(已修复 ✅)
|
||||
|
||||
> 修复文件:
|
||||
> - `components-diy/diy-article.vue`
|
||||
> - `pages_promotion/point/goods_list.vue`
|
||||
> - `pages_tool/member/cancelstatus.vue`
|
||||
> - `pages_tool/member/card_buy.vue`
|
||||
>
|
||||
> 验证:自定义脚本验证所有修改后的 key 均可解析,`npm run i18n:check` 通过。
|
||||
|
||||
### 1. `components-diy/diy-article.vue`
|
||||
|
||||
| 行 | 错误调用 | 语言包实际 key | 说明 |
|
||||
|---|---|---|---|
|
||||
| 93 | `$lang('components-diy.diy-article.viewMore')` | `components-diy.diyArticle.viewMore` | 组件名大小写不一致,`normalizeSharedLangPack` 将 `diy-article` 转为 `diyArticle` |
|
||||
| 98 | `$lang('components-diy.diy-article.noArticles')` | `components-diy.diyArticle.noArticles` | 同上 |
|
||||
|
||||
**修复建议**:统一改为 `$lang('components-diy.diyArticle.viewMore')` / `$lang('components-diy.diyArticle.noArticles')`。
|
||||
|
||||
### 2. `pages_promotion/point/goods_list.vue`
|
||||
|
||||
| 行 | 错误调用 | 语言包实际 key | 说明 |
|
||||
|---|---|---|---|
|
||||
| 9, 17, 20, 29, 38, 62, 75, 86, 94, 98, 101, 104, 109, 112, 123, 124, 286, 292, 299, 305 | `$lang('goods_list.xxx')` | 扁平 key(如 `searchPlaceholder`) | `normalizePageLangPack` 将文件名 `goods_list` camelCase 为 `goodsList`,导致 `goods_list.xxx` 无法解析 |
|
||||
|
||||
**修复建议**:页面内所有 `$lang('goods_list.xxx')` 改为 `$lang('xxx')`(该页面语言包为扁平结构),或改为 `$lang('goodsList.xxx')`。
|
||||
|
||||
### 3. `pages_tool/member/cancelstatus.vue`
|
||||
|
||||
| 行 | 错误调用 | 语言包实际 key | 说明 |
|
||||
|---|---|---|---|
|
||||
| 8, 9, 16, 17, 26, 27, 32, 33, 84, 85 | `$lang('member.cancelStatus.xxx')` | `member.cancelstatus.xxx`(小写 s) | 文件名 `cancelstatus` camelCase 后为 `cancelstatus`,页面调用为 `cancelStatus` |
|
||||
|
||||
**修复建议**:统一改为 `$lang('member.cancelstatus.xxx')`。
|
||||
|
||||
### 4. `pages_tool/member/card_buy.vue`
|
||||
|
||||
| 行 | 错误调用 | 语言包实际 key |
|
||||
|---|---|---|
|
||||
| 353 | `$lang('common.tip')` | `customerService.tip`(位于 `common.customerService.tip`) |
|
||||
|
||||
**修复建议**:改为 `$lang('customerService.tip')`,或根据业务语义使用其他 key。
|
||||
|
||||
---
|
||||
|
||||
## 三、P1 - 模板中硬编码中文(已修复 ✅)
|
||||
|
||||
共 **59 处**硬编码中文已替换为 `$lang()` 调用,并补充了对应的中/英文语言包。
|
||||
|
||||
- 验证:自定义 P1 验证脚本 `verify_p1_keys.js` 全部通过。
|
||||
- 静态校验:`npm run i18n:check` 通过(zh-cn / en-us key 完全一致)。
|
||||
- 语法检查:相关 `.js` 文件均通过 `node --check`。
|
||||
|
||||
原始 59 处按模块归类如下(实际落地 key 与最初建议略有差异,详见文末“命名空间修正记录”):
|
||||
|
||||
### 3.1 公共组件(`components/`)
|
||||
|
||||
| 文件 | 行 | 中文文本 | 建议 key |
|
||||
|---|---|---|---|
|
||||
| `components/chat-message/chat-message.vue` | 60 | 客服不在线 | `components.chatMessage.customerOffline` |
|
||||
| `components/chat-message/chat-message.vue` | 63 | 客服在线 | `components.chatMessage.customerOnline` |
|
||||
| `components/pick-regions/pick-regions.vue` | 17 | 取消 | `components.pickRegions.cancel` |
|
||||
| `components/pick-regions/pick-regions.vue` | 18 | 完成 | `components.pickRegions.confirm` |
|
||||
| `components/ns-goods-sku/ns-goods-sku.vue` | 22 | `{point}积分` | `components.goodsSku.pointSuffix`(使用命名参数) |
|
||||
| `components/ns-chat/ns-chat-receiveGoods.vue` | 8 | 库存: | `components.chatReceiveGoods.stockPrefix` |
|
||||
| `components/ns-chat/ns-chat-receiveGoods.vue` | 9 | 销量: | `components.chatReceiveGoods.salesPrefix` |
|
||||
| `components/ns-chat/ns-chat-receiveGoods.vue` | 17 | 查看商品 | `components.chatReceiveGoods.viewGoods` |
|
||||
| `components/ns-chat/ns-chat-goods.vue` | 12 | 已发送 | `components.chatGoods.sent` |
|
||||
| `components/ns-chat/ns-chat-goods.vue` | 25 | 发送 | `components.chatGoods.send` |
|
||||
| `components/ns-video-player-popup/ns-video-player-popup.vue` | 17 | 视频地址不存在 | `components.videoPlayerPopup.videoUrlNotExist` |
|
||||
|
||||
### 3.2 装修组件(`components-diy/`)
|
||||
|
||||
| 文件 | 行 | 中文文本 | 建议 key |
|
||||
|---|---|---|---|
|
||||
| `components-diy/diy-search.vue` | 31 | 搜索 | `components-diy.diySearch.searchBtn` |
|
||||
| `components-diy/diy-map.vue` | 16 | 一键导航 | `components-diy.diyMap.navigate` |
|
||||
| `components-diy/diy-kefu.vue` | 17 | 立即添加 | `components-diy.diyKefu.addNow` |
|
||||
| `components-diy/diy-channel-video.vue` | 20, 32, 40 | `{count}次观看` | `components-diy.diyChannelVideo.viewCountSuffix`(使用命名参数) |
|
||||
| `components-diy/diy-live.vue` | 29 | 主播: | `components-diy.diyLive.anchorPrefix` |
|
||||
| `components-diy/diy-live.vue` | 33 | 直播商品: | `components-diy.diyLive.goodsPrefix` |
|
||||
| `components-diy/diy-listmenu.vue` | 15 | 查看 | `components-diy.diyListMenu.view` |
|
||||
| `components-diy/diy-text.vue` | 211 | 专区 | `components-diy.diyText.zone` |
|
||||
| `components-diy/diy-goods-recommend.vue` | 56 | 售 / 件 | `components-diy.diyGoodsRecommend.soldPrefix` / `unit.piece` |
|
||||
| `components-diy/diy-notes.vue` | 42 | 阅读 | `components-diy.diyNotes.readPrefix` |
|
||||
|
||||
### 3.3 页面级(`pages_tool/`、`pages_promotion/`)
|
||||
|
||||
| 文件 | 行 | 中文文本 | 建议 key |
|
||||
|---|---|---|---|
|
||||
| `pages_tool/login/login.vue` | 396 | 请输入手机号 | `login.login.mobileRequired` |
|
||||
| `pages_tool/login/login.vue` | 400 | 请输入正确的手机号 | `login.login.mobileFormatError` |
|
||||
| `pages_tool/login/login.vue` | 419 | `{seconds}s后可重新获取` | `login.login.resendAfterSeconds` |
|
||||
| `pages_tool/login/login.vue` | 449 | 获取动态码 | `login.login.getDynacode` |
|
||||
| `pages_tool/member/index.vue` | 61 | 我知道了 | `member.index.iKnow` |
|
||||
| `pages_tool/index/diy.vue` | 46 | 我知道了 | `index.index.iKnow` |
|
||||
| `pages_tool/member/cancelsuccess.vue` | 7 | 您已成功注销账号 | `member.cancelsuccess.title` |
|
||||
| `pages_tool/member/cancelsuccess.vue` | 8 | 待下次与您更好的相遇... | `member.cancelsuccess.desc` |
|
||||
| `pages_tool/member/cancelsuccess.vue` | 9 | 完成 | `member.cancelsuccess.done` |
|
||||
| `pages_tool/member/cancelrefuse.vue` | 5 | 您的申请已拒绝 | `member.cancelrefuse.title` |
|
||||
| `pages_tool/member/cancelrefuse.vue` | 6 | 拒绝理由: | `member.cancelrefuse.reasonPrefix` |
|
||||
| `pages_tool/member/cancelrefuse.vue` | 8 | 返回 | `member.cancelrefuse.back` |
|
||||
| `pages_tool/member/cancelrefuse.vue` | 9 | 重新申请 | `member.cancelrefuse.reapply` |
|
||||
| `pages_tool/member/public/js/member-public.js` | 63 | 风险提示 | `member.public.riskWarningTitle` |
|
||||
| `pages_tool/member/public/js/member-public.js` | 64 | 确定要注销当前账号吗? | `member.public.cancelAccountConfirm` |
|
||||
| `pages_tool/goods/not_exist.vue` | 5 | 您查看的商品不存在... | `goods.notExist.message` |
|
||||
| `pages_tool/file-preview/file-preview.vue` | 15, 21 | 文件地址不存在 | `filePreview.fileUrlNotExist` |
|
||||
| `pages_tool/file-preview/file-preview.vue` | 27 | 视频地址不存在 | `filePreview.videoUrlNotExist` |
|
||||
| `pages_tool/file-preview/file-preview.vue` | 32 | 不支持的文件类型 | `filePreview.unsupportedFileType` |
|
||||
| `pages_tool/agreement/contenr.vue` | 11 | 暂无内容 | `agreement.noContent` |
|
||||
| `pages_tool/agreement/contenr.vue` | 12 | 该协议内容暂未设置 | `agreement.noContentDesc` |
|
||||
| `pages_tool/agreement/contenr.vue` | 13 | 重新加载 | `agreement.reload` |
|
||||
| `pages_tool/notice/list.vue` | 10 | 置顶 | `notice.list.topTag` |
|
||||
| `pages_tool/notice/list.vue` | 18 | 暂无公告 | `notice.list.noNotice` |
|
||||
| `pages_tool/notice/detail.vue` | 5 | 发表时间: | `notice.detail.publishTimePrefix` |
|
||||
| `pages_tool/help/list.vue` | 9 | 暂无帮助信息 | `help.list.noHelp` |
|
||||
| `pages_tool/help/detail.vue` | 6 | 发表时间: | `help.detail.publishTimePrefix` |
|
||||
| `pages_tool/goods/brand.vue` | 14 | 暂无更多品牌,去首页看看吧 | `goods.brand.noMoreBrand` |
|
||||
| `pages_tool/recharge/order_list.vue` | 16 | 充值成功 | `recharge.orderList.rechargeSuccess` |
|
||||
| `pages_tool/recharge/order_list.vue` | 34 | 暂无充值记录 | `recharge.orderList.noRecord` |
|
||||
| `pages_tool/seal/structure.vue` | 6 | 当前选择: / 请选择 | `seal.structure.currentChoicePrefix` / `seal.structure.pleaseSelect` |
|
||||
| `pages_tool/seal/structure.vue` | 12 | 选型结果: | `seal.structure.resultPrefix` |
|
||||
| `pages_tool/seal/structure.vue` | 23 | 结果文件 | `seal.structure.resultFile` |
|
||||
| `pages_promotion/fenxiao/bill.vue` | 11 | 账单编号: | `fenxiao.bill.billNoPrefix` |
|
||||
| `pages_promotion/fenxiao/bill.vue` | 19 | 暂无账单信息 | `fenxiao.bill.noBillData` |
|
||||
|
||||
### 3.4 公共 JS
|
||||
|
||||
| 文件 | 行 | 中文文本 | 建议 key |
|
||||
|---|---|---|---|
|
||||
| `common/js/auth.js` | 36, 77 | 小程序配置错误 | `common.auth.miniProgramConfigError` |
|
||||
| `common/js/map/openMap.js` | 11 | 打开地图失败,请稍后重试 | `common.map.openMapFailed` |
|
||||
| `common/js/goods_detail_base.js` | 144, 202 | 件 | `unit.piece`(`common.js` 中位于顶层 `unit.piece`) |
|
||||
| `pages_order/public/js/refundMethod.js` | 10 | 撤销之后本次申请将会关闭... | `order.refund.cancelRefundContent` |
|
||||
| `pages_order/public/js/refundMethod.js` | 11 | 暂不撤销 | `order.refund.cancelRefundCancelText` |
|
||||
| `pages_goods/public/js/list.js` | 40 | 购买 | `goods.list.buyText` |
|
||||
|
||||
### 3.5 文档示例(可忽略)
|
||||
|
||||
| 文件 | 行 | 中文文本 |
|
||||
|---|---|---|
|
||||
| `docs/example/wechat-channel-demo.vue` | 4-23 | 微信视频号集成示例、单个视频号、视频号列表、使用说明等 |
|
||||
|
||||
> 该文件为文档示例,是否国际化可单独评估。
|
||||
|
||||
---
|
||||
|
||||
## 四、P2 - 动态 key 调用(已兜底 + 验证 ✅)
|
||||
|
||||
以下 8 处 `$lang()` 使用运行时变量拼接 key,无法被 `scripts/i18n-check.js` 静态覆盖。已采取以下措施:
|
||||
|
||||
1. **运行时兜底**:对可获取默认文案的场景,为 `$lang()` 增加第二参数 fallback,避免 key 缺失时暴露 raw key。
|
||||
2. **专项验证脚本**:新增 `scripts/verify-p2-keys.js`,枚举所有已知取值并模拟 resolver 校验 zh-cn / en-us 解析结果。
|
||||
3. **DIY 数据侧**:`components-diy/diy-tab.vue` 的 title 来自运行时配置,已在组件内增加 `this.$lang(title, title)` 兜底。
|
||||
|
||||
| 文件 | 行 | 调用片段 | 处理 |
|
||||
|---|---|---|---|
|
||||
| `components/payment/payment.vue` | 27 | `$lang('components.payment.' + item.type)` | 增加 `item.name` fallback |
|
||||
| `components-diy/diy-tab.vue` | 907 | `this.$lang ? this.$lang(title) : title` | 改为 `this.$lang(title, title)` |
|
||||
| `pages_order/list.vue` | 15 | `$lang('status.' + statusItem.status)` | 增加 `statusItem.name` fallback |
|
||||
| `pages_promotion/fenxiao/order.vue` | 6 | `$lang(item.nameKey)` | 取值本地枚举,已在 `verify-p2-keys.js` 验证 |
|
||||
| `pages_promotion/fenxiao/team.vue` | 7 | `$lang(item.nameKey)` | 取值本地枚举,已在 `verify-p2-keys.js` 验证 |
|
||||
| `pages_promotion/point/order_list.vue` | 8 | `$lang('point.orderList.' + statusItem.nameKey)` | 取值本地枚举,已在 `verify-p2-keys.js` 验证 |
|
||||
| `pages_tool/goods/evaluate.vue` | 6 | `$lang(item.langKey)` | 取值本地枚举,已在 `verify-p2-keys.js` 验证 |
|
||||
| `pages_tool/pay/cashier.vue` | 16 | `$lang('pay.cashier.' + item.type)` | 增加 `item.name` fallback |
|
||||
|
||||
验证结果:`node scripts/verify-p2-keys.js` 全部 ✅;`npm run i18n:check` 通过。
|
||||
|
||||
---
|
||||
|
||||
## 五、修复优先级建议
|
||||
|
||||
1. **P0(已修复)**:4 个文件共 30+ 处错误调用已在英文环境下直接显示 raw key,已全部修正。
|
||||
2. **P1(已修复)**:59 处硬编码中文已按模块补充语言包并替换为 `$lang()`。
|
||||
3. **P2(已兜底 + 验证)**:8 处动态 key 已增加运行时 fallback 并建立枚举值验证脚本。
|
||||
|
||||
---
|
||||
|
||||
## 六、命名空间规范提醒
|
||||
|
||||
根据项目约定和 `common/js/lang.js` 的解析逻辑:
|
||||
|
||||
- 页面语言包:优先使用扁平 key(如 `$lang('searchPlaceholder')`),必要时使用 camelCase 路径(如 `$lang('member.cancelstatus.step1Name')`)。
|
||||
- 共享组件语言包:统一按 `components.{ComponentCamelCase}.key` 或 `components-diy.{ComponentCamelCase}.key` 调用。
|
||||
- 公共 JS 文案:统一放入 `lang/zh-cn/common.js` / `lang/en-us/common.js`,使用 `common.*` 命名空间。
|
||||
- 新增 key 后务必在 `lang/zh-cn/` 和 `lang/en-us/` 同步,并运行 `npm run i18n:check`。
|
||||
|
||||
## 七、P1 实施过程中的命名空间修正记录
|
||||
|
||||
在按审计建议替换 P1 硬编码中文时,发现若干组件/页面实际调用的 key 与语言包结构不匹配,已一并修正:
|
||||
|
||||
| 文件 | 问题 | 修正 |
|
||||
|---|---|---|
|
||||
| `components-diy/diy-listmenu.vue` | 调用 `components-diy.diyListMenu.view`,但 `normalizeSharedLangPack` 生成的命名空间为 `diyListmenu`(小写 m) | 改为 `components-diy.diyListmenu.view` |
|
||||
| `lang/zh-cn/components/ns-goods-sku.js`、`lang/en-us/components/ns-goods-sku.js` | 语言包使用 `components.goodsSku.pointSuffix`,与文件路径 `components/ns-goods-sku` 生成的 `components.nsGoodsSku` 不一致 | 改为 `components.nsGoodsSku.pointSuffix` |
|
||||
| `lang/zh-cn/notice/list.js`、`lang/en-us/notice/list.js` | 包内存在嵌套 `notice: { ... }`,导致完整命名空间 `notice.list.topTag` 无法解析 | 拍平为扁平结构 |
|
||||
| `lang/zh-cn/notice/detail.js`、`lang/en-us/notice/detail.js` | 同上,嵌套 `notice` 对象 | 拍平为扁平结构 |
|
||||
| `lang/zh-cn/help/list.js`、`lang/en-us/help/list.js` | 同上,嵌套 `help` 对象 | 拍平为扁平结构 |
|
||||
| `lang/zh-cn/help/detail.js`、`lang/en-us/help/detail.js` | 同上,嵌套 `help` 对象 | 拍平为扁平结构 |
|
||||
| `lang/zh-cn/login/login.js`、`lang/en-us/login/login.js` | 新增 key 放在 `login: { ... }` 下,导致 `login.login.mobileRequired` 解析失败 | 拍平为扁平结构 |
|
||||
| `lang/zh-cn/member/public.js`、`lang/en-us/member/public.js` | 包内为 `{ public: { ... } }`,但源码调用 `member.public.riskWarningTitle` | 包装为 `{ member: { public: { ... } } }` |
|
||||
| `common/js/goods_detail_base.js`、`components-diy/diy-goods-recommend.vue` | 调用 `common.unit.piece`,但 `common.js` 中该 key 位于顶层 `unit.piece` | 改为 `unit.piece` |
|
||||
| `common/js/lang.js` | 新增组件语言包未注入共享上下文 | 补充 `SHARED_LANG_PACKS` 列表 |
|
||||
|
||||
> 说明:`components/ns-goods-sku/*.vue` 中大量使用了 `goods.detail.*` 形式的 key。这些 key 实际定义在页面语言包 `lang/zh-cn/goods/detail.js` 中,运行时通过 `tryResolveQualifiedPageField` 动态加载并解析;P1 验证脚本已按此逻辑加载 `goods/detail` 包进行校验,确认全部可解析。
|
||||
@@ -1,4 +1,13 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: 'Agreement',
|
||||
// agreement/contenr.vue page new translation items
|
||||
agreement: {
|
||||
// No content
|
||||
noContent: 'No content',
|
||||
// This agreement content has not been set
|
||||
noContentDesc: 'This agreement content has not been set',
|
||||
// Reload
|
||||
reload: 'Reload',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -262,4 +262,17 @@ export const lang = {
|
||||
locationParamFormatError: 'Invalid location parameter format',
|
||||
keyCannotBeEmpty: 'Key cannot be empty',
|
||||
},
|
||||
|
||||
// Common JS text
|
||||
auth: {
|
||||
miniProgramConfigError: 'Mini program configuration error',
|
||||
},
|
||||
|
||||
map: {
|
||||
openMapFailed: 'Failed to open map, please try again later',
|
||||
},
|
||||
|
||||
unit: {
|
||||
piece: 'pc',
|
||||
},
|
||||
}
|
||||
5
lang/en-us/components-diy/diy-channel-video.js
Normal file
5
lang/en-us/components-diy/diy-channel-video.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-channel-video channel video component
|
||||
// View count suffix: {count} is the view count placeholder
|
||||
viewCountSuffix: '{count} views',
|
||||
}
|
||||
5
lang/en-us/components-diy/diy-goods-recommend.js
Normal file
5
lang/en-us/components-diy/diy-goods-recommend.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-goods-recommend product recommendation component
|
||||
// Sold prefix
|
||||
soldPrefix: 'Sold ',
|
||||
}
|
||||
5
lang/en-us/components-diy/diy-kefu.js
Normal file
5
lang/en-us/components-diy/diy-kefu.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-kefu customer service component
|
||||
// Add now
|
||||
addNow: 'Add Now',
|
||||
}
|
||||
5
lang/en-us/components-diy/diy-listmenu.js
Normal file
5
lang/en-us/components-diy/diy-listmenu.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-listmenu list menu component
|
||||
// View
|
||||
view: 'View',
|
||||
}
|
||||
7
lang/en-us/components-diy/diy-live.js
Normal file
7
lang/en-us/components-diy/diy-live.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export const lang = {
|
||||
// diy-live live streaming component
|
||||
// Anchor prefix
|
||||
anchorPrefix: 'Anchor: ',
|
||||
// Live goods prefix
|
||||
goodsPrefix: 'Live Products: ',
|
||||
}
|
||||
5
lang/en-us/components-diy/diy-map.js
Normal file
5
lang/en-us/components-diy/diy-map.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-map map component
|
||||
// One-click navigation
|
||||
navigate: 'Navigate',
|
||||
}
|
||||
5
lang/en-us/components-diy/diy-notes.js
Normal file
5
lang/en-us/components-diy/diy-notes.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-notes notes component
|
||||
// Read prefix
|
||||
readPrefix: 'Reads',
|
||||
}
|
||||
5
lang/en-us/components-diy/diy-search.js
Normal file
5
lang/en-us/components-diy/diy-search.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-search search component
|
||||
// Search button
|
||||
searchBtn: 'Search',
|
||||
}
|
||||
5
lang/en-us/components-diy/diy-text.js
Normal file
5
lang/en-us/components-diy/diy-text.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-text text component
|
||||
// Zone label
|
||||
zone: 'Zone',
|
||||
}
|
||||
11
lang/en-us/components/chat-message.js
Normal file
11
lang/en-us/components/chat-message.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const lang = {
|
||||
// chat-message component
|
||||
components: {
|
||||
chatMessage: {
|
||||
// Customer service offline hint
|
||||
customerOffline: 'Customer service is offline',
|
||||
// Customer service online hint
|
||||
customerOnline: 'Customer service is online',
|
||||
},
|
||||
},
|
||||
}
|
||||
11
lang/en-us/components/ns-chat-goods.js
Normal file
11
lang/en-us/components/ns-chat-goods.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const lang = {
|
||||
// ns-chat-goods chat goods component
|
||||
components: {
|
||||
chatGoods: {
|
||||
// Sent status
|
||||
sent: 'Sent',
|
||||
// Send button
|
||||
send: 'Send',
|
||||
},
|
||||
},
|
||||
}
|
||||
13
lang/en-us/components/ns-chat-receiveGoods.js
Normal file
13
lang/en-us/components/ns-chat-receiveGoods.js
Normal file
@@ -0,0 +1,13 @@
|
||||
export const lang = {
|
||||
// ns-chat-receiveGoods chat received goods component
|
||||
components: {
|
||||
chatReceiveGoods: {
|
||||
// Stock prefix
|
||||
stockPrefix: 'Stock:',
|
||||
// Sales prefix
|
||||
salesPrefix: 'Sales:',
|
||||
// View goods button
|
||||
viewGoods: 'View Product',
|
||||
},
|
||||
},
|
||||
}
|
||||
9
lang/en-us/components/ns-goods-sku.js
Normal file
9
lang/en-us/components/ns-goods-sku.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const lang = {
|
||||
// ns-goods-sku product SKU component
|
||||
components: {
|
||||
nsGoodsSku: {
|
||||
// Point suffix: {point} is the points amount placeholder
|
||||
pointSuffix: '{point} points',
|
||||
},
|
||||
},
|
||||
}
|
||||
9
lang/en-us/components/ns-video-player-popup.js
Normal file
9
lang/en-us/components/ns-video-player-popup.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const lang = {
|
||||
// ns-video-player-popup video popup component
|
||||
components: {
|
||||
videoPlayerPopup: {
|
||||
// Video URL does not exist hint
|
||||
videoUrlNotExist: 'Video URL does not exist',
|
||||
},
|
||||
},
|
||||
}
|
||||
11
lang/en-us/components/pick-regions.js
Normal file
11
lang/en-us/components/pick-regions.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const lang = {
|
||||
// pick-regions region picker component
|
||||
components: {
|
||||
pickRegions: {
|
||||
// Cancel button
|
||||
cancel: 'Cancel',
|
||||
// Confirm button
|
||||
confirm: 'Done',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,16 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: 'Bill'
|
||||
}
|
||||
title: 'Bill',
|
||||
// fenxiao/bill.vue page new translation items
|
||||
fenxiao: {
|
||||
// Bill
|
||||
bill: {
|
||||
// Bill number prefix
|
||||
billNoPrefix: 'Bill No.:',
|
||||
// No bill information
|
||||
noBill: 'No bill information',
|
||||
// Distribution is not enabled by merchant
|
||||
addonClosed: 'Distribution is not enabled by merchant',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: 'File Preview',
|
||||
// file-preview/file-preview.vue page new translation items
|
||||
filePreview: {
|
||||
// File URL does not exist
|
||||
fileUrlNotExist: 'File URL does not exist',
|
||||
// Video URL does not exist
|
||||
videoUrlNotExist: 'Video URL does not exist',
|
||||
// This file type preview is not supported
|
||||
unsupportedFileType: 'This file type preview is not supported',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: 'Brand Zone'
|
||||
title: 'Brand Zone',
|
||||
// goods/brand.vue page new translation items
|
||||
brand: {
|
||||
// No more brands
|
||||
noMoreBrand: 'No more brands, go to the homepage',
|
||||
},
|
||||
}
|
||||
@@ -20,4 +20,6 @@ export const lang = {
|
||||
minPriceGreaterThanMax: 'Minimum price cannot be greater than maximum price',
|
||||
maxPriceLessThanMin: 'Maximum price cannot be less than minimum price',
|
||||
|
||||
// pages_goods/public/js/list.js new translation items
|
||||
buyText: 'Buy',
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: 'Product Not Found',
|
||||
// goods/not_exist.vue page new translation items
|
||||
notExist: {
|
||||
// The product you are viewing does not exist, it may have been removed or deleted
|
||||
message: 'The product you are viewing does not exist, it may have been removed or deleted',
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: 'Help Detail'
|
||||
}
|
||||
// Page title
|
||||
title: 'Help Detail',
|
||||
// Publish time prefix
|
||||
publishTimePrefix: 'Published at: ',
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
// Page title
|
||||
title: 'Help Center',
|
||||
emptyText: 'No help information available'
|
||||
}
|
||||
emptyText:"No help information",
|
||||
// No help
|
||||
noHelp: 'No help',
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export const lang = {
|
||||
title: ''
|
||||
title: '',
|
||||
// index/diy.vue page new translation items
|
||||
diy: {
|
||||
// I got it
|
||||
iKnow: 'Got it',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -34,5 +34,10 @@ export const lang = {
|
||||
enterMobileTip: 'Please enter mobile phone number',
|
||||
enterCorrectMobileTip: 'Please enter correct mobile phone number',
|
||||
enterCaptchaTip: 'Please enter verification code',
|
||||
requestFailTip: 'request:fail'
|
||||
requestFailTip: 'request:fail',
|
||||
|
||||
// Login validation and countdown text
|
||||
mobileRequired: 'Please enter mobile phone number',
|
||||
mobileFormatError: 'Please enter correct mobile phone number',
|
||||
resendAfterSeconds: '{seconds}s before resend',
|
||||
}
|
||||
@@ -1,3 +1,14 @@
|
||||
export const lang = {
|
||||
title: 'Account Cancellation',
|
||||
// member/cancelrefuse.vue page new translation items
|
||||
cancelrefuse: {
|
||||
// Your application has been rejected
|
||||
title: 'Your application has been rejected',
|
||||
// Reason for rejection:
|
||||
reasonPrefix: 'Reason for rejection: ',
|
||||
// Back
|
||||
back: 'Back',
|
||||
// Reapply
|
||||
reapply: 'Reapply',
|
||||
},
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
export const lang = {
|
||||
title: 'Account Cancellation',
|
||||
// member/cancelsuccess.vue page new translation items
|
||||
cancelsuccess: {
|
||||
// You have successfully cancelled your account
|
||||
title: 'You have successfully cancelled your account',
|
||||
// Looking forward to seeing you again. To use again, please re-register
|
||||
desc: 'Looking forward to seeing you again. To use again, please re-register',
|
||||
// Done
|
||||
done: 'Done',
|
||||
},
|
||||
}
|
||||
@@ -58,4 +58,10 @@ export const lang = {
|
||||
// 协议
|
||||
privacyPolicy: 'Privacy Policy',
|
||||
registrationAgreement: 'Registration Agreement',
|
||||
|
||||
// member/index.vue page new translation items
|
||||
index: {
|
||||
// I got it
|
||||
iKnow: 'Got it',
|
||||
},
|
||||
}
|
||||
|
||||
11
lang/en-us/member/public.js
Normal file
11
lang/en-us/member/public.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const lang = {
|
||||
// member/public member common mixin
|
||||
member: {
|
||||
public: {
|
||||
// Risk warning
|
||||
riskWarningTitle: 'Risk Warning',
|
||||
// Confirm account cancellation
|
||||
cancelAccountConfirm: 'Are you sure you want to cancel the current account?',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: 'Notice Detail'
|
||||
}
|
||||
// Page title
|
||||
title: 'Announcement Detail',
|
||||
// Publish time prefix
|
||||
publishTimePrefix: 'Published at: ',
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: 'Notice List',
|
||||
emptyText:"No more information available",
|
||||
contentTitle:'Upgrade Notice'
|
||||
}
|
||||
// Page title
|
||||
title: 'Announcement List',
|
||||
emptyText:"No more information",
|
||||
contentTitle:'Upgrade Announcement',
|
||||
// Top tag
|
||||
topTag: 'Top',
|
||||
// No announcements
|
||||
noNotice: 'No announcements',
|
||||
}
|
||||
|
||||
@@ -33,5 +33,14 @@ export const lang = {
|
||||
// ====== Form Validation ======
|
||||
validate: {
|
||||
pleaseSelectRefundReason: 'Please select a refund reason'
|
||||
}
|
||||
},
|
||||
|
||||
// ====== Refund Method Popup ======
|
||||
// pages_order/public/js/refundMethod.js new translation items
|
||||
refund: {
|
||||
// After cancellation, this application will be closed; you can reapply later if needed
|
||||
cancelRefundContent: 'After cancellation, this application will be closed; you can reapply later if needed.',
|
||||
// Do not cancel for now
|
||||
cancelRefundCancelText: 'Do not cancel for now',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: 'Recharge Records',
|
||||
// recharge/order_list.vue page new translation items
|
||||
recharge: {
|
||||
// Recharge successful
|
||||
rechargeSuccess: 'Recharge Successful',
|
||||
// No recharge records
|
||||
noRecord: 'No recharge records',
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,27 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: 'Structure Selection',
|
||||
// seal/structure.vue page new translation items
|
||||
structure: {
|
||||
// Current choice prefix
|
||||
currentChoicePrefix: 'Current choice: ',
|
||||
// Please select
|
||||
pleaseSelect: 'Please select',
|
||||
// Result prefix
|
||||
resultPrefix: 'Selection result: ',
|
||||
// Result file
|
||||
resultFile: 'Result file',
|
||||
// Please select material
|
||||
selectMaterial: 'Please select material',
|
||||
// Please select structure
|
||||
selectStructure: 'Please select structure',
|
||||
// Upload image
|
||||
uploadImage: 'Upload Image',
|
||||
// Please upload image
|
||||
pleaseUploadImage: 'Please upload image',
|
||||
// Submit requirement
|
||||
submitRequirement: 'Submit Requirement',
|
||||
// Submit successful
|
||||
submitSuccess: 'Submitted Successfully',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: '协议',
|
||||
// agreement/contenr.vue 页面新增翻译项
|
||||
agreement: {
|
||||
// 暂无内容
|
||||
noContent: '暂无内容',
|
||||
// 该协议内容暂未设置
|
||||
noContentDesc: '该协议内容暂未设置',
|
||||
// 重新加载
|
||||
reload: '重新加载',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -260,4 +260,17 @@ export const lang = {
|
||||
keyCannotBeEmpty: 'key值不能为空',
|
||||
},
|
||||
|
||||
// 公共 JS 文案
|
||||
auth: {
|
||||
miniProgramConfigError: '小程序配置错误',
|
||||
},
|
||||
|
||||
map: {
|
||||
openMapFailed: '打开地图失败,请稍后重试',
|
||||
},
|
||||
|
||||
unit: {
|
||||
piece: '件',
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
5
lang/zh-cn/components-diy/diy-channel-video.js
Normal file
5
lang/zh-cn/components-diy/diy-channel-video.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-channel-video 视频号组件
|
||||
// 观看次数后缀: {count} 为次数占位符
|
||||
viewCountSuffix: '{count}次观看',
|
||||
}
|
||||
5
lang/zh-cn/components-diy/diy-goods-recommend.js
Normal file
5
lang/zh-cn/components-diy/diy-goods-recommend.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-goods-recommend 商品推荐组件
|
||||
// 销量前缀
|
||||
soldPrefix: '售',
|
||||
}
|
||||
5
lang/zh-cn/components-diy/diy-kefu.js
Normal file
5
lang/zh-cn/components-diy/diy-kefu.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-kefu 客服组件
|
||||
// 立即添加
|
||||
addNow: '立即添加',
|
||||
}
|
||||
5
lang/zh-cn/components-diy/diy-listmenu.js
Normal file
5
lang/zh-cn/components-diy/diy-listmenu.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-listmenu 列表菜单组件
|
||||
// 查看
|
||||
view: '查看',
|
||||
}
|
||||
7
lang/zh-cn/components-diy/diy-live.js
Normal file
7
lang/zh-cn/components-diy/diy-live.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export const lang = {
|
||||
// diy-live 直播组件
|
||||
// 主播前缀
|
||||
anchorPrefix: '主播:',
|
||||
// 直播商品前缀
|
||||
goodsPrefix: '直播商品:',
|
||||
}
|
||||
5
lang/zh-cn/components-diy/diy-map.js
Normal file
5
lang/zh-cn/components-diy/diy-map.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-map 地图组件
|
||||
// 一键导航
|
||||
navigate: '一键导航',
|
||||
}
|
||||
5
lang/zh-cn/components-diy/diy-notes.js
Normal file
5
lang/zh-cn/components-diy/diy-notes.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-notes 笔记组件
|
||||
// 阅读前缀
|
||||
readPrefix: '阅读',
|
||||
}
|
||||
5
lang/zh-cn/components-diy/diy-search.js
Normal file
5
lang/zh-cn/components-diy/diy-search.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-search 搜索组件
|
||||
// 搜索按钮
|
||||
searchBtn: '搜索',
|
||||
}
|
||||
5
lang/zh-cn/components-diy/diy-text.js
Normal file
5
lang/zh-cn/components-diy/diy-text.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const lang = {
|
||||
// diy-text 文本组件
|
||||
// 专区标签
|
||||
zone: '专区',
|
||||
}
|
||||
11
lang/zh-cn/components/chat-message.js
Normal file
11
lang/zh-cn/components/chat-message.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const lang = {
|
||||
// chat-message 组件
|
||||
components: {
|
||||
chatMessage: {
|
||||
// 客服不在线提示
|
||||
customerOffline: '客服不在线',
|
||||
// 客服在线提示
|
||||
customerOnline: '客服在线',
|
||||
},
|
||||
},
|
||||
}
|
||||
11
lang/zh-cn/components/ns-chat-goods.js
Normal file
11
lang/zh-cn/components/ns-chat-goods.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const lang = {
|
||||
// ns-chat-goods 聊天商品组件
|
||||
components: {
|
||||
chatGoods: {
|
||||
// 已发送状态
|
||||
sent: '已发送',
|
||||
// 发送按钮
|
||||
send: '发送',
|
||||
},
|
||||
},
|
||||
}
|
||||
13
lang/zh-cn/components/ns-chat-receiveGoods.js
Normal file
13
lang/zh-cn/components/ns-chat-receiveGoods.js
Normal file
@@ -0,0 +1,13 @@
|
||||
export const lang = {
|
||||
// ns-chat-receiveGoods 聊天收到商品组件
|
||||
components: {
|
||||
chatReceiveGoods: {
|
||||
// 库存前缀
|
||||
stockPrefix: '库存:',
|
||||
// 销量前缀
|
||||
salesPrefix: '销量:',
|
||||
// 查看商品按钮
|
||||
viewGoods: '查看商品',
|
||||
},
|
||||
},
|
||||
}
|
||||
9
lang/zh-cn/components/ns-goods-sku.js
Normal file
9
lang/zh-cn/components/ns-goods-sku.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const lang = {
|
||||
// ns-goods-sku 商品 SKU 组件
|
||||
components: {
|
||||
nsGoodsSku: {
|
||||
// 积分后缀: {point} 为积分数量占位符
|
||||
pointSuffix: '{point}积分',
|
||||
},
|
||||
},
|
||||
}
|
||||
9
lang/zh-cn/components/ns-video-player-popup.js
Normal file
9
lang/zh-cn/components/ns-video-player-popup.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const lang = {
|
||||
// ns-video-player-popup 视频弹窗组件
|
||||
components: {
|
||||
videoPlayerPopup: {
|
||||
// 视频地址不存在提示
|
||||
videoUrlNotExist: '视频地址不存在',
|
||||
},
|
||||
},
|
||||
}
|
||||
11
lang/zh-cn/components/pick-regions.js
Normal file
11
lang/zh-cn/components/pick-regions.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const lang = {
|
||||
// pick-regions 地区选择组件
|
||||
components: {
|
||||
pickRegions: {
|
||||
// 取消按钮
|
||||
cancel: '取消',
|
||||
// 完成按钮
|
||||
confirm: '完成',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,16 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: '账单'
|
||||
title: '账单',
|
||||
// fenxiao/bill.vue 页面新增翻译项
|
||||
fenxiao: {
|
||||
// 账单
|
||||
bill: {
|
||||
// 账单编号前缀
|
||||
billNoPrefix: '账单编号:',
|
||||
// 暂无账单信息
|
||||
noBill: '暂无账单信息',
|
||||
// 商家未开启分销
|
||||
addonClosed: '商家未开启分销',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: '文件预览',
|
||||
// file-preview/file-preview.vue 页面新增翻译项
|
||||
filePreview: {
|
||||
// 文件地址不存在
|
||||
fileUrlNotExist: '文件地址不存在',
|
||||
// 视频地址不存在
|
||||
videoUrlNotExist: '视频地址不存在',
|
||||
// 暂不支持该类型文件预览
|
||||
unsupportedFileType: '暂不支持该类型文件预览',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: '品牌专区'
|
||||
title: '品牌专区',
|
||||
// goods/brand.vue 页面新增翻译项
|
||||
brand: {
|
||||
// 没有更多品牌了
|
||||
noMoreBrand: '暂无更多品牌,去首页看看吧',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -20,4 +20,6 @@ export const lang = {
|
||||
minPriceGreaterThanMax: '最低价不能大于最高价',
|
||||
maxPriceLessThanMin: '最高价不能小于最低价',
|
||||
|
||||
// pages_goods/public/js/list.js 新增翻译项
|
||||
buyText: '购买',
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: '商品找不到',
|
||||
// goods/not_exist.vue 页面新增翻译项
|
||||
notExist: {
|
||||
// 您查看的商品不存在,可能已下架或被删除
|
||||
message: '您查看的商品不存在,可能已下架或被删除',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: '帮助详情'
|
||||
title: '帮助详情',
|
||||
// 发布时间前缀
|
||||
publishTimePrefix: '发布时间:',
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: '帮助中心',
|
||||
emptyText:"当前暂无帮助信息"
|
||||
emptyText:"当前暂无帮助信息",
|
||||
// 暂无帮助
|
||||
noHelp: '暂无帮助',
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export const lang = {
|
||||
title: ''
|
||||
title: '',
|
||||
// index/diy.vue 页面新增翻译项
|
||||
diy: {
|
||||
// 我知道了
|
||||
iKnow: '我知道了',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -17,22 +17,27 @@ export const lang = {
|
||||
registerTips1: '注册',
|
||||
registerTips2: '吧',
|
||||
newUserRegister: '新用户注册',
|
||||
// login.vue页面新增翻译项
|
||||
welcomeText: '您好,<br />欢迎使用',
|
||||
getDynacode: '获取动态码',
|
||||
mobileInputPlaceholder: '请输入您的手机号',
|
||||
accountInputPlaceholder: '请输入账号',
|
||||
passwordInputPlaceholder: '请输入密码',
|
||||
forgetPasswordText: '忘记密码?',
|
||||
dynacodeInputPlaceholder: '请输入动态码',
|
||||
loginButtonText: '登录',
|
||||
registerAgreementText: '若您未注册,则登录后将自动帮您注册。注册即视为同意',
|
||||
privacyPolicyText: '《隐私条款》',
|
||||
userServiceAgreementText: '《用户服务协议》',
|
||||
agreeAgreementTip: '请先阅读并同意协议',
|
||||
loginSuccessTip: '登录成功',
|
||||
enterMobileTip: '请输入手机号',
|
||||
enterCorrectMobileTip: '请输入正确的手机号',
|
||||
enterCaptchaTip: '请输入验证码',
|
||||
requestFailTip: 'request:fail'
|
||||
// login.vue 页面新增翻译项
|
||||
welcomeText: '您好,<br />欢迎使用',
|
||||
getDynacode: '获取动态码',
|
||||
mobileInputPlaceholder: '请输入您的手机号',
|
||||
accountInputPlaceholder: '请输入账号',
|
||||
passwordInputPlaceholder: '请输入密码',
|
||||
forgetPasswordText: '忘记密码?',
|
||||
dynacodeInputPlaceholder: '请输入动态码',
|
||||
loginButtonText: '登录',
|
||||
registerAgreementText: '若您未注册,则登录后将自动帮您注册。注册即视为同意',
|
||||
privacyPolicyText: '《隐私条款》',
|
||||
userServiceAgreementText: '《用户服务协议》',
|
||||
agreeAgreementTip: '请先阅读并同意协议',
|
||||
loginSuccessTip: '登录成功',
|
||||
enterMobileTip: '请输入手机号',
|
||||
enterCorrectMobileTip: '请输入正确的手机号',
|
||||
enterCaptchaTip: '请输入验证码',
|
||||
requestFailTip: 'request:fail',
|
||||
|
||||
// 登录校验与倒计时文案
|
||||
mobileRequired: '请输入手机号',
|
||||
mobileFormatError: '请输入正确的手机号',
|
||||
resendAfterSeconds: '{seconds}s后可重新获取',
|
||||
}
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
export const lang = {
|
||||
title: '账号注销',
|
||||
// member/cancelrefuse.vue 页面新增翻译项
|
||||
cancelrefuse: {
|
||||
// 您的申请已拒绝
|
||||
title: '您的申请已拒绝',
|
||||
// 拒绝理由:
|
||||
reasonPrefix: '拒绝理由:',
|
||||
// 返回
|
||||
back: '返回',
|
||||
// 重新申请
|
||||
reapply: '重新申请',
|
||||
},
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
export const lang = {
|
||||
title: '账号注销',
|
||||
// member/cancelsuccess.vue 页面新增翻译项
|
||||
cancelsuccess: {
|
||||
// 您已成功注销账号
|
||||
title: '您已成功注销账号',
|
||||
// 待下次与您更好的相遇,如需再次使用,请重新注册
|
||||
desc: '待下次与您更好的相遇,如需再次使用,请重新注册',
|
||||
// 完成
|
||||
done: '完成',
|
||||
},
|
||||
}
|
||||
@@ -61,4 +61,10 @@ export const lang = {
|
||||
// 协议
|
||||
privacyPolicy: '隐私协议',
|
||||
registrationAgreement: '注册协议',
|
||||
|
||||
// member/index.vue 页面新增翻译项
|
||||
index: {
|
||||
// 我知道了
|
||||
iKnow: '我知道了',
|
||||
},
|
||||
}
|
||||
|
||||
11
lang/zh-cn/member/public.js
Normal file
11
lang/zh-cn/member/public.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const lang = {
|
||||
// member/public 会员公共 mixin
|
||||
member: {
|
||||
public: {
|
||||
// 风险提示
|
||||
riskWarningTitle: '风险提示',
|
||||
// 注销账号确认
|
||||
cancelAccountConfirm: '确定要注销当前账号吗?',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: '公告详情'
|
||||
title: '公告详情',
|
||||
// 发布时间前缀
|
||||
publishTimePrefix: '发布时间:',
|
||||
}
|
||||
|
||||
@@ -2,5 +2,9 @@ export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: '公告列表',
|
||||
emptyText:"当前暂无更多信息",
|
||||
contentTitle:'升级公告'
|
||||
contentTitle:'升级公告',
|
||||
// 置顶标签
|
||||
topTag: '置顶',
|
||||
// 暂无公告
|
||||
noNotice: '暂无公告',
|
||||
}
|
||||
|
||||
@@ -33,5 +33,14 @@ export const lang = {
|
||||
// ====== 表单验证 ======
|
||||
validate: {
|
||||
pleaseSelectRefundReason: '请选择退款原因'
|
||||
}
|
||||
},
|
||||
|
||||
// ====== 退款方式弹窗 ======
|
||||
// pages_order/public/js/refundMethod.js 新增翻译项
|
||||
refund: {
|
||||
// 撤销之后本次申请将会关闭,如后续仍有问题可再次发起申请。
|
||||
cancelRefundContent: '撤销之后本次申请将会关闭,如后续仍有问题可再次发起申请。',
|
||||
// 暂不撤销
|
||||
cancelRefundCancelText: '暂不撤销',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: '充值记录',
|
||||
// recharge/order_list.vue 页面新增翻译项
|
||||
recharge: {
|
||||
// 充值成功
|
||||
rechargeSuccess: '充值成功',
|
||||
// 暂无充值记录
|
||||
noRecord: '暂无充值记录',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,27 @@
|
||||
export const lang = {
|
||||
//title为每个页面的标题
|
||||
title: '结构选型',
|
||||
// seal/structure.vue 页面新增翻译项
|
||||
structure: {
|
||||
// 当前选择前缀
|
||||
currentChoicePrefix: '当前选择:',
|
||||
// 请选择
|
||||
pleaseSelect: '请选择',
|
||||
// 选型结果前缀
|
||||
resultPrefix: '选型结果:',
|
||||
// 结果文件
|
||||
resultFile: '结果文件',
|
||||
// 请选择材质
|
||||
selectMaterial: '请选择材质',
|
||||
// 请选择结构
|
||||
selectStructure: '请选择结构',
|
||||
// 上传图片
|
||||
uploadImage: '上传图片',
|
||||
// 请上传图片
|
||||
pleaseUploadImage: '请上传图片',
|
||||
// 提交需求
|
||||
submitRequirement: '提交需求',
|
||||
// 提交成功
|
||||
submitSuccess: '提交成功',
|
||||
},
|
||||
}
|
||||
|
||||
119
p2-solution-plan.md
Normal file
119
p2-solution-plan.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# P2 动态 key 国际化处理方案
|
||||
|
||||
> 范围:审计报告 P2 章节列出的 8 处运行时拼接 `$lang()` key
|
||||
> 日期:2026-06-29
|
||||
> 结论:**所有已知取值目前在 zh-cn / en-us 中均已存在对应 key,核心问题是无法被静态扫描覆盖;已通过“枚举值兜底 + 运行时 fallback + 专项验证脚本”解决。**
|
||||
>
|
||||
> 实施状态:✅ 已完成(2026-06-29)
|
||||
|
||||
---
|
||||
|
||||
## 一、8 处动态 key 现状盘点
|
||||
|
||||
| # | 文件 | 调用方式 | 运行时取值 | 对应语言包 | key 是否齐全 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `components/payment/payment.vue:27` | `$lang('components.payment.' + item.type)` | `alipay`、`wechatpay` | `lang/xx/components/payment.js` | ✅ |
|
||||
| 2 | `components-diy/diy-tab.vue:907` | `$lang(title)`(仅当 title 含 `.`) | 由 DIY 数据或业务配置决定,常见如 `goods.list.xxx`、`member.index.xxx` | 取决于具体 title | ⚠️ 需数据侧保证 |
|
||||
| 3 | `pages_order/list.vue:15` | `$lang('status.' + statusItem.status)` | `all`、`waitpay`、`waitsend`、`waitconfirm`、`waitrate` | `lang/xx/order/list.js` 中 `status` 对象 | ✅ |
|
||||
| 4 | `pages_promotion/fenxiao/order.vue:6` | `$lang(item.nameKey)` | `all`、`pendingSettlement`、`settled`、`refunded` | `lang/xx/fenxiao/order.js` | ✅ |
|
||||
| 5 | `pages_promotion/fenxiao/team.vue:7` | `$lang(item.nameKey)` | `levelOne`、`levelTwo` | `lang/xx/fenxiao/team.js` | ✅ |
|
||||
| 6 | `pages_promotion/point/order_list.vue:8` | `$lang('point.orderList.' + statusItem.nameKey)` | `statusAll`、`statusPendingPayment`、`statusCompleted` | `lang/xx/point/order_list.js` | ✅ |
|
||||
| 7 | `pages_tool/goods/evaluate.vue:6` | `$lang(item.langKey)` | `tabAll`、`tabPositive`、`tabNeutral`、`tabNegative` | `lang/xx/goods/evaluate.js` | ✅ |
|
||||
| 8 | `pages_tool/pay/cashier.vue:16` | `$lang('pay.cashier.' + item.type)` | `alipay`、`wechatpay` | `lang/xx/pay/cashier.js` | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 二、核心风险分析
|
||||
|
||||
1. **静态扫描盲区**:`scripts/i18n-check.js` 只检查 zh-cn / en-us 两包 key 一致性,不会验证 `$lang('a.' + b)` 形式的 key 在运行时是否真实存在。
|
||||
2. **后端新增取值**:#1、#8 的 `item.type` 来自 `/api/pay/type`,理论上后端可能新增支付方式(如 `unionpay`、`applepay`),前端当前只处理了 `alipay` / `wechatpay`。
|
||||
3. **DIY 配置不可控**:#2 `diy-tab.vue` 的 title 来自装修组件数据,如果运营人员填写了含 `.` 的字符串且未配置对应 key,会显示 raw key 或空串。
|
||||
4. **本地枚举安全**:#3 ~ #7 的取值由前端本地数组硬编码,只要语言包同步就不会出现缺失;风险最低。
|
||||
|
||||
---
|
||||
|
||||
## 三、推荐处理方案
|
||||
|
||||
### 3.1 立即执行:为动态调用增加 fallback 兜底
|
||||
|
||||
对于能拿到原始中文/默认文案的场景,给 `$lang()` 增加第二参数作为兜底,避免生产环境出现 raw key 或空串。
|
||||
|
||||
| # | 文件 | 建议修改 |
|
||||
|---|---|---|
|
||||
| 1 | `components/payment/payment.vue:27` | `{{ $lang('components.payment.' + item.type, item.name) }}` |
|
||||
| 3 | `pages_order/list.vue:15` | `{{ $lang('status.' + statusItem.status, statusItem.name) }}` |
|
||||
| 8 | `pages_tool/pay/cashier.vue:16` | `{{ $lang('pay.cashier.' + item.type, item.name) }}` |
|
||||
|
||||
> 说明:`item.name` / `statusItem.name` 本身可能是中文,兜底仅在对应 key 缺失时生效,符合“宁可显示中文也不显示 raw key”的保守策略。
|
||||
|
||||
### 3.2 立即执行:DIY tab 组件兜底增强
|
||||
|
||||
`components-diy/diy-tab.vue:907` 已有对象型 title 的兜底逻辑,但字符串型 title 在 key 缺失时会返回 raw key。建议改为:
|
||||
|
||||
```js
|
||||
return this.$lang ? this.$lang(title, title) : title;
|
||||
```
|
||||
|
||||
这样当 key 不存在时,至少返回原始 title 字符串本身,不会暴露 raw key。
|
||||
|
||||
### 3.3 可选执行:扩展支付方式 key
|
||||
|
||||
在 `lang/zh-cn/components/payment.js`、`lang/en-us/components/payment.js`、`lang/zh-cn/pay/cashier.js`、`lang/en-us/pay/cashier.js` 中预置常见支付方式 key:
|
||||
|
||||
```js
|
||||
// components/payment.js
|
||||
unionpay: '银联支付',
|
||||
applepay: 'Apple Pay',
|
||||
// ...
|
||||
|
||||
// pay/cashier.js
|
||||
unionpay: '银联支付',
|
||||
applepay: 'Apple Pay',
|
||||
// ...
|
||||
```
|
||||
|
||||
这样即使后端新增支付方式,也不会出现 raw key。建议与产品/后端确认当前系统支持的全部支付方式后补充。
|
||||
|
||||
### 3.4 立即执行:建立 P2 专项验证脚本
|
||||
|
||||
新增 `scripts/verify-p2-keys.js`,枚举所有已知动态取值,模拟 resolver 检查是否能解析:
|
||||
|
||||
- 读取 `common/js/lang.js` 的 `normalizeSharedLangPack` / `normalizePageLangPack`。
|
||||
- 对 #1、#3、#4、#5、#6、#7、#8 的取值列表逐一验证。
|
||||
- 对 #2 的 diy-tab,收集项目内所有可能作为 title 传入的 i18n key(可通过扫描 `diy-tab` 调用方或装修数据 schema 获得),建立白名单。
|
||||
|
||||
脚本输出示例:
|
||||
|
||||
```text
|
||||
✅ components/payment.alipay => 支付宝支付
|
||||
✅ components/payment.wechatpay => 微信支付
|
||||
✅ status.waitpay => 待付款
|
||||
...
|
||||
```
|
||||
|
||||
### 3.5 长期建议:减少动态 key 模式
|
||||
|
||||
- **支付方式**:建议后端接口直接返回支付方式名称的 i18n key 或多语言 map,而不是让前端用 `type` 去拼 key。
|
||||
- **订单状态**:`pages_order/list.vue` 已经在 `statusList` 里做了 `name` 兜底,后续可考虑直接用 `statusItem.name` 渲染,不再走动态 key。
|
||||
- **分销/团队/积分 tab**:这些本地枚举 tab 可以直接写死 `$lang('xxx')`,不需要通过 `nameKey` / `langKey` 间接取值,虽然改动略多,但能彻底消除 P2 风险。
|
||||
|
||||
---
|
||||
|
||||
## 四、实施优先级
|
||||
|
||||
| 优先级 | 事项 | 预计改动量 | 收益 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| P0 | 为 #1、#3、#8 增加 `item.name` / `statusItem.name` fallback | 3 处模板 | 高:防止后端新增取值时暴露 raw key | ✅ 已完成 |
|
||||
| P0 | `diy-tab.vue` 改为 `this.$lang(title, title)` | 1 处 JS | 高:防止 DIY 配置错误导致 raw key | ✅ 已完成 |
|
||||
| P1 | 建立 `scripts/verify-p2-keys.js` 并接入 CI / pre-commit | 1 个脚本 | 高:持续守护动态 key | ✅ 已完成 |
|
||||
| P1 | 扩展支付方式 key(确认后端全部支持类型后) | 4 个语言包 | 中:提升扩展性 | ⏸️ 可选,待与后端确认 |
|
||||
| P2 | 逐步将本地枚举 tab 改为直接 `$lang()` 调用 | 5 ~ 6 个文件 | 低:彻底消除动态 key | ⏸️ 可选,收益较低 |
|
||||
|
||||
---
|
||||
|
||||
## 五、验证清单
|
||||
|
||||
- [x] 修改后 `npm run i18n:check` 通过。
|
||||
- [x] `node --check` 检查新增脚本通过;`.vue` 文件 script 块提取后语法检查通过。
|
||||
- [x] `scripts/verify-p2-keys.js` 输出全部 ✅。
|
||||
- [ ] 在英文环境下手动检查支付方式弹窗、订单列表 tab、分销/团队/积分 tab、商品评价 tab 显示正常(建议发布后人工复核)。
|
||||
@@ -67,6 +67,8 @@ export default {
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.config.text = this.$lang('goods.list.buyText');
|
||||
|
||||
this.$api.sendRequest({
|
||||
url: '/api/lucky/status',
|
||||
data: {},
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
:id="statusItem.id" :data-current="statusIndex" @click="ontabtap">
|
||||
<text class="uni-tab-item-title"
|
||||
:class="statusItem.status == orderStatus ? 'uni-tab-item-title-active color-base-text' : ''">
|
||||
{{ $lang('status.' + statusItem.status) }}
|
||||
{{ $lang('status.' + statusItem.status, statusItem.name) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -7,8 +7,8 @@ export default {
|
||||
*/
|
||||
cancleRefund(order_goods_id, callback) {
|
||||
uni.showModal({
|
||||
content: '撤销之后本次申请将会关闭,如后续仍有问题可再次发起申请。',
|
||||
cancelText: '暂不撤销',
|
||||
content: this.$lang('order.refund.cancelRefundContent'),
|
||||
cancelText: this.$lang('order.refund.cancelRefundCancelText'),
|
||||
cancelColor: '#898989',
|
||||
success: res => {
|
||||
if (res.confirm) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<image v-else :src="$util.img('public/uniapp/fenxiao/bill/withdraw.png')" mode="widthFix" show-menu-by-longpress></image>
|
||||
<view class="balances-info">
|
||||
<text>{{ item.type_name }}</text>
|
||||
<text>账单编号: {{ item.account_no }}</text>
|
||||
<text>{{ $lang('fenxiao.bill.billNoPrefix') }}{{ item.account_no }}</text>
|
||||
<text>{{ $util.timeStampTurnTime(item.create_time) }}</text>
|
||||
</view>
|
||||
<view class="balances-num">
|
||||
@@ -16,7 +16,7 @@
|
||||
item.money }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<ns-empty v-if="!accountList.length && showEmpty" text="暂无账单信息" :isIndex="false"></ns-empty>
|
||||
<ns-empty v-if="!accountList.length && showEmpty" :text="$lang('fenxiao.bill.noBill')" :isIndex="false"></ns-empty>
|
||||
</block>
|
||||
<loading-cover ref="loadingCover"></loading-cover>
|
||||
</mescroll-uni>
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
setTimeout(() => {
|
||||
if (!this.addonIsExist.fenxiao) {
|
||||
this.$util.showToast({
|
||||
title: '商家未开启分销',
|
||||
title: this.$lang('fenxiao.bill.addonClosed'),
|
||||
mask: true,
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
<!-- 空状态 -->
|
||||
<view v-else-if="!content" class="empty-state">
|
||||
<view class="empty-icon">📄</view>
|
||||
<text class="empty-title">暂无内容</text>
|
||||
<text class="empty-desc">该协议内容暂未设置</text>
|
||||
<button class="empty-button" @click="getcontent">重新加载</button>
|
||||
<text class="empty-title">{{ $lang('agreement.noContent') }}</text>
|
||||
<text class="empty-desc">{{ $lang('agreement.noContentDesc') }}</text>
|
||||
<button class="empty-button" @click="getcontent">{{ $lang('agreement.reload') }}</button>
|
||||
</view>
|
||||
|
||||
<!-- 内容状态 -->
|
||||
|
||||
@@ -12,24 +12,24 @@
|
||||
<!-- PDF 文件预览 -->
|
||||
<view v-if="fileType === 'pdf'" class="file-viewer">
|
||||
<web-view v-if="fileUrl" :src="fileUrl"></web-view>
|
||||
<view v-else class="error-message">文件地址不存在</view>
|
||||
<view v-else class="error-message">{{ $lang('filePreview.fileUrlNotExist') }}</view>
|
||||
</view>
|
||||
|
||||
<!-- Word 文件预览 -->
|
||||
<view v-else-if="fileType === 'word'" class="file-viewer">
|
||||
<web-view v-if="fileUrl" :src="fileUrl"></web-view>
|
||||
<view v-else class="error-message">文件地址不存在</view>
|
||||
<view v-else class="error-message">{{ $lang('filePreview.fileUrlNotExist') }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 视频文件预览 -->
|
||||
<view v-else-if="fileType === 'video'" class="video-viewer">
|
||||
<video v-if="fileUrl" :src="fileUrl" controls autoplay class="video-player"></video>
|
||||
<view v-else class="error-message">视频地址不存在</view>
|
||||
<view v-else class="error-message">{{ $lang('filePreview.videoUrlNotExist') }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 其他文件类型 -->
|
||||
<view v-else class="file-viewer">
|
||||
<view class="error-message">不支持的文件类型</view>
|
||||
<view class="error-message">{{ $lang('filePreview.unsupportedFileType') }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</uni-grid-item>
|
||||
</uni-grid>
|
||||
</view>
|
||||
<view v-if="brandList.length == 0"><ns-empty text="暂无更多品牌,去首页看看吧"></ns-empty></view>
|
||||
<view v-if="brandList.length == 0"><ns-empty :text="$lang('goods.brand.noMoreBrand')"></ns-empty></view>
|
||||
</block>
|
||||
</mescroll-uni>
|
||||
<loading-cover ref="loadingCover"></loading-cover>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user