Browse Source

翻译

main
Mac 2 weeks ago
parent
commit
627eb6e1a8
  1. 201
      app/screens/ProductDetailScreen.tsx
  2. 77
      app/screens/setting/SettingList.tsx
  3. 3
      app/services/api/productApi.ts
  4. 48
      app/utils/languageUtils.ts

201
app/screens/ProductDetailScreen.tsx

@ -16,7 +16,7 @@ import {
Platform,
StatusBar,
SafeAreaView,
Modal
Modal,
} from "react-native";
import fontSize from "../utils/fontsizeUtils";
import widthUtils from "../utils/widthUtils";
@ -29,13 +29,12 @@ import HeartRedIcon from "../components/HeartIconRed";
import ShareIcon from "../components/ShareIcon";
import { Image as ExpoImage, ImageBackground } from "expo-image";
import useUserStore from "../store/user";
import { getSubjectTransLanguage } from "../utils/languageUtils";
import { getSubjectTransLanguage,getSkuTransLanguage } from "../utils/languageUtils";
import { useTranslation } from "react-i18next";
import { getBurialPointData } from "../store/burialPoint";
import useBurialPointStore from "../store/burialPoint";
import * as ImagePicker from "expo-image-picker";
import * as FileSystem from "expo-file-system";
import {
productApi,
ProductDetailParams,
@ -91,18 +90,18 @@ export const ProductDetailScreen = () => {
// 遍历数据
res.skus.forEach((item) => {
item.attributes.forEach((attribute) => {
const { attribute_name, value } = attribute;
const { attribute_name_trans, value } = attribute;
// 如果结果对象中没有对应的属性名,则创建一个空数组
if (!result[attribute_name]) {
result[attribute_name] = [];
if (!result[attribute_name_trans]) {
result[attribute_name_trans] = [];
}
// 如果当前属性的值(value)已经存在于该组内,跳过
if (
!result[attribute_name].some(
!result[attribute_name_trans].some(
(existingAttribute: any) => existingAttribute.value === value
)
) {
result[attribute_name].push(attribute);
result[attribute_name_trans].push(attribute);
}
});
});
@ -128,7 +127,10 @@ export const ProductDetailScreen = () => {
// Add has_image to the list item
list.push({
attribute_name: attributeName,
has_image: withImage.length > 0, // If there are any items with images, set has_image to true
attribute_name_trans: attributeName,
attribute_name_trans_ar: attributeName,
attribute_name_trans_en: attributeName,
has_image: withImage.length > 0,
attributes: [...withImage, ...withoutImage],
});
}
@ -325,9 +327,9 @@ export const ProductDetailScreen = () => {
imageUrls.length > 5 ? imageUrls.slice(0, 5) : imageUrls;
setImageUrls(limitedImageUrls);
setGroupList(list);
const previousScreen = navigation.getState().routes[navigation.getState().index - 1];
const previousScreen =
navigation.getState().routes[navigation.getState().index - 1];
const data = {
offer_id: res.offer_id,
category_id: res.category_id,
@ -337,12 +339,9 @@ export const ProductDetailScreen = () => {
product_name: res.subject,
product_img: res.product_image_urls[0],
timestamp: new Date().toISOString(),
}
logViewProduct(data,previousScreen?.name as string);
};
logViewProduct(data, previousScreen?.name as string);
console.log(getBurialPointData());
} catch (error) {
console.error("Error fetching product details:", error);
} finally {
@ -389,7 +388,6 @@ export const ProductDetailScreen = () => {
navigation.navigate("Search");
}, [navigation]);
const handleCameraPress = useCallback(() => {
setShowImagePickerModal(true);
}, []);
@ -404,36 +402,35 @@ export const ProductDetailScreen = () => {
},
[navigation]
);
// Add this function to render skeleton UI
const renderSkeletonItems = () => {
// Create an array of 5 skeleton items
return Array(5).fill(0).map((_, index) => (
<View style={styles.productCard} key={`skeleton-${index}`}>
<View style={[styles.cardContainerWithPrice, styles.skeletonBox]} />
<View style={styles.priceContainerFlex}>
<View style={[styles.skeletonText, { width: 50 }]} />
<View style={[styles.skeletonText, { width: 30, marginLeft: 5 }]} />
return Array(5)
.fill(0)
.map((_, index) => (
<View style={styles.productCard} key={`skeleton-${index}`}>
<View style={[styles.cardContainerWithPrice, styles.skeletonBox]} />
<View style={styles.priceContainerFlex}>
<View style={[styles.skeletonText, { width: 50 }]} />
<View style={[styles.skeletonText, { width: 30, marginLeft: 5 }]} />
</View>
</View>
</View>
));
));
};
// 清理expo-image-picker临时文件
const cleanupImagePickerCache = async () => {
try {
// Skip cache cleanup on web platform
if (Platform.OS === 'web') {
console.log('Cache cleanup skipped on web platform');
if (Platform.OS === "web") {
console.log("Cache cleanup skipped on web platform");
setGalleryUsed(false);
return;
}
// 相册选择后清理临时缓存
const cacheDir = `${FileSystem.cacheDirectory}ImagePicker`;
await FileSystem.deleteAsync(cacheDir, { idempotent: true });
console.log("已清理ImagePicker缓存");
// 立即重置状态,无需用户干预
setGalleryUsed(false);
} catch (error) {
@ -442,12 +439,10 @@ export const ProductDetailScreen = () => {
setGalleryUsed(false);
}
};
// 处理从相册选择
const handleChooseFromGallery = useCallback(async () => {
console.log("handleChooseFromGallery");
setShowImagePickerModal(false);
// 等待模态窗关闭后再执行
setTimeout(async () => {
try {
@ -458,7 +453,6 @@ export const ProductDetailScreen = () => {
console.log("相册权限被拒绝");
return;
}
// 打开相册
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
@ -466,10 +460,8 @@ export const ProductDetailScreen = () => {
aspect: [4, 3],
quality: 1,
});
if (!result.canceled && result.assets && result.assets.length > 0) {
console.log("相册选择成功:", result.assets[0].uri);
await cleanupImagePickerCache();
navigation.navigate("ImageSearchResultScreen", {
image: result.assets[0].uri,
@ -483,12 +475,10 @@ export const ProductDetailScreen = () => {
}
}, 500);
}, [navigation, userStore.user]);
// 处理相机拍照
const handleTakePhoto = useCallback(async () => {
console.log("handleTakePhoto");
setShowImagePickerModal(false);
// 等待模态窗关闭后再执行
setTimeout(async () => {
try {
@ -498,17 +488,14 @@ export const ProductDetailScreen = () => {
console.log("相机权限被拒绝");
return;
}
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!result.canceled && result.assets && result.assets.length > 0) {
console.log("拍照成功:", result.assets[0].uri);
// 使用后清理缓存
await cleanupImagePickerCache();
navigation.navigate("ImageSearchResultScreen", {
@ -523,19 +510,15 @@ export const ProductDetailScreen = () => {
}
}, 500);
}, [navigation, userStore.user]);
// 重置应用状态函数
const resetAppState = useCallback(() => {
// 重置标记
setGalleryUsed(false);
// 清理缓存
cleanupImagePickerCache();
// 提示用户
Alert.alert("已重置", "现在您可以使用相机功能了");
}, []);
return (
<SafeAreaView style={styles.safeArea}>
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
@ -555,18 +538,17 @@ export const ProductDetailScreen = () => {
>
<BackIcon size={fontSize(20)} />
</TouchableOpacity>
<TouchableOpacity
style={styles.search}
<TouchableOpacity
style={styles.search}
onPress={handleSearchPress}
activeOpacity={0.7}
>
<View style={{ width: '100%', alignItems: 'flex-start' }}>
<View style={{ width: "100%", alignItems: "flex-start" }}>
<Text style={styles.searchText}>{t("search")}</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
style={styles.searchIcon}
onPress={() => {
@ -660,7 +642,8 @@ export const ProductDetailScreen = () => {
}}
>
<Text style={styles.activeIndexText}>
{activeIndex + 1}/{product?.product_image_urls?.length}
{activeIndex + 1}/
{product?.product_image_urls?.length}
</Text>
</View>
</View>
@ -693,9 +676,10 @@ export const ProductDetailScreen = () => {
{userStore.user?.vip_level > 0 && (
<>
<View style={styles.discountInfoContainer}>
<Text style={styles.emphasizedTextWidget}>-5%</Text>
<Text style={styles.emphasizedTextWidget}>
-5%
</Text>
</View>
<View style={styles.priceInfoVip}>
<ImageBackground
source={require("../../assets/img/vip1.png")}
@ -715,23 +699,28 @@ export const ProductDetailScreen = () => {
<View style={styles.blackThemeContainer}>
{groupList.map((item, index) =>
item.has_image ? (
<View key={item.attribute_name}>
<View key={item.attribute_name_trans}>
<Text style={styles.uniqueTextBlock}>
{item.attribute_name} :{" "}
{item.attribute_name_trans} :{" "}
{
item.attributes.find((item) => item.has_color)
?.value
// getSkuTransLanguage()
item.attributes.find(
(item) => item.has_color
)?.value
}
</Text>
<View style={styles.horizontalFlexContainer}>
{getDisplayAttributes(
item.attributes,
item.attribute_name
item.attribute_name_trans
).map((attribute) => (
<TouchableOpacity
key={attribute.value}
onPress={() =>
handleColorSelect(attribute.value, index)
handleColorSelect(
attribute.value,
index
)
}
style={[
styles.colorImageContainer,
@ -740,17 +729,19 @@ export const ProductDetailScreen = () => {
]}
>
<Image
source={{ uri: attribute.sku_image_url }}
source={{
uri: attribute.sku_image_url,
}}
style={styles.imageContainer}
/>
</TouchableOpacity>
))}
{!expandedGroups[item.attribute_name] &&
{!expandedGroups[item.attribute_name_trans] &&
item.attributes.length > 6 && (
<TouchableOpacity
style={styles.expandButton}
onPress={() =>
toggleExpand(item.attribute_name)
toggleExpand(item.attribute_name_trans)
}
>
<Text style={styles.expandButtonText}>
@ -758,11 +749,11 @@ export const ProductDetailScreen = () => {
</Text>
</TouchableOpacity>
)}
{expandedGroups[item.attribute_name] && (
{expandedGroups[item.attribute_name_trans] && (
<TouchableOpacity
style={styles.expandButton}
onPress={() =>
toggleExpand(item.attribute_name)
toggleExpand(item.attribute_name_trans)
}
>
<Text style={styles.expandButtonText}>
@ -793,14 +784,14 @@ export const ProductDetailScreen = () => {
)}
</View>
) : (
<View key={item.attribute_name}>
<View key={item.attribute_name_trans}>
<Text style={styles.uniqueTextBlock}>
{item.attribute_name}
{item.attribute_name_trans}
</Text>
<View style={styles.horizontalFlexContainer}>
{getDisplayAttributes(
item.attributes,
item.attribute_name
item.attribute_name_trans
).map((attribute) => (
<TouchableOpacity
key={attribute.value}
@ -824,12 +815,12 @@ export const ProductDetailScreen = () => {
</Text>
</TouchableOpacity>
))}
{!expandedGroups[item.attribute_name] &&
{!expandedGroups[item.attribute_name_trans] &&
item.attributes.length > 6 && (
<TouchableOpacity
style={styles.expandButton}
onPress={() =>
toggleExpand(item.attribute_name)
toggleExpand(item.attribute_name_trans)
}
>
<Text style={styles.expandButtonText}>
@ -837,11 +828,11 @@ export const ProductDetailScreen = () => {
</Text>
</TouchableOpacity>
)}
{expandedGroups[item.attribute_name] && (
{expandedGroups[item.attribute_name_trans] && (
<TouchableOpacity
style={styles.expandButton}
onPress={() =>
toggleExpand(item.attribute_name)
toggleExpand(item.attribute_name_trans)
}
>
<Text style={styles.expandButtonText}>
@ -888,31 +879,32 @@ export const ProductDetailScreen = () => {
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.productGridContainer}
>
{isSimilarsFlag ?
similars?.map((item) => (
<TouchableOpacity
style={styles.productCard}
key={item.offer_id}
onPress={() => handleProductPress(item)}
>
<View style={styles.cardContainerWithPrice}>
<Image
source={{ uri: item.product_image_urls[0] }}
style={styles.imageContainerCompact}
/>
</View>
<View style={styles.priceContainerFlex}>
<Text style={styles.highlightedText1}>
{item.max_price}
</Text>
<Text style={styles.highlightedTextWithBorder}>
FCFA
</Text>
</View>
</TouchableOpacity>
))
: renderSkeletonItems()
}
{isSimilarsFlag
? similars?.map((item) => (
<TouchableOpacity
style={styles.productCard}
key={item.offer_id}
onPress={() => handleProductPress(item)}
>
<View style={styles.cardContainerWithPrice}>
<Image
source={{ uri: item.product_image_urls[0] }}
style={styles.imageContainerCompact}
/>
</View>
<View style={styles.priceContainerFlex}>
<Text style={styles.highlightedText1}>
{item.max_price}
</Text>
<Text
style={styles.highlightedTextWithBorder}
>
FCFA
</Text>
</View>
</TouchableOpacity>
))
: renderSkeletonItems()}
</ScrollView>
</View>
<View style={{ width: "100%" }}>
@ -962,7 +954,7 @@ export const ProductDetailScreen = () => {
)}
</View>
</View>
{/* Image Picker Modal */}
<Modal
visible={showImagePickerModal}
@ -994,16 +986,13 @@ export const ProductDetailScreen = () => {
<Text style={styles.imagePickerText}></Text>
</TouchableOpacity>
)}
<View style={styles.imagePickerDivider} />
<TouchableOpacity
style={styles.imagePickerOption}
onPress={handleChooseFromGallery}
>
<Text style={styles.imagePickerText}></Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.imagePickerCancelButton}
onPress={() => setShowImagePickerModal(false)}
@ -1019,15 +1008,15 @@ export const ProductDetailScreen = () => {
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#fff',
backgroundColor: "#fff",
},
safeAreaContent: {
flex: 1,
paddingTop: Platform.OS === 'android' ? 0 : 0,
paddingTop: Platform.OS === "android" ? 0 : 0,
},
container: {
flex: 1,
backgroundColor: '#fff',
backgroundColor: "#fff",
},
scrollView: {
flex: 1,
@ -1306,7 +1295,7 @@ const styles = StyleSheet.create({
fontSize: fontSize(18),
color: "#f1c355",
textAlign: "center",
marginLeft:2
marginLeft: 2,
},
emphasizedTextVip: {
fontStyle: "italic",
@ -1733,14 +1722,14 @@ const styles = StyleSheet.create({
color: "#666",
},
skeletonBox: {
backgroundColor: '#e0e0e0',
backgroundColor: "#e0e0e0",
height: widthUtils(90, 90).height,
width: widthUtils(90, 90).width,
borderRadius: 5,
},
skeletonText: {
height: 16,
backgroundColor: '#e0e0e0',
backgroundColor: "#e0e0e0",
borderRadius: 3,
},
imagePickerOverlay: {

77
app/screens/setting/SettingList.tsx

@ -1,4 +1,12 @@
import { View, Text, StyleSheet, TouchableOpacity, SafeAreaView, StatusBar, Platform } from "react-native";
import {
View,
Text,
StyleSheet,
TouchableOpacity,
SafeAreaView,
StatusBar,
Platform,
} from "react-native";
import BackIcon from "../../components/BackIcon";
import fontSize from "../../utils/fontsizeUtils";
import LeftArrowIcon from "../../components/DownArrowIcon";
@ -8,24 +16,26 @@ import { useState, useEffect } from "react";
import { settingApi, MySetting } from "../../services/api/setting";
import { RootStackParamList } from "../../navigation/types";
import { eventBus } from "../../utils/eventBus";
import AsyncStorage from '@react-native-async-storage/async-storage';
import AsyncStorage from "@react-native-async-storage/async-storage";
import { useTranslation } from "react-i18next";
import useUserStore from "../../store/user";
export const SettingList = () => {
const { t } = useTranslation();
const [mySetting, setMySetting] = useState<MySetting>();
const { user } = useUserStore();
const getMySetting = async () => {
const res = await settingApi.getMySetting()
console.log("MySetting:");
console.log(res);
const res = await settingApi.getMySetting();
setMySetting(res);
}
};
useEffect(() => {
getMySetting();
const refreshSetting = () => {
if (user?.user_id) {
getMySetting();
}
const refreshSetting = () => {
getMySetting();
};
eventBus.on("refreshSetting", refreshSetting);
return () => {
eventBus.off("refreshSetting", refreshSetting);
@ -39,15 +49,13 @@ export const SettingList = () => {
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
<View style={styles.safeAreaContent}>
<View style={styles.header}>
<TouchableOpacity
onPress={() => navigation.goBack()}
>
<TouchableOpacity onPress={() => navigation.goBack()}>
<BackIcon size={fontSize(24)} />
</TouchableOpacity>
<Text style={styles.headerTitle}>{t("settings.title")}</Text>
<View style={styles.placeholder} />
</View>
<View style={styles.content}>
<View style={styles.item}>
<Text>{t("settings.profile")}</Text>
@ -70,19 +78,19 @@ export const SettingList = () => {
</View>
<View style={styles.content}>
<TouchableOpacity
onPress={() => {
if (mySetting?.language && mySetting?.currency) {
navigation.navigate("MyAddress");
}
}}
style={styles.item}
>
<Text>{t("settings.my_address")}</Text>
<Text>
<LeftArrowIcon size={fontSize(20)} color="#acacac" />
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
if (mySetting?.language && mySetting?.currency) {
navigation.navigate("MyAddress");
}
}}
style={styles.item}
>
<Text>{t("settings.my_address")}</Text>
<Text>
<LeftArrowIcon size={fontSize(20)} color="#acacac" />
</Text>
</TouchableOpacity>
<View style={styles.item}>
<Text>{t("settings.feedback")}</Text>
<Text>
@ -102,10 +110,13 @@ export const SettingList = () => {
</Text>
</View>
<TouchableOpacity style={styles.item} onPress={() => {
AsyncStorage.clear();
navigation.navigate("CountrySelect");
}}>
<TouchableOpacity
style={styles.item}
onPress={() => {
AsyncStorage.clear();
navigation.navigate("CountrySelect");
}}
>
<Text>{t("settings.clear_cache")}</Text>
<Text>
<LeftArrowIcon size={fontSize(20)} color="#acacac" />
@ -115,9 +126,9 @@ export const SettingList = () => {
<View>
<TouchableOpacity
onPress={() => {
// if (mySetting?.language && mySetting?.currency) {
navigation.navigate("CountrySetting", { mySetting });
// }
// if (mySetting?.language && mySetting?.currency) {
navigation.navigate("CountrySetting", { mySetting });
// }
}}
style={styles.item}
>
@ -136,7 +147,7 @@ export const SettingList = () => {
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#fff',
backgroundColor: "#fff",
},
safeAreaContent: {
flex: 1,

3
app/services/api/productApi.ts

@ -178,6 +178,9 @@ export type Products = Product[]
export interface ProductGroupList {
attribute_name:string,
has_image:boolean,
attribute_name_trans:string,
attribute_name_trans_ar:string,
attribute_name_trans_en:string,
attributes:SkuAttribute[],
value?:string
}

48
app/utils/languageUtils.ts

@ -30,28 +30,28 @@ export const getSubjectTransLanguage = <T extends Record<string, any>>(data: T):
// export const getSkuTransLanguage = <T extends Record<string, any>>(data: T): string => {
// // 获取当前i18n语言
// const currentLang = getCurrentLanguage();
export const getSkuTransLanguage = <T extends Record<string, any>>(data: T): string => {
// 获取当前i18n语言
const currentLang = getCurrentLanguage();
// // 特殊处理中文
// if (currentLang === 'zh' && 'value' in data) {
// return data.value as string;
// }
// // 获取所有subject_trans开头的字段
// const translationFields = Object.keys(data).filter(key =>
// key.startsWith('value_trans')
// );
// // 查找匹配的字段
// const matchedField = translationFields.find(field => {
// // 从字段名中提取语言代码
// const langCode = field.replace('value_trans_', '');
// // 如果没有后缀,则为法语
// return langCode === '' ? currentLang === 'fr' : langCode === currentLang;
// });
// // 返回匹配的翻译值,如果没有匹配则返回法语
// return (data[matchedField || 'value_trans'] as string) || '';
// };
// 特殊处理中文
if (currentLang === 'zh' && 'value' in data) {
return data.value as string;
}
// 获取所有subject_trans开头的字段
const translationFields = Object.keys(data).filter(key =>
key.startsWith('value_trans')
);
// 查找匹配的字段
const matchedField = translationFields.find(field => {
// 从字段名中提取语言代码
const langCode = field.replace('value_trans_', '');
// 如果没有后缀,则为法语
return langCode === '' ? currentLang === 'fr' : langCode === currentLang;
});
// 返回匹配的翻译值,如果没有匹配则返回法语
return (data[matchedField || 'value_trans'] as string) || '';
};

Loading…
Cancel
Save