feat: 路由
9
app.json
@ -16,7 +16,8 @@
|
|||||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"edgeToEdgeEnabled": true
|
"edgeToEdgeEnabled": true,
|
||||||
|
"package": "com.jinyaqiu.memowake"
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"bundler": "metro",
|
"bundler": "metro",
|
||||||
@ -37,6 +38,12 @@
|
|||||||
],
|
],
|
||||||
"experiments": {
|
"experiments": {
|
||||||
"typedRoutes": true
|
"typedRoutes": true
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"router": {},
|
||||||
|
"eas": {
|
||||||
|
"projectId": "e4634b8b-fdb8-4e6f-ac7e-7032e6898612"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,17 +29,10 @@ export default function TabLayout() {
|
|||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="index"
|
name="index"
|
||||||
options={{
|
options={{
|
||||||
title: 'Home',
|
title: 'Memo',
|
||||||
tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
|
tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
|
||||||
name="explore"
|
|
||||||
options={{
|
|
||||||
title: 'Explore',
|
|
||||||
tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,110 +0,0 @@
|
|||||||
import { Image } from 'expo-image';
|
|
||||||
import { Platform, StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
import { Collapsible } from '@/components/Collapsible';
|
|
||||||
import { ExternalLink } from '@/components/ExternalLink';
|
|
||||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
|
||||||
import { ThemedText } from '@/components/ThemedText';
|
|
||||||
import { ThemedView } from '@/components/ThemedView';
|
|
||||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
|
||||||
|
|
||||||
export default function TabTwoScreen() {
|
|
||||||
return (
|
|
||||||
<ParallaxScrollView
|
|
||||||
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
|
|
||||||
headerImage={
|
|
||||||
<IconSymbol
|
|
||||||
size={310}
|
|
||||||
color="#808080"
|
|
||||||
name="chevron.left.forwardslash.chevron.right"
|
|
||||||
style={styles.headerImage}
|
|
||||||
/>
|
|
||||||
}>
|
|
||||||
<ThemedView style={styles.titleContainer}>
|
|
||||||
<ThemedText type="title">Explore</ThemedText>
|
|
||||||
</ThemedView>
|
|
||||||
<ThemedText>This app includes example code to help you get started.</ThemedText>
|
|
||||||
<Collapsible title="File-based routing">
|
|
||||||
<ThemedText>
|
|
||||||
This app has two screens:{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
|
|
||||||
</ThemedText>
|
|
||||||
<ThemedText>
|
|
||||||
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
|
|
||||||
sets up the tab navigator.
|
|
||||||
</ThemedText>
|
|
||||||
<ExternalLink href="https://docs.expo.dev/router/introduction">
|
|
||||||
<ThemedText type="link">Learn more</ThemedText>
|
|
||||||
</ExternalLink>
|
|
||||||
</Collapsible>
|
|
||||||
<Collapsible title="Android, iOS, and web support">
|
|
||||||
<ThemedText>
|
|
||||||
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
|
|
||||||
</ThemedText>
|
|
||||||
</Collapsible>
|
|
||||||
<Collapsible title="Images">
|
|
||||||
<ThemedText>
|
|
||||||
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
|
|
||||||
different screen densities
|
|
||||||
</ThemedText>
|
|
||||||
<Image source={require('@/assets/images/react-logo.png')} style={{ alignSelf: 'center' }} />
|
|
||||||
<ExternalLink href="https://reactnative.dev/docs/images">
|
|
||||||
<ThemedText type="link">Learn more</ThemedText>
|
|
||||||
</ExternalLink>
|
|
||||||
</Collapsible>
|
|
||||||
<Collapsible title="Custom fonts">
|
|
||||||
<ThemedText>
|
|
||||||
Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{' '}
|
|
||||||
<ThemedText style={{ fontFamily: 'SpaceMono' }}>
|
|
||||||
custom fonts such as this one.
|
|
||||||
</ThemedText>
|
|
||||||
</ThemedText>
|
|
||||||
<ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
|
|
||||||
<ThemedText type="link">Learn more</ThemedText>
|
|
||||||
</ExternalLink>
|
|
||||||
</Collapsible>
|
|
||||||
<Collapsible title="Light and dark mode components">
|
|
||||||
<ThemedText>
|
|
||||||
This template has light and dark mode support. The{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
|
|
||||||
what the user's current color scheme is, and so you can adjust UI colors accordingly.
|
|
||||||
</ThemedText>
|
|
||||||
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
|
|
||||||
<ThemedText type="link">Learn more</ThemedText>
|
|
||||||
</ExternalLink>
|
|
||||||
</Collapsible>
|
|
||||||
<Collapsible title="Animations">
|
|
||||||
<ThemedText>
|
|
||||||
This template includes an example of an animated component. The{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
|
|
||||||
the powerful <ThemedText type="defaultSemiBold">react-native-reanimated</ThemedText>{' '}
|
|
||||||
library to create a waving hand animation.
|
|
||||||
</ThemedText>
|
|
||||||
{Platform.select({
|
|
||||||
ios: (
|
|
||||||
<ThemedText>
|
|
||||||
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
|
|
||||||
component provides a parallax effect for the header image.
|
|
||||||
</ThemedText>
|
|
||||||
),
|
|
||||||
})}
|
|
||||||
</Collapsible>
|
|
||||||
</ParallaxScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
headerImage: {
|
|
||||||
color: '#808080',
|
|
||||||
bottom: -90,
|
|
||||||
left: -35,
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
titleContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
gap: 8,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -1,75 +1,82 @@
|
|||||||
import { Image } from 'expo-image';
|
|
||||||
import { Platform, StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
import { HelloWave } from '@/components/HelloWave';
|
|
||||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
|
||||||
import { ThemedText } from '@/components/ThemedText';
|
import { ThemedText } from '@/components/ThemedText';
|
||||||
import { ThemedView } from '@/components/ThemedView';
|
import { ThemedView } from '@/components/ThemedView';
|
||||||
|
import { useAuth } from '@/contexts/auth-context';
|
||||||
|
import { fetchApi } from '@/lib/server-api-util';
|
||||||
|
import { store } from '@/store';
|
||||||
|
import { User } from '@/types/user';
|
||||||
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { TouchableOpacity, View } from 'react-native';
|
||||||
|
|
||||||
export default function HomeScreen() {
|
export default function HomeScreen() {
|
||||||
|
const { login } = useAuth();
|
||||||
|
const token = store.getState().auth.token;
|
||||||
|
console.log(token);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const handleApi = () => {
|
||||||
|
fetchApi('/iam/login/password-login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
account: "jinyaqiu@fairclip.cn",
|
||||||
|
password: "111111",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
login(data as User, data.access_token || '')
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err));
|
||||||
|
// fetch('http://192.168.31.42/api/v1/iam/login/password-login', {
|
||||||
|
// method: 'POST',
|
||||||
|
// headers: {
|
||||||
|
// 'Content-Type': 'application/json',
|
||||||
|
// },
|
||||||
|
// body: JSON.stringify({
|
||||||
|
// account: "jinyaqiu@fairclip.cn",
|
||||||
|
// password: "111111",
|
||||||
|
// }),
|
||||||
|
// })
|
||||||
|
// .then((res) => res.json())
|
||||||
|
// .then((data) => console.log(data))
|
||||||
|
// .catch((err) => console.log(err));
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<ParallaxScrollView
|
<ThemedView className="flex-1 bg-beige">
|
||||||
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
|
<View className="flex-1 items-center pt-[60px] px-5">
|
||||||
headerImage={
|
<ThemedText className="text-3xl font-bold mb-8 text-saddlebrown" onPress={handleApi}>
|
||||||
<Image
|
{t('title', { ns: "example" })}
|
||||||
source={require('@/assets/images/partial-react-logo.png')}
|
|
||||||
style={styles.reactLogo}
|
|
||||||
/>
|
|
||||||
}>
|
|
||||||
<ThemedView style={styles.titleContainer}>
|
|
||||||
<ThemedText type="title">Welcome!</ThemedText>
|
|
||||||
<HelloWave />
|
|
||||||
</ThemedView>
|
|
||||||
<ThemedView style={styles.stepContainer}>
|
|
||||||
<ThemedText type="subtitle">Step 1: Try it</ThemedText>
|
|
||||||
<ThemedText>
|
|
||||||
Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes.
|
|
||||||
Press{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">
|
|
||||||
{Platform.select({
|
|
||||||
ios: 'cmd + d',
|
|
||||||
android: 'cmd + m',
|
|
||||||
web: 'F12',
|
|
||||||
})}
|
|
||||||
</ThemedText>{' '}
|
|
||||||
to open developer tools.
|
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
</ThemedView>
|
|
||||||
<ThemedView style={styles.stepContainer}>
|
<View className="mb-8 items-center">
|
||||||
<ThemedText type="subtitle">Step 2: Explore</ThemedText>
|
<View className="w-[150px] h-[150px] rounded-full bg-white/80 justify-center items-center border-8 border-saddlebrown shadow-lg">
|
||||||
<ThemedText>
|
<View className="items-center">
|
||||||
{`Tap the Explore tab to learn more about what's included in this starter app.`}
|
<Ionicons name="person" size={60} color="#8B4513" />
|
||||||
|
<ThemedText className="mt-1 text-lg font-semibold text-saddlebrown">
|
||||||
|
MeMo
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
</ThemedView>
|
</View>
|
||||||
<ThemedView style={styles.stepContainer}>
|
</View>
|
||||||
<ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText>
|
</View>
|
||||||
<ThemedText>
|
|
||||||
{`When you're ready, run `}
|
<ThemedText className="text-base text-center mb-10 text-saddlebrown px-5 leading-6">
|
||||||
<ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '}
|
Ready to wake up your memories? Just ask! Let MeMo bring them back to life!
|
||||||
<ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">app</ThemedText> to{' '}
|
|
||||||
<ThemedText type="defaultSemiBold">app-example</ThemedText>.
|
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
className="w-20 h-20 rounded-full bg-white/90 justify-center items-center mb-8 shadow-lg"
|
||||||
|
>
|
||||||
|
<Ionicons name="mic" size={32} color="#8B0000" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View className="w-full items-center mt-auto mb-8">
|
||||||
|
<View className="h-px w-4/5 bg-saddlebrown/50 mb-4" />
|
||||||
|
<ThemedText className="text-center text-saddlebrown text-sm">
|
||||||
|
<ThemedText className="font-bold text-darkred">Explore More</ThemedText> Memory Reel Made Just For You!
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</ThemedView >
|
</ThemedView >
|
||||||
</ParallaxScrollView>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
titleContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 8,
|
|
||||||
},
|
|
||||||
stepContainer: {
|
|
||||||
gap: 8,
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
reactLogo: {
|
|
||||||
height: 178,
|
|
||||||
width: 290,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
@ -1,28 +1,22 @@
|
|||||||
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||||
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
||||||
import { useFonts } from 'expo-font';
|
|
||||||
import { Stack } from 'expo-router';
|
import { Stack } from 'expo-router';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import 'react-native-reanimated';
|
import 'react-native-reanimated';
|
||||||
|
import '../global.css';
|
||||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
import { Provider } from "../provider";
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
const [loaded] = useFonts({
|
|
||||||
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!loaded) {
|
|
||||||
// Async font loading only occurs in development.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||||
|
<Provider>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="+not-found" />
|
<Stack.Screen name="+not-found" />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Provider>
|
||||||
<StatusBar style="auto" />
|
<StatusBar style="auto" />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
3
assets/icons/svg/activity.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-activity-icon lucide-activity">
|
||||||
|
<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 381 B |
6
assets/icons/svg/ai-video.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ai-icon lucide-ai">
|
||||||
|
<path d="M20.2 6 3 11l-.9-2.4c-.3-1.1.3-2.2 1.3-2.5l13.5-4c1.1-.3 2.2.3 2.5 1.3Z" />
|
||||||
|
<path d="m6.2 5.3 3.1 3.9" />
|
||||||
|
<path d="m12.4 3.4 3.1 4" />
|
||||||
|
<path d="M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 444 B |
4
assets/icons/svg/album.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-album-icon lucide-album">
|
||||||
|
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
|
||||||
|
<polyline points="11 3 11 11 14 8 17 11 17 3" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 350 B |
1
assets/icons/svg/arrow-big-left.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-big-left-icon lucide-arrow-big-left"><path d="M18 15h-6v4l-7-7 7-7v4h6v6z"/></svg>
|
||||||
|
After Width: | Height: | Size: 290 B |
4
assets/icons/svg/arrow-right-long.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5 12H19M19 12L12 5M19 12L12 19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 274 B |
4
assets/icons/svg/arrow-right-rotate.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5 12H19M19 12L12 5M19 12L12 19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 274 B |
3
assets/icons/svg/arrow-right.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="9 18 15 12 9 6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 230 B |
1
assets/icons/svg/back-arrow.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-left-icon lucide-chevron-left"><path d="m15 18-6-6 6-6"/></svg>
|
||||||
|
After Width: | Height: | Size: 273 B |
4
assets/icons/svg/badge-check.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-badge-check-icon lucide-badge-check">
|
||||||
|
<path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" />
|
||||||
|
<path d="m9 12 2 2 4-4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 455 B |
9
assets/icons/svg/bot-off.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot-off-icon lucide-bot-off">
|
||||||
|
<path d="M13.67 8H18a2 2 0 0 1 2 2v4.33" />
|
||||||
|
<path d="M2 14h2" />
|
||||||
|
<path d="M20 14h2" />
|
||||||
|
<path d="M22 22 2 2" />
|
||||||
|
<path d="M8 8H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 1.414-.586" />
|
||||||
|
<path d="M9 13v2" />
|
||||||
|
<path d="M9.67 4H12v2.33" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 502 B |
17
assets/icons/svg/brain-cog.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-brain-cog-icon lucide-brain-cog">
|
||||||
|
<path d="m10.852 14.772-.383.923" />
|
||||||
|
<path d="m10.852 9.228-.383-.923" />
|
||||||
|
<path d="m13.148 14.772.382.924" />
|
||||||
|
<path d="m13.531 8.305-.383.923" />
|
||||||
|
<path d="m14.772 10.852.923-.383" />
|
||||||
|
<path d="m14.772 13.148.923.383" />
|
||||||
|
<path d="M17.598 6.5A3 3 0 1 0 12 5a3 3 0 0 0-5.63-1.446 3 3 0 0 0-.368 1.571 4 4 0 0 0-2.525 5.771" />
|
||||||
|
<path d="M17.998 5.125a4 4 0 0 1 2.525 5.771" />
|
||||||
|
<path d="M19.505 10.294a4 4 0 0 1-1.5 7.706" />
|
||||||
|
<path d="M4.032 17.483A4 4 0 0 0 11.464 20c.18-.311.892-.311 1.072 0a4 4 0 0 0 7.432-2.516" />
|
||||||
|
<path d="M4.5 10.291A4 4 0 0 0 6 18" />
|
||||||
|
<path d="M6.002 5.125a3 3 0 0 0 .4 1.375" />
|
||||||
|
<path d="m9.228 10.852-.923-.383" />
|
||||||
|
<path d="m9.228 13.148-.923.383" />
|
||||||
|
<circle cx="12" cy="12" r="3" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1009 B |
3
assets/icons/svg/check.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 278 B |
4
assets/icons/svg/chevron-left.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor">
|
||||||
|
<path d="m15 18-6-6 6-6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 257 B |
3
assets/icons/svg/close.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 221 B |
1
assets/icons/svg/cloud-upload.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-upload-icon lucide-cloud-upload"><path d="M12 13v8"/><path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"/><path d="m8 17 4-4 4 4"/></svg>
|
||||||
|
After Width: | Height: | Size: 360 B |
5
assets/icons/svg/customer-service.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user-circle-icon lucide-user-circle">
|
||||||
|
<path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z" />
|
||||||
|
<path d="m10 15-3-3 3-3" />
|
||||||
|
<path d="M7 12h7a2 2 0 0 1 2 2v1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 368 B |
9
assets/icons/svg/decorative.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg viewBox="0 0 63 55" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="38.7098" y="17.6353" width="8.3012" height="37.3927" rx="3" transform="rotate(58.8959 38.7098 17.6353)" stroke="#FFBB00" stroke-width="2" />
|
||||||
|
<line x1="30.0343" y1="21.7041" x2="35.3559" y2="30.5243" stroke="#FFBB00" stroke-width="2" />
|
||||||
|
<path d="M41.8246 30.6299L45.5694 36.8367" stroke="#FFBB00" stroke-width="2" stroke-linecap="round" />
|
||||||
|
<path d="M29.4093 10.0507L33.1541 16.2575" stroke="#FFBB00" stroke-width="2" stroke-linecap="round" />
|
||||||
|
<path d="M40.8665 6.25525L40.0611 14.0031" stroke="#FFBB00" stroke-width="2" stroke-linecap="round" />
|
||||||
|
<path d="M53.9637 27.9646L46.813 24.947" stroke="#FFBB00" stroke-width="2" stroke-linecap="round" />
|
||||||
|
<path d="M54.1488 13.3939L46.3335 18.1092" stroke="#FFBB00" stroke-width="2" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 866 B |
3
assets/icons/svg/delete.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 325 B |
5
assets/icons/svg/download.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M11.3694 8.14966V10.5459C11.3694 10.8636 11.2474 11.1684 11.0302 11.393C10.8131 11.6177 10.5185 11.744 10.2114 11.744H2.10528C1.79816 11.744 1.50361 11.6177 1.28644 11.393C1.06927 11.1684 0.947266 10.8636 0.947266 10.5459V8.14966" stroke="currentColor" stroke-width="1.15531" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3.26416 5.1543L6.1592 8.14955L9.05423 5.1543" stroke="currentColor" stroke-width="1.15531" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6.16064 8.14953V0.960938" stroke="currentColor" stroke-width="1.15531" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 723 B |
5
assets/icons/svg/eye.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.625 7.5C0.625 7.5 3.125 2.5 7.5 2.5C11.875 2.5 14.375 7.5 14.375 7.5C14.375 7.5 11.875 12.5 7.5 12.5C3.125 12.5 0.625 7.5 0.625 7.5Z" stroke="currentColor" stroke-width="1.16" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.5 9.375C8.53553 9.375 9.375 8.53553 9.375 7.5C9.375 6.46447 8.53553 5.625 7.5 5.625C6.46447 5.625 5.625 6.46447 5.625 7.5C5.625 8.53553 6.46447 9.375 7.5 9.375Z" stroke="currentColor" stroke-width="1.16" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 648 B |
3
assets/icons/svg/file.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 345 B |
5
assets/icons/svg/gallery.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-image-icon lucide-image">
|
||||||
|
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
|
||||||
|
<circle cx="9" cy="9" r="2" />
|
||||||
|
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
7
assets/icons/svg/image-plus.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-image-plus-icon lucide-image-plus">
|
||||||
|
<path d="M16 5h6" />
|
||||||
|
<path d="M19 2v6" />
|
||||||
|
<path d="M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5" />
|
||||||
|
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
|
||||||
|
<circle cx="9" cy="9" r="2" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 468 B |
5
assets/icons/svg/logout.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
||||||
|
<polyline points="16 17 21 12 16 7" />
|
||||||
|
<line x1="21" y1="12" x2="9" y2="12" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 333 B |
1
assets/icons/svg/memory-data.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-computer-icon lucide-computer"><rect width="14" height="8" x="5" y="2" rx="2"/><rect width="20" height="8" x="2" y="14" rx="2"/><path d="M6 18h2"/><path d="M12 18h6"/></svg>
|
||||||
|
After Width: | Height: | Size: 375 B |
5
assets/icons/svg/phone.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-vibrate-icon lucide-vibrate">
|
||||||
|
<path d="m2 8 2 2-2 2 2 2-2 2" />
|
||||||
|
<path d="m22 8-2 2 2 2-2 2 2 2" />
|
||||||
|
<rect width="8" height="14" x="8" y="5" rx="1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 370 B |
1
assets/icons/svg/photo-count.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" className="lucide lucide-image-icon lucide-image"><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /><circle cx="9" cy="9" r="2" /><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" /></svg>
|
||||||
|
After Width: | Height: | Size: 378 B |
3
assets/icons/svg/play.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="22" height="22" viewBox="0 0 22 22">
|
||||||
|
<polygon points="6,4 18,11 6,18" fill="#fff" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 108 B |
3
assets/icons/svg/plus.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 233 B |
5
assets/icons/svg/recommend.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-share-2-icon lucide-share-2">
|
||||||
|
<path d="M13.5 19.5 12 21l-7-7c-1.5-1.45-3-3.2-3-5.5A5.5 5.5 0 0 1 7.5 3c1.76 0 3 .5 4.5 2 1.5-1.5 2.74-2 4.5-2a5.5 5.5 0 0 1 5.402 6.5" />
|
||||||
|
<path d="M15 15h6" />
|
||||||
|
<path d="M18 12v6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 435 B |
4
assets/icons/svg/send.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send-icon lucide-send">
|
||||||
|
<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z" />
|
||||||
|
<path d="m21.854 2.147-10.94 10.939" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 422 B |
1
assets/icons/svg/settings.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cog-icon lucide-cog"><path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"/><path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/><path d="M12 2v2"/><path d="M12 22v-2"/><path d="m17 20.66-1-1.73"/><path d="M11 10.27 7 3.34"/><path d="m20.66 17-1.73-1"/><path d="m3.34 7 1.73 1"/><path d="M14 12h8"/><path d="M2 12h2"/><path d="m20.66 7-1.73 1"/><path d="m3.34 17 1.73-1"/><path d="m17 3.34-1 1.73"/><path d="m11 13.73-4 6.93"/></svg>
|
||||||
|
After Width: | Height: | Size: 623 B |
7
assets/icons/svg/share.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg width="26" height="28" viewBox="0 0 26 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20.4237 9.10858C22.4634 9.10858 24.1169 7.45507 24.1169 5.41537C24.1169 3.37567 22.4634 1.72217 20.4237 1.72217C18.384 1.72217 16.7305 3.37567 16.7305 5.41537C16.7305 7.45507 18.384 9.10858 20.4237 9.10858Z" stroke="currentColor" stroke-width="2.46215" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5.65317 17.728C7.69287 17.728 9.34637 16.0745 9.34637 14.0348C9.34637 11.9951 7.69287 10.3416 5.65317 10.3416C3.61347 10.3416 1.95996 11.9951 1.95996 14.0348C1.95996 16.0745 3.61347 17.728 5.65317 17.728Z" stroke="currentColor" stroke-width="2.46215" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M20.4237 26.3434C22.4634 26.3434 24.1169 24.6899 24.1169 22.6502C24.1169 20.6105 22.4634 18.957 20.4237 18.957C18.384 18.957 16.7305 20.6105 16.7305 22.6502C16.7305 24.6899 18.384 26.3434 20.4237 26.3434Z" stroke="currentColor" stroke-width="2.46215" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8.84277 15.8914L17.251 20.791" stroke="currentColor" stroke-width="2.46215" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M17.2387 7.27588L8.84277 12.1755" stroke="currentColor" stroke-width="2.46215" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
4
assets/icons/svg/shield-check.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shield-check-icon lucide-shield-check">
|
||||||
|
<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />
|
||||||
|
<path d="m9 12 2 2 4-4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 460 B |
6
assets/icons/svg/star-x.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<line x1="4.72803" y1="12.7279" x2="20.728" y2="12.7279" stroke="#FFBB00" stroke-width="2" stroke-linecap="round" />
|
||||||
|
<line x1="12.728" y1="4.72791" x2="12.728" y2="20.7279" stroke="#FFBB00" stroke-width="2" stroke-linecap="round" />
|
||||||
|
<line x1="7.07107" y1="7.07104" x2="18.3848" y2="18.3848" stroke="#FFBB00" stroke-width="2" stroke-linecap="round" />
|
||||||
|
<line x1="18.3848" y1="7.07107" x2="7.07106" y2="18.3848" stroke="#FFBB00" stroke-width="2" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 565 B |
7
assets/icons/svg/star.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star-icon lucide-star">
|
||||||
|
<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" />
|
||||||
|
<path d="M20 3v4" />
|
||||||
|
<path d="M22 5h-4" />
|
||||||
|
<path d="M4 17v2" />
|
||||||
|
<path d="M5 18H3" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 603 B |
5
assets/icons/svg/sync-third-party.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sync-icon lucide-sync">
|
||||||
|
<rect width="10" height="14" x="3" y="8" rx="2" />
|
||||||
|
<path d="M5 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2h-2.4" />
|
||||||
|
<path d="M8 18h.01" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 390 B |
3
assets/icons/svg/upload-device.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M7 16a4 4 0 0 1-.88-7.903A5 5 0 1 1 15.9 6L16 6a5 5 0 0 1 1 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 299 B |
3
assets/icons/svg/upload.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 300 B |
6
assets/icons/svg/user-edit.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor">
|
||||||
|
<path d="M2 21a8 8 0 0 1 10.821-7.487" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<circle cx="10" cy="8" r="5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 574 B |
6
assets/icons/svg/user-id.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book-lock-icon lucide-book-lock">
|
||||||
|
<path d="M18 6V4a2 2 0 1 0-4 0v2" />
|
||||||
|
<path d="M20 15v6a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" />
|
||||||
|
<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H10" />
|
||||||
|
<rect x="12" y="6" width="8" height="5" rx="1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 451 B |
5
assets/icons/svg/user.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="4" r="2" />
|
||||||
|
<path d="M10.2 3.2C5.5 4 2 8.1 2 13a2 2 0 0 0 4 0v-1a2 2 0 0 1 4 0v4a2 2 0 0 0 4 0v-4a2 2 0 0 1 4 0v1a2 2 0 0 0 4 0c0-4.9-3.5-9-8.2-9.8" />
|
||||||
|
<path d="M3.2 14.8a9 9 0 0 0 17.6 0" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 413 B |
1
assets/icons/svg/version.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-target-icon lucide-target"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 329 B |
1
assets/icons/svg/video-count.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-monitor-play-icon lucide-monitor-play"><path d="M10 7.75a.75.75 0 0 1 1.142-.638l3.664 2.249a.75.75 0 0 1 0 1.278l-3.664 2.25a.75.75 0 0 1-1.142-.64z"/><path d="M12 17v4"/><path d="M8 21h8"/><rect x="2" y="3" width="20" height="14" rx="2"/></svg>
|
||||||
|
After Width: | Height: | Size: 448 B |
3
assets/icons/svg/video.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-video-icon lucide-video">
|
||||||
|
<path d="M4.5 4.5a3 3 0 0 0-3 3v9a3 3 0 0 0 3 3h8.25a3 3 0 0 0 3-3v-9a3 3 0 0 0-3-3H4.5ZM19.94 18.75l-2.69-2.69V7.94l2.69-2.69c.944-.945 2.56-.276 2.56 1.06v11.38c0 1.336-1.616 2.005-2.56 1.06Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 437 B |
3
assets/icons/svg/wave.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 113 152" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M25.8559 151C12.8609 139.722 -9.23051 107.241 6.36346 67.5413C25.8559 17.9171 91.25 30.8873 97.5379 49.4961C100.053 56.9397 103.197 67.5413 91.25 74.3084C82.2279 79.4187 66.1175 77.9572 61.6969 71.4888C57.2763 65.0204 42.8332 32.0151 112 1" stroke="#FFBB00" stroke-width="2" stroke-dasharray="4 4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 396 B |
1
assets/icons/svg/x.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||||
|
After Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 17 KiB |
9
babel.config.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module.exports = function (api) {
|
||||||
|
api.cache(true);
|
||||||
|
return {
|
||||||
|
presets: [
|
||||||
|
["babel-preset-expo", { jsxImportSource: "nativewind" }],
|
||||||
|
"nativewind/babel",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,14 +1,9 @@
|
|||||||
import { View, type ViewProps } from 'react-native';
|
import { View, type ViewProps } from 'react-native';
|
||||||
|
|
||||||
import { useThemeColor } from '@/hooks/useThemeColor';
|
type ThemedViewProps = ViewProps & {
|
||||||
|
className?: string;
|
||||||
export type ThemedViewProps = ViewProps & {
|
|
||||||
lightColor?: string;
|
|
||||||
darkColor?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
|
export function ThemedView({ className, style, ...props }: ThemedViewProps) {
|
||||||
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background');
|
return <View className={className} style={style} {...props} />;
|
||||||
|
|
||||||
return <View style={[{ backgroundColor }, style]} {...otherProps} />;
|
|
||||||
}
|
}
|
||||||
|
|||||||
37
components/steps.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import type { RootState } from '../store';
|
||||||
|
|
||||||
|
// 定义 slice state 的类型
|
||||||
|
interface CounterState {
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用该类型定义初始 state
|
||||||
|
const initialState: CounterState = {
|
||||||
|
value: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const counterSlice = createSlice({
|
||||||
|
name: 'counter',
|
||||||
|
// `createSlice` 将从 `initialState` 参数推断 state 类型
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
increment: (state) => {
|
||||||
|
state.value += 1;
|
||||||
|
},
|
||||||
|
decrement: (state) => {
|
||||||
|
state.value -= 1;
|
||||||
|
},
|
||||||
|
// 使用 PayloadAction 类型声明 `action.payload` 的内容
|
||||||
|
incrementByAmount: (state, action: PayloadAction<number>) => {
|
||||||
|
state.value += action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
|
||||||
|
|
||||||
|
// selectors 等其他代码可以使用导入的 `RootState` 类型
|
||||||
|
export const selectCount = (state: RootState) => state.counter.value;
|
||||||
|
|
||||||
|
export default counterSlice.reducer;
|
||||||
123
contexts/auth-context.tsx
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import { clearCredentials, selectCurrentToken, selectCurrentUser, setCredentials } from '@/features/auth/authSlice';
|
||||||
|
import { EVENT_TYPES, eventEmitter } from '@/lib/event-util';
|
||||||
|
import { fetchApi, refreshAuthToken } from '@/lib/server-api-util';
|
||||||
|
import { store } from '@/store';
|
||||||
|
import { User } from '@/types/user';
|
||||||
|
import React, { createContext, ReactNode, useContext, useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
interface AuthContextType {
|
||||||
|
user: User | null;
|
||||||
|
login: (user: User, jwt: string) => void;
|
||||||
|
logout: () => void;
|
||||||
|
jwt: string | null;
|
||||||
|
isGuester: () => Promise<boolean>;
|
||||||
|
isFormalUser: (userMessage: User | null) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const user = useSelector(selectCurrentUser);
|
||||||
|
const jwt = useSelector(selectCurrentToken);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 检查 Redux store 中是否已有 token
|
||||||
|
const refreshTokenAction = async () => {
|
||||||
|
const token = store.getState().auth.token;
|
||||||
|
if (token) {
|
||||||
|
// 验证当前 token 是否有效
|
||||||
|
fetchApi('/user/identity-check', {}, false)
|
||||||
|
.catch(async (error) => {
|
||||||
|
console.error("JWT validation failed, attempting to refresh token...");
|
||||||
|
try {
|
||||||
|
// 尝试刷新 token
|
||||||
|
await refreshAuthToken("Token expired");
|
||||||
|
console.log("Token refreshed successfully");
|
||||||
|
// Token 刷新成功,不需要做其他操作
|
||||||
|
} catch (refreshError) {
|
||||||
|
// 刷新 token 失败,才进行登出操作
|
||||||
|
console.error("Token refresh failed, logging out", refreshError);
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// 尝试刷新 token
|
||||||
|
await refreshAuthToken("Token expired");
|
||||||
|
console.log("Token refreshed successfully");
|
||||||
|
// Token 刷新成功,不需要做其他操作
|
||||||
|
} catch (refreshError) {
|
||||||
|
// 刷新 token 失败,才进行登出操作
|
||||||
|
console.error("Token refresh failed, logging out", refreshError);
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshTokenAction();
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const login = (newUser: User, newJwt: string) => {
|
||||||
|
// 更新 Redux store
|
||||||
|
dispatch(setCredentials({
|
||||||
|
user: newUser,
|
||||||
|
token: newJwt
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 触发事件通知
|
||||||
|
eventEmitter.emit(EVENT_TYPES.USER_INFO_UPDATED, newUser);
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
// 清除 Redux store 中的认证信息
|
||||||
|
dispatch(clearCredentials());
|
||||||
|
|
||||||
|
// 触发事件通知
|
||||||
|
eventEmitter.emit(EVENT_TYPES.USER_INFO_UPDATED, null);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否是游客身份
|
||||||
|
const isGuester = async () => {
|
||||||
|
// 调用接口判断是否是游客
|
||||||
|
try {
|
||||||
|
await fetchApi(`/iam/identity-check`, {
|
||||||
|
method: 'POST',
|
||||||
|
}, false);
|
||||||
|
user?.email === ""
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 判断是否是正式用户 手机号注册和email注册都为正式用户
|
||||||
|
const isFormalUser = (userMessage: User | null) => {
|
||||||
|
if (userMessage?.account) {
|
||||||
|
if (userMessage?.account?.includes("@")) {
|
||||||
|
return true
|
||||||
|
} else if (/^1[3-9]\d{9}$/.test(userMessage?.account)) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 没有用户信息
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ user, login, logout, jwt, isGuester, isFormalUser }}>
|
||||||
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAuth = () => {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useAuth must be used within an AuthProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
21
eas.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"cli": {
|
||||||
|
"version": ">= 16.11.0",
|
||||||
|
"appVersionSource": "remote"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"development": {
|
||||||
|
"developmentClient": true,
|
||||||
|
"distribution": "internal"
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"distribution": "internal"
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"autoIncrement": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"submit": {
|
||||||
|
"production": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
features/auth/authSlice.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import { User } from '@/types/user';
|
||||||
|
|
||||||
|
interface AuthState {
|
||||||
|
user: User | null;
|
||||||
|
token: string | null;
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
task_id: string | null;
|
||||||
|
url: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: AuthState = {
|
||||||
|
user: null,
|
||||||
|
token: null,
|
||||||
|
isAuthenticated: false,
|
||||||
|
task_id: null,
|
||||||
|
url: null
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authSlice = createSlice({
|
||||||
|
name: 'auth',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setCredentials: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ user: User; token: string }>
|
||||||
|
) => {
|
||||||
|
const { user, token } = action.payload;
|
||||||
|
state.user = user;
|
||||||
|
state.token = token;
|
||||||
|
state.isAuthenticated = true;
|
||||||
|
},
|
||||||
|
clearCredentials: (state) => {
|
||||||
|
state.user = null;
|
||||||
|
state.token = null;
|
||||||
|
state.isAuthenticated = false;
|
||||||
|
},
|
||||||
|
setGuestTaskData: (state, action: PayloadAction<{ task_id: string; url: string }>) => {
|
||||||
|
state.task_id = action.payload.task_id;
|
||||||
|
state.url = action.payload.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setCredentials, clearCredentials, setGuestTaskData } = authSlice.actions;
|
||||||
|
|
||||||
|
export default authSlice.reducer;
|
||||||
|
|
||||||
|
// Selectors
|
||||||
|
export const selectCurrentUser = (state: { auth: AuthState }) => state.auth.user;
|
||||||
|
export const selectCurrentToken = (state: { auth: AuthState }) => state.auth.token;
|
||||||
|
export const selectIsAuthenticated = (state: { auth: AuthState }) => state.auth.isAuthenticated;
|
||||||
3
global.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
50
hooks/useWindowSize.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
|
interface WindowSize {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
isMobile: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义Hook,用于获取并监听窗口大小变化
|
||||||
|
* @returns 当前窗口的宽度和高度以及是否为移动设备
|
||||||
|
*/
|
||||||
|
export function useWindowSize(): WindowSize {
|
||||||
|
// 初始化状态为 undefined,避免服务器端渲染问题
|
||||||
|
const [windowSize, setWindowSize] = useState<Omit<WindowSize, 'isMobile'>>({
|
||||||
|
width: typeof window !== 'undefined' ? window.innerWidth : 0,
|
||||||
|
height: typeof window !== 'undefined' ? window.innerHeight : 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 判断是否为移动设备,默认小于768px为移动设备
|
||||||
|
const isMobile = useMemo(() => windowSize.width < 768, [windowSize.width]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 只在客户端执行
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理窗口大小变化的函数
|
||||||
|
function handleResize() {
|
||||||
|
setWindowSize({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加事件监听器
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
// 确保初始状态正确
|
||||||
|
handleResize();
|
||||||
|
|
||||||
|
// 清理函数
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []); // 空依赖数组意味着这个effect只在组件挂载和卸载时运行
|
||||||
|
|
||||||
|
return { ...windowSize, isMobile };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useWindowSize;
|
||||||
55
i18n/LanguageContext.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import React, { createContext, useState, useContext, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
type LanguageContextType = {
|
||||||
|
currentLanguage: string;
|
||||||
|
changeLanguage: (lang: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
// 从localStorage获取保存的语言设置,如果没有则使用当前语言或默认为'en'
|
||||||
|
const savedLanguage = localStorage.getItem('i18nextLng') || i18n.language || 'en';
|
||||||
|
const [currentLanguage, setCurrentLanguage] = useState(savedLanguage);
|
||||||
|
|
||||||
|
const changeLanguage = (lang: string) => {
|
||||||
|
i18n.changeLanguage(lang);
|
||||||
|
// 将语言设置保存到localStorage
|
||||||
|
localStorage.setItem('i18nextLng', lang);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 确保初始化时使用正确的语言
|
||||||
|
if (savedLanguage && i18n.language !== savedLanguage) {
|
||||||
|
i18n.changeLanguage(savedLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLanguageChanged = () => {
|
||||||
|
setCurrentLanguage(i18n.language);
|
||||||
|
// 将更改后的语言保存到localStorage
|
||||||
|
localStorage.setItem('i18nextLng', i18n.language);
|
||||||
|
};
|
||||||
|
|
||||||
|
i18n.on('languageChanged', handleLanguageChanged);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
i18n.off('languageChanged', handleLanguageChanged);
|
||||||
|
};
|
||||||
|
}, [i18n, savedLanguage]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LanguageContext.Provider value={{ currentLanguage, changeLanguage }}>
|
||||||
|
{children}
|
||||||
|
</LanguageContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLanguage = (): LanguageContextType => {
|
||||||
|
const context = useContext(LanguageContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useLanguage must be used within a LanguageProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
42
i18n/generate-imports.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 脚本用来自动引入所有的json文件
|
||||||
|
*/
|
||||||
|
|
||||||
|
function generateImports() {
|
||||||
|
const localesPath = path.join(__dirname, 'locales');
|
||||||
|
const languages = fs.readdirSync(localesPath);
|
||||||
|
let imports = '';
|
||||||
|
let translationsMap = 'const translations = {\n';
|
||||||
|
|
||||||
|
languages.forEach(lang => {
|
||||||
|
const langPath = path.join(localesPath, lang);
|
||||||
|
if (fs.statSync(langPath).isDirectory()) {
|
||||||
|
const files = fs.readdirSync(langPath).filter(file => file.endsWith('.json'));
|
||||||
|
|
||||||
|
files.forEach(file => {
|
||||||
|
const ns = file.replace('.json', '');
|
||||||
|
const varName = `${lang}${ns.charAt(0).toUpperCase() + ns.slice(1)}`;
|
||||||
|
|
||||||
|
imports += `import ${varName} from './locales/${lang}/${file}';\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
translationsMap += ` ${lang}: {\n`;
|
||||||
|
files.forEach((file, index) => {
|
||||||
|
const ns = file.replace('.json', '');
|
||||||
|
const varName = `${lang}${ns.charAt(0).toUpperCase() + ns.slice(1)}`;
|
||||||
|
translationsMap += ` ${ns}: ${varName}${index < files.length - 1 ? ',' : ''}\n`;
|
||||||
|
});
|
||||||
|
translationsMap += ' },\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
translationsMap += '};\n\nexport default translations;';
|
||||||
|
|
||||||
|
const output = `// 自动生成的导入文件,请勿手动修改\n\n${imports}\n${translationsMap}`;
|
||||||
|
fs.writeFileSync(path.join(__dirname, 'translations-generated.ts'), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateImports();
|
||||||
100
i18n/index.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import i18n from 'i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
// 自动生成的导入
|
||||||
|
import translations from './translations-generated';
|
||||||
|
|
||||||
|
// 初始化i18next
|
||||||
|
i18n
|
||||||
|
// 检测用户语言
|
||||||
|
.use(LanguageDetector)
|
||||||
|
// 将i18n实例传递给react-i18next
|
||||||
|
.use(initReactI18next)
|
||||||
|
// 初始化i18next
|
||||||
|
.init({
|
||||||
|
// 初始不加载任何资源
|
||||||
|
resources: translations,
|
||||||
|
|
||||||
|
// 支持命名空间
|
||||||
|
ns: ['common', 'example'],
|
||||||
|
defaultNS: 'common',
|
||||||
|
|
||||||
|
// 设置默认语言为中文
|
||||||
|
lng: 'zh',
|
||||||
|
fallbackLng: 'en',
|
||||||
|
debug: process.env.NODE_ENV === 'development',
|
||||||
|
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false, // 不需要转义,React已经处理了
|
||||||
|
},
|
||||||
|
|
||||||
|
detection: {
|
||||||
|
order: ['localStorage', 'navigator'],
|
||||||
|
caches: ['localStorage'],
|
||||||
|
lookupLocalStorage: 'i18nextLng',
|
||||||
|
// 确保语言设置被保存到localStorage
|
||||||
|
lookupFromPathIndex: 0,
|
||||||
|
lookupFromSubdomainIndex: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按需加载翻译资源的函数
|
||||||
|
export const loadNamespaceForLanguage = async (lng: string, ns: string) => {
|
||||||
|
// 如果已经加载过该命名空间,则不重复加载
|
||||||
|
if (i18n.hasResourceBundle(lng, ns)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const langTranslations = translations[lng as keyof typeof translations];
|
||||||
|
if (!langTranslations) {
|
||||||
|
console.warn(`No translations found for language: ${lng}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const resource = langTranslations[ns as keyof typeof langTranslations];
|
||||||
|
if (resource) {
|
||||||
|
i18n.addResourceBundle(lng, ns, resource, true, true);
|
||||||
|
} else {
|
||||||
|
console.warn(`No translations found for ${lng}/${ns}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load translations for ${lng}/${ns}:`, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载指定命名空间及其备用语言的翻译
|
||||||
|
export const loadNamespaceWithFallback = async (ns: string, language?: string) => {
|
||||||
|
const currentLng = language || i18n.language || 'en';
|
||||||
|
|
||||||
|
// 加载当前语言的翻译
|
||||||
|
await loadNamespaceForLanguage(currentLng, ns);
|
||||||
|
|
||||||
|
// 如果当前语言不是英语,也加载英语作为备用
|
||||||
|
if (currentLng !== 'en') {
|
||||||
|
await loadNamespaceForLanguage('en', ns);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 预加载公共翻译资源
|
||||||
|
export const preloadCommonTranslations = async () => {
|
||||||
|
const currentLng = i18n.language || 'en';
|
||||||
|
|
||||||
|
// 预加载 common 和 example 命名空间
|
||||||
|
await Promise.all([
|
||||||
|
loadNamespaceForLanguage(currentLng, 'common'),
|
||||||
|
loadNamespaceForLanguage(currentLng, 'example')
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 如果当前语言不是英语,也预加载英语作为备用
|
||||||
|
if (currentLng !== 'en') {
|
||||||
|
await Promise.all([
|
||||||
|
loadNamespaceForLanguage('en', 'common'),
|
||||||
|
loadNamespaceForLanguage('en', 'example')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始加载公共翻译
|
||||||
|
preloadCommonTranslations();
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
319
i18n/locales/en.json
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
{
|
||||||
|
"navbar": {
|
||||||
|
"useCases": "Use cases",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"getFree": "Get FairClip free"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"english": "English",
|
||||||
|
"chinese": "简体中文"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"search": "Search...",
|
||||||
|
"title": "MemoWake - Home Video Memory, Powered by AI",
|
||||||
|
"name":"MemoWake",
|
||||||
|
"homepage":"HomePage",
|
||||||
|
"signup":"Sign up",
|
||||||
|
"login":"Login"
|
||||||
|
},
|
||||||
|
"imagePreview": {
|
||||||
|
"zoomOut": "Zoom out",
|
||||||
|
"zoomIn": "Zoom in",
|
||||||
|
"resetView": "Reset view",
|
||||||
|
"rotateCounterClockwise": "Rotate counter-clockwise",
|
||||||
|
"rotateClockwise": "Rotate clockwise",
|
||||||
|
"close": "Close preview",
|
||||||
|
"previous": "Previous image",
|
||||||
|
"next": "Next image",
|
||||||
|
"imageCounter": "Image {{current}} of {{total}}",
|
||||||
|
"preview": "Preview image"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"welcome": "Creativity, Simplified with ",
|
||||||
|
"subtitle": "FairClip is an AI-powered agent that analyzes, processes, and edits your video clips into professional content.",
|
||||||
|
"requestDemo": "Request a demo",
|
||||||
|
"useCases": "Use cases",
|
||||||
|
"travelVlog": "Travel Vlog",
|
||||||
|
"travelVlogDesc": "AI-powered video editing for travel vlogs",
|
||||||
|
"faq": "Frequent Asked Questions",
|
||||||
|
"accordion1": "Accordion 1",
|
||||||
|
"accordion2": "Accordion 2",
|
||||||
|
"accordion3": "Accordion 3",
|
||||||
|
"accordionContent": "First download and register an account, then log in, then register an account and log in, and finally register an account and log in. First download and register an account, then log in, then register an account and log in, and finally register an account and log in. First download and register an account, then log in, then register an account and log in, and finally register an account and log in.",
|
||||||
|
"exploreUseCase": "Explore use case"
|
||||||
|
},
|
||||||
|
"useCase": {
|
||||||
|
"request_demo": {
|
||||||
|
"title": "Have more interest?",
|
||||||
|
"description": "We are in private beta. Feel free to join our waiting list.",
|
||||||
|
"pause": "Pause",
|
||||||
|
"resume": "Resume",
|
||||||
|
"reset": "Reset"
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"select_material": "Select material",
|
||||||
|
"material_processing": "Material processing",
|
||||||
|
"coarse_editing": "Coarse editing",
|
||||||
|
"streamlining": "Streamlining",
|
||||||
|
"exporting": "Exporting",
|
||||||
|
"upload_meterial": "Upload meterial",
|
||||||
|
"video_analysis": "Video analysis",
|
||||||
|
"video_tagging": "Video tagging",
|
||||||
|
"video_classification": "Video classification",
|
||||||
|
"select_mainline": "Select mainline",
|
||||||
|
"select_content_structure": "Select content structure",
|
||||||
|
"script_creation": "Script creation",
|
||||||
|
"strategy_matching": "Strategy matching",
|
||||||
|
"material_arrangement": "Material arrangement",
|
||||||
|
"video_coarse_editing": "Video coarse editing",
|
||||||
|
"transition_effect_processing": "Transition effect processing",
|
||||||
|
"music_and_sound_effect_processing": "Music and sound effect processing",
|
||||||
|
"subtitle_processing": "Subtitle processing",
|
||||||
|
"color_adjustment": "Color adjustment",
|
||||||
|
"video_streamlining": "Video streamlining",
|
||||||
|
"bitrate_adjustment": "Bitrate adjustment",
|
||||||
|
"video_rendering": "Video rendering",
|
||||||
|
"export_publish": "Export publish",
|
||||||
|
"complete": "Complete"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"message1": {
|
||||||
|
"content": "Welcome to FairClip! I will assist you in creating a travel Vlog video. I will utilize the most suitable short clips, adopt your preferred creative narrative, content structure, and editing style, and complete the rough cut and refined packaging of the material."
|
||||||
|
},
|
||||||
|
"process_material": {
|
||||||
|
"message1": "Fairclip is performing material processing tasks...",
|
||||||
|
"message2": "The material processing has been completed.",
|
||||||
|
"message3": "The material slicing and classification have been completed.",
|
||||||
|
"stats": {
|
||||||
|
"title": "Processing Statistics",
|
||||||
|
"videos_processed": "Videos Processed",
|
||||||
|
"total_duration": "Total Duration",
|
||||||
|
"task_time": "Task Time",
|
||||||
|
"avg_speed": "Average Speed",
|
||||||
|
"videos_count": "{{count}} materials",
|
||||||
|
"minutes": "{{count}} minutes",
|
||||||
|
"seconds_per_video": "{{count}} sec/material"
|
||||||
|
},
|
||||||
|
"clips_set": "Clips Set",
|
||||||
|
"complete_match_clip_strategy": "Matching clip strategy has been completed.",
|
||||||
|
"video_editing_strategy": "Video editing strategy",
|
||||||
|
"mainline": "Mainline",
|
||||||
|
"content_structure": "Content structure",
|
||||||
|
"editing_style": "Editing style",
|
||||||
|
"select_mainline": "Select mainline",
|
||||||
|
"select_content_structure": "Select content structure",
|
||||||
|
"select_editing_style": "Select editing style",
|
||||||
|
"material_first": "Material first",
|
||||||
|
"travel_mix_cutting": "Travel mix cutting",
|
||||||
|
"film_style": "Film style",
|
||||||
|
"video_editing_strategy_note": "* Video editing strategy is not available in use cases."
|
||||||
|
},
|
||||||
|
"coarse_editing": {
|
||||||
|
"title": "Coarse editing",
|
||||||
|
"complete_script": "Script creation is completed.",
|
||||||
|
"script_creation": "Script creation",
|
||||||
|
"view_full_script": "View full",
|
||||||
|
"complete_arrangement": "Material clip arrangement is completed."
|
||||||
|
},
|
||||||
|
"detail_editing": {
|
||||||
|
"title": "Detail editing",
|
||||||
|
"transition": {
|
||||||
|
"message1": "{{count}} transitions have been added, making your video smoother.",
|
||||||
|
"title": "Transition"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"title": "Output Settings",
|
||||||
|
"resolution": "Resolution",
|
||||||
|
"aspect_ratio": "Aspect ratio",
|
||||||
|
"format": "Format",
|
||||||
|
"estimated_size": "Estimated size",
|
||||||
|
"rendering": "Rendering video in progress......",
|
||||||
|
"render_finished": "Rendering completed",
|
||||||
|
"share": "Share",
|
||||||
|
"download": "Download"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"upload_material": "Upload Material",
|
||||||
|
"upload_disable_reason": "Upload material is not available yet in use cases.",
|
||||||
|
"dragAndDropFiles": "Drag and drop files here",
|
||||||
|
"dropFilesHere": "Drop files here",
|
||||||
|
"orClickToUpload": "or click to upload",
|
||||||
|
"supportedFormats": "Supported formats",
|
||||||
|
"maxFileSize": "Maximum file size",
|
||||||
|
"uploadingFiles": "Uploading Files",
|
||||||
|
"addMoreFiles": "Add More",
|
||||||
|
"upload": "Upload",
|
||||||
|
"uploading": "Uploading...",
|
||||||
|
"presetMaterials": "Preset Material Sets",
|
||||||
|
"useThisSet": "Use this set",
|
||||||
|
"chengduTravelSet": "Chengdu traveling materials set",
|
||||||
|
"beijingTravelSet": "Beijing traveling materials set",
|
||||||
|
"shanghaiTravelSet": "Shanghai traveling materials set",
|
||||||
|
"or": "OR"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"faqs": {
|
||||||
|
"title": "'s Frequently Asked Questions",
|
||||||
|
"mobile_title": "Frequently Asked Questions",
|
||||||
|
"title1": "What is Fairclip?",
|
||||||
|
"content1": "Fairclip is a video editing platform for building performant, accessible and beautiful web experiences.",
|
||||||
|
"title2": "How can I apply to the Open Source Discount?",
|
||||||
|
"content2": "The Open Source Discount is available for everyone who is building an open source project. You can apply to the discount by sending an email to support@fairclip.cn",
|
||||||
|
"title3": "Can I use Fairclip for my freelance projects?",
|
||||||
|
"content3": "Yes, you can use Fairclip for your freelance projects. You can purchase the Freelancer License from our website.",
|
||||||
|
"contact_us": "Contact us"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"login": {
|
||||||
|
"title": "Log in",
|
||||||
|
"email": "Email",
|
||||||
|
"account": "Account",
|
||||||
|
"password": "Password",
|
||||||
|
"emailPlaceholder": "Enter your email",
|
||||||
|
"passwordPlaceholder": "Enter your password",
|
||||||
|
"rememberMe": "Remember me",
|
||||||
|
"forgotPassword": "Forgot password?",
|
||||||
|
"loginButton": "Log In",
|
||||||
|
"createAccount": "Create an account",
|
||||||
|
"accountPlaceholder": "Enter your account or email",
|
||||||
|
"signUpMessage": "Don’t have an account?",
|
||||||
|
"signUp": "Sign up"
|
||||||
|
},
|
||||||
|
"agree": {
|
||||||
|
"text": "By signing up, you agree to our",
|
||||||
|
"terms": " Terms",
|
||||||
|
"join": "&",
|
||||||
|
"privacyPolicy": " Privacy Policy."
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"welcome": "Welcome to MemoWake",
|
||||||
|
"slogan": "Preserve your love, laughter and precious moments forever",
|
||||||
|
"notice": "s back to live family portrait"
|
||||||
|
},
|
||||||
|
"forgetPwd": {
|
||||||
|
"title": "forget password",
|
||||||
|
"emailPlaceholder": "Enter your email",
|
||||||
|
"sendEmailBtn": "Send email",
|
||||||
|
"goback": "Go back"
|
||||||
|
},
|
||||||
|
"resetPwd": {
|
||||||
|
"title": "Reset password",
|
||||||
|
"loginButton": "Log in",
|
||||||
|
"signupButton": "Sign up",
|
||||||
|
"resetButton": "Reset",
|
||||||
|
"goback": "Go back"
|
||||||
|
},
|
||||||
|
"signup": {
|
||||||
|
"title": "Sign up",
|
||||||
|
"username": "Username",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"confirmPassword": "Confirm Password",
|
||||||
|
"usernamePlaceholder": "Enter your username",
|
||||||
|
"emailPlaceholder": "Enter your email",
|
||||||
|
"passwordPlaceholder": "Enter your password",
|
||||||
|
"confirmPasswordPlaceholder": "Confirm your password",
|
||||||
|
"agreeTerms": "I agree with the",
|
||||||
|
"terms": "Terms",
|
||||||
|
"and": "and",
|
||||||
|
"privacyPolicy": "Privacy Policy",
|
||||||
|
"signupButton": "Sign Up",
|
||||||
|
"haveAccount": "Have an account?",
|
||||||
|
"login": "Login",
|
||||||
|
"verifyCode": "Verification Code",
|
||||||
|
"verifyCodePlaceholder": "Enter 6-digit code",
|
||||||
|
"sendCode": "Send Code",
|
||||||
|
"resendCode": "Resend",
|
||||||
|
"codeExpireTime": "Code will expire in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"email": {
|
||||||
|
"required": "Email is required",
|
||||||
|
"invalid": "Please enter a valid email address",
|
||||||
|
"missingAt": "Email must contain @",
|
||||||
|
"missingDomain": "Email must contain a domain (e.g. example.com)",
|
||||||
|
"missingUsername": "Email must contain a username before @",
|
||||||
|
"tooShort": "Email is too short",
|
||||||
|
"tooLong": "Email is too long"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"required": "Password is required",
|
||||||
|
"tooShort": "Password must be at least 6 characters",
|
||||||
|
"tooLong": "Password is too long",
|
||||||
|
"noUppercase": "Password must contain at least one uppercase letter",
|
||||||
|
"noLowercase": "Password must contain at least one lowercase letter",
|
||||||
|
"noNumber": "Password must contain at least one number",
|
||||||
|
"noSpecialChar": "Password must contain at least one special character",
|
||||||
|
"mismatch": "Passwords do not match"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"required": "Username is required",
|
||||||
|
"invalidChars": "Username can only contain letters, numbers, and Chinese characters",
|
||||||
|
"tooShort": "Username is too short",
|
||||||
|
"tooLong": "Username is too long"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"required": "Number is required",
|
||||||
|
"notNumber": "Please enter a valid number",
|
||||||
|
"tooSmall": "Number is too small",
|
||||||
|
"tooLarge": "Number is too large",
|
||||||
|
"invalidFormat": "Invalid number format",
|
||||||
|
"decimalNotAllowed": "Decimal numbers are not allowed",
|
||||||
|
"negativeNotAllowed": "Negative numbers are not allowed"
|
||||||
|
},
|
||||||
|
"verifyCode": {
|
||||||
|
"required": "Verification code is required",
|
||||||
|
"invalidLength": "Verification code must be 6 digits",
|
||||||
|
"invalidFormat": "Verification code must contain only numbers",
|
||||||
|
"expired": "Verification code has expired, please request a new one",
|
||||||
|
"incorrect": "Incorrect verification code, please try again"
|
||||||
|
},
|
||||||
|
"terms": {
|
||||||
|
"required": "You must agree to the Terms and Privacy Policy"
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"invalidType": "Invalid file type. Only video files are allowed.",
|
||||||
|
"tooLarge": "File is too large. Maximum size is 500MB."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"continue": "Continue",
|
||||||
|
"material": {
|
||||||
|
"set": {
|
||||||
|
"clips": "clips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"close": "Close",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"video_script": {
|
||||||
|
"title": "Video script",
|
||||||
|
"table": {
|
||||||
|
"scene_index": "Scene index",
|
||||||
|
"scene_name": "Scene name",
|
||||||
|
"shot_description": "Shot description",
|
||||||
|
"shot_size": "Shot size",
|
||||||
|
"shot_index": "Shot index",
|
||||||
|
"shot_duration": "Shot duration",
|
||||||
|
"actor_line": "Actor line"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"coarseVideoCard": {
|
||||||
|
"description": "Clip description"
|
||||||
|
},
|
||||||
|
"EWS": "EWS",
|
||||||
|
"WS": "WS",
|
||||||
|
"FS": "FS",
|
||||||
|
"MWS": "MWS",
|
||||||
|
"MS": "MS",
|
||||||
|
"MCU": "MCU",
|
||||||
|
"CU": "CU",
|
||||||
|
"ECU": "ECU",
|
||||||
|
"2S": "2S",
|
||||||
|
"OTS": "OTS",
|
||||||
|
"POV": "POV",
|
||||||
|
"CI": "CI",
|
||||||
|
"CA": "CA"
|
||||||
|
}
|
||||||
140
i18n/locales/en/admin.json
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"users": "Users",
|
||||||
|
"settings": "Settings",
|
||||||
|
"profile": "Profile",
|
||||||
|
"logout": "Logout",
|
||||||
|
"home": "Home",
|
||||||
|
"tasks": "Tasks",
|
||||||
|
"projects": "Projects",
|
||||||
|
"analytics": "Analytics",
|
||||||
|
"reports": "Reports",
|
||||||
|
"works": "Works",
|
||||||
|
"qualification": "Qualification",
|
||||||
|
"promptTable": {
|
||||||
|
"camera_direction": "Camera Direction",
|
||||||
|
"camera_movement": "Camera Movement",
|
||||||
|
"composition": "Composition",
|
||||||
|
"music_description": "Music Description",
|
||||||
|
"perspective": "Perspective",
|
||||||
|
"scene_name": "Scene Name",
|
||||||
|
"scene_sequence": "Scene Sequence",
|
||||||
|
"shot_description": "Shot Description",
|
||||||
|
"shot_duration": "Shot Duration",
|
||||||
|
"shot_name": "Shot Name",
|
||||||
|
"shot_sequence": "Shot Sequence",
|
||||||
|
"shot_sizes": "Shot Sizes",
|
||||||
|
"sound_effect": "Sound Effect",
|
||||||
|
"transition_in": "Transition In",
|
||||||
|
"voice_over": "Voice Over"
|
||||||
|
},
|
||||||
|
"formwork": {
|
||||||
|
"works": {
|
||||||
|
"title": "Work Templates",
|
||||||
|
"id": "Template ID",
|
||||||
|
"name": "Template Name",
|
||||||
|
"description": "Template Description",
|
||||||
|
"model": "Model",
|
||||||
|
"count": "Sample Count",
|
||||||
|
"version": "Version",
|
||||||
|
"prompt": "Prompt",
|
||||||
|
"updated_at": "Last Updated",
|
||||||
|
"operate": "Actions",
|
||||||
|
"view": "View",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete"
|
||||||
|
},
|
||||||
|
"ability": {
|
||||||
|
"title": "Ability Templates"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"materialList": "Samples List",
|
||||||
|
"version": "Current Version:",
|
||||||
|
"model": "Current Model:",
|
||||||
|
"setVersion": "Set as Online Version",
|
||||||
|
"save": "Save",
|
||||||
|
"imagePreview": "Image Preview",
|
||||||
|
"videoPreview": "Video Preview",
|
||||||
|
"clipPreview": "Clip Preview",
|
||||||
|
"clipList": "Clip Segments",
|
||||||
|
"caption": "Caption/Tag Preview",
|
||||||
|
"prompt": "Prompt Debugging",
|
||||||
|
"promptPreview": "Result Preview",
|
||||||
|
"goodCase": "Good Case",
|
||||||
|
"badCase": "Bad Case",
|
||||||
|
"upload": "Upload",
|
||||||
|
"run": "Run",
|
||||||
|
"view": "View",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"uploadMaterialList": "Upload Materials",
|
||||||
|
"uploadMaterialListTitle": "Select Samples",
|
||||||
|
"uploadMaterialListoption": "Can't find the sample you want? Upload new materials here",
|
||||||
|
"confirm": "Confirm"
|
||||||
|
},
|
||||||
|
"tasks_page": {
|
||||||
|
"status": {
|
||||||
|
"Created": "Created",
|
||||||
|
"Processing": "Processing",
|
||||||
|
"Completed": "Completed",
|
||||||
|
"Failed": "Failed",
|
||||||
|
"Pending": "Pending",
|
||||||
|
"Approved": "Approved",
|
||||||
|
"Rejected": "Rejected",
|
||||||
|
"Waiting": "Waiting"
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"total": "Total Tasks",
|
||||||
|
"pending": "Pending",
|
||||||
|
"processing": "Processing",
|
||||||
|
"success": "Success",
|
||||||
|
"failed": "Failed",
|
||||||
|
"queued": "Queued"
|
||||||
|
},
|
||||||
|
"task_list": "Task List",
|
||||||
|
"no_preview": "No Preview",
|
||||||
|
"no_review_required": "No Review Required",
|
||||||
|
"view_details": "View Details",
|
||||||
|
"approve": "Approve",
|
||||||
|
"reject": "Reject",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"no_tasks": "No tasks available",
|
||||||
|
"items_per_page": "Items per page",
|
||||||
|
"items": "items",
|
||||||
|
"fetch_error": "Failed to fetch task list",
|
||||||
|
"thumbnail": "Thumbnail",
|
||||||
|
"search": "Search",
|
||||||
|
"search_placeholder": "Enter task ID to search",
|
||||||
|
"reset": "Reset",
|
||||||
|
"filter": {
|
||||||
|
"status": "Status",
|
||||||
|
"all": "All"
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"preview": "Preview",
|
||||||
|
"task_id": "Task ID",
|
||||||
|
"status": "Status",
|
||||||
|
"method": "Generation Method",
|
||||||
|
"review": "Review Status",
|
||||||
|
"actions": "Actions"
|
||||||
|
},
|
||||||
|
"download_origin_file": "Download Origin File",
|
||||||
|
"output_video": "Output Video",
|
||||||
|
"video_not_supported": "Your browser does not support video playback",
|
||||||
|
"upload_result": "Upload Result File",
|
||||||
|
"upload_result_title": "Upload Result File",
|
||||||
|
"upload_result_description": "Please upload a result video file that will serve as the final output for this task. After uploading, the task will be marked as rejected.",
|
||||||
|
"upload_and_approve": "Upload and Approve",
|
||||||
|
"upload_error": "Upload Error",
|
||||||
|
"no_file_selected": "Please select a file to upload",
|
||||||
|
"upload_error_description": "An error occurred while uploading the result file",
|
||||||
|
"reject_success": "Rejected successfully",
|
||||||
|
"approve_success": "Approved successfully",
|
||||||
|
"update_status_error": "Failed to update status"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"close": "Close",
|
||||||
|
"cancel": "Cancel"
|
||||||
|
}
|
||||||
|
}
|
||||||
113
i18n/locales/en/common.json
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
"navbar": {
|
||||||
|
"useCases": "Use cases",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"getFree": "Get FairClip free"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"english": "English",
|
||||||
|
"chinese": "简体中文"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"search": "Search...",
|
||||||
|
"title": "MemoWake - Home Video Memory, Powered by AI",
|
||||||
|
"name":"MemoWake",
|
||||||
|
"homepage":"HomePage",
|
||||||
|
"signup":"Sign up",
|
||||||
|
"login":"Login",
|
||||||
|
"trade":"copyright 2025 MemoWake - All rights reserved",
|
||||||
|
"logout":"Logout",
|
||||||
|
"self":"Personal Center"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"welcome": "Welcome to MemoWake~",
|
||||||
|
"slogan": "Preserve your love, laughter and precious moments forever",
|
||||||
|
"notice": "s back to live family portrait "
|
||||||
|
},
|
||||||
|
"imagePreview": {
|
||||||
|
"zoomOut": "Zoom out",
|
||||||
|
"zoomIn": "Zoom in",
|
||||||
|
"resetView": "Reset view",
|
||||||
|
"rotateCounterClockwise": "Rotate counter-clockwise",
|
||||||
|
"rotateClockwise": "Rotate clockwise",
|
||||||
|
"close": "Close preview",
|
||||||
|
"previous": "Previous image",
|
||||||
|
"next": "Next image",
|
||||||
|
"imageCounter": "Image {{current}} of {{total}}",
|
||||||
|
"preview": "Preview image"
|
||||||
|
},
|
||||||
|
"fileUploader": {
|
||||||
|
"uploadingFiles": "Uploading files...",
|
||||||
|
"selectedFiles": "Selected files",
|
||||||
|
"addMoreFiles": "Add more files",
|
||||||
|
"clearAll": "Clear all",
|
||||||
|
"upload": "Upload",
|
||||||
|
"uploading": "Uploading...",
|
||||||
|
"dragAndDropFiles": "Drag and drop files here",
|
||||||
|
"dropFilesHere": "Drop files here",
|
||||||
|
"orClickToUpload": "or click to upload",
|
||||||
|
"supportedFormats": "Supported formats",
|
||||||
|
"maxFileSize": "Maximum file size",
|
||||||
|
"uploadProgress": "Upload progress",
|
||||||
|
"remove": "Remove",
|
||||||
|
"uploadedFiles": "Uploaded files",
|
||||||
|
"clickToReplaceFile": "Click to replace file",
|
||||||
|
"invalidFileTitle": "Invalid file type",
|
||||||
|
"fileTooLargeTitle": "File too large",
|
||||||
|
"fileTooSmallTitle": "File too small",
|
||||||
|
"fileTooSmall": "File too small,please upload a larger file.",
|
||||||
|
"uploadErrorTitle": "Upload failed"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"file": {
|
||||||
|
"invalidType": "Invalid file type",
|
||||||
|
"tooLarge": "File too large",
|
||||||
|
"tooSmall":"File too small"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"required": "Email is required",
|
||||||
|
"invalid": "Please enter a valid email address",
|
||||||
|
"missingAt": "Email must contain @",
|
||||||
|
"missingDomain": "Email must contain a domain (e.g. example.com)",
|
||||||
|
"missingUsername": "Email must contain a username before @",
|
||||||
|
"tooShort": "Email is too short",
|
||||||
|
"tooLong": "Email is too long"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"required": "Password is required",
|
||||||
|
"tooShort": "Password must be at least 6 characters",
|
||||||
|
"tooLong": "Password is too long",
|
||||||
|
"noUppercase": "Password must contain at least one uppercase letter",
|
||||||
|
"noLowercase": "Password must contain at least one lowercase letter",
|
||||||
|
"noNumber": "Password must contain at least one number",
|
||||||
|
"noSpecialChar": "Password must contain at least one special character",
|
||||||
|
"mismatch": "Passwords do not match"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"required": "Username is required",
|
||||||
|
"invalidChars": "Username can only contain letters, numbers, and Chinese characters",
|
||||||
|
"tooShort": "Username is too short",
|
||||||
|
"tooLong": "Username is too long"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"required": "Number is required",
|
||||||
|
"notNumber": "Please enter a valid number",
|
||||||
|
"tooSmall": "Number is too small",
|
||||||
|
"tooLarge": "Number is too large",
|
||||||
|
"invalidFormat": "Invalid number format",
|
||||||
|
"decimalNotAllowed": "Decimal numbers are not allowed",
|
||||||
|
"negativeNotAllowed": "Negative numbers are not allowed"
|
||||||
|
},
|
||||||
|
"verifyCode": {
|
||||||
|
"required": "Verification code is required",
|
||||||
|
"invalidLength": "Verification code must be 6 digits",
|
||||||
|
"invalidFormat": "Verification code must contain only numbers",
|
||||||
|
"expired": "Verification code has expired, please request a new one",
|
||||||
|
"incorrect": "Incorrect verification code, please try again"
|
||||||
|
},
|
||||||
|
"terms": {
|
||||||
|
"required": "You must agree to the Terms and Privacy Policy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loading": "Loading..."
|
||||||
|
}
|
||||||
3
i18n/locales/en/example.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"title": "example"
|
||||||
|
}
|
||||||
319
i18n/locales/en/fairclip.json
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
{
|
||||||
|
"navbar": {
|
||||||
|
"useCases": "Use cases",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"getFree": "Get FairClip free"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"english": "English",
|
||||||
|
"chinese": "简体中文"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"search": "Search...",
|
||||||
|
"title": "MemoWake - Home Video Memory, Powered by AI",
|
||||||
|
"name":"MemoWake",
|
||||||
|
"homepage":"HomePage",
|
||||||
|
"signup":"Sign up",
|
||||||
|
"login":"Login"
|
||||||
|
},
|
||||||
|
"imagePreview": {
|
||||||
|
"zoomOut": "Zoom out",
|
||||||
|
"zoomIn": "Zoom in",
|
||||||
|
"resetView": "Reset view",
|
||||||
|
"rotateCounterClockwise": "Rotate counter-clockwise",
|
||||||
|
"rotateClockwise": "Rotate clockwise",
|
||||||
|
"close": "Close preview",
|
||||||
|
"previous": "Previous image",
|
||||||
|
"next": "Next image",
|
||||||
|
"imageCounter": "Image {{current}} of {{total}}",
|
||||||
|
"preview": "Preview image"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"welcome": "Creativity, Simplified with ",
|
||||||
|
"subtitle": "FairClip is an AI-powered agent that analyzes, processes, and edits your video clips into professional content.",
|
||||||
|
"requestDemo": "Request a demo",
|
||||||
|
"useCases": "Use cases",
|
||||||
|
"travelVlog": "Travel Vlog",
|
||||||
|
"travelVlogDesc": "AI-powered video editing for travel vlogs",
|
||||||
|
"faq": "Frequent Asked Questions",
|
||||||
|
"accordion1": "Accordion 1",
|
||||||
|
"accordion2": "Accordion 2",
|
||||||
|
"accordion3": "Accordion 3",
|
||||||
|
"accordionContent": "First download and register an account, then log in, then register an account and log in, and finally register an account and log in. First download and register an account, then log in, then register an account and log in, and finally register an account and log in. First download and register an account, then log in, then register an account and log in, and finally register an account and log in.",
|
||||||
|
"exploreUseCase": "Explore use case"
|
||||||
|
},
|
||||||
|
"useCase": {
|
||||||
|
"request_demo": {
|
||||||
|
"title": "Have more interest?",
|
||||||
|
"description": "We are in private beta. Feel free to join our waiting list.",
|
||||||
|
"pause": "Pause",
|
||||||
|
"resume": "Resume",
|
||||||
|
"reset": "Reset"
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"select_material": "Select material",
|
||||||
|
"material_processing": "Material processing",
|
||||||
|
"coarse_editing": "Coarse editing",
|
||||||
|
"streamlining": "Streamlining",
|
||||||
|
"exporting": "Exporting",
|
||||||
|
"upload_meterial": "Upload meterial",
|
||||||
|
"video_analysis": "Video analysis",
|
||||||
|
"video_tagging": "Video tagging",
|
||||||
|
"video_classification": "Video classification",
|
||||||
|
"select_mainline": "Select mainline",
|
||||||
|
"select_content_structure": "Select content structure",
|
||||||
|
"script_creation": "Script creation",
|
||||||
|
"strategy_matching": "Strategy matching",
|
||||||
|
"material_arrangement": "Material arrangement",
|
||||||
|
"video_coarse_editing": "Video coarse editing",
|
||||||
|
"transition_effect_processing": "Transition effect processing",
|
||||||
|
"music_and_sound_effect_processing": "Music and sound effect processing",
|
||||||
|
"subtitle_processing": "Subtitle processing",
|
||||||
|
"color_adjustment": "Color adjustment",
|
||||||
|
"video_streamlining": "Video streamlining",
|
||||||
|
"bitrate_adjustment": "Bitrate adjustment",
|
||||||
|
"video_rendering": "Video rendering",
|
||||||
|
"export_publish": "Export publish",
|
||||||
|
"complete": "Complete"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"message1": {
|
||||||
|
"content": "Welcome to FairClip! I will assist you in creating a travel Vlog video. I will utilize the most suitable short clips, adopt your preferred creative narrative, content structure, and editing style, and complete the rough cut and refined packaging of the material."
|
||||||
|
},
|
||||||
|
"process_material": {
|
||||||
|
"message1": "Fairclip is performing material processing tasks...",
|
||||||
|
"message2": "The material processing has been completed.",
|
||||||
|
"message3": "The material slicing and classification have been completed.",
|
||||||
|
"stats": {
|
||||||
|
"title": "Processing Statistics",
|
||||||
|
"videos_processed": "Videos Processed",
|
||||||
|
"total_duration": "Total Duration",
|
||||||
|
"task_time": "Task Time",
|
||||||
|
"avg_speed": "Average Speed",
|
||||||
|
"videos_count": "{{count}} materials",
|
||||||
|
"minutes": "{{count}} minutes",
|
||||||
|
"seconds_per_video": "{{count}} sec/material"
|
||||||
|
},
|
||||||
|
"clips_set": "Clips Set",
|
||||||
|
"complete_match_clip_strategy": "Matching clip strategy has been completed.",
|
||||||
|
"video_editing_strategy": "Video editing strategy",
|
||||||
|
"mainline": "Mainline",
|
||||||
|
"content_structure": "Content structure",
|
||||||
|
"editing_style": "Editing style",
|
||||||
|
"select_mainline": "Select mainline",
|
||||||
|
"select_content_structure": "Select content structure",
|
||||||
|
"select_editing_style": "Select editing style",
|
||||||
|
"material_first": "Material first",
|
||||||
|
"travel_mix_cutting": "Travel mix cutting",
|
||||||
|
"film_style": "Film style",
|
||||||
|
"video_editing_strategy_note": "* Video editing strategy is not available in use cases."
|
||||||
|
},
|
||||||
|
"coarse_editing": {
|
||||||
|
"title": "Coarse editing",
|
||||||
|
"complete_script": "Script creation is completed.",
|
||||||
|
"script_creation": "Script creation",
|
||||||
|
"view_full_script": "View full",
|
||||||
|
"complete_arrangement": "Material clip arrangement is completed."
|
||||||
|
},
|
||||||
|
"detail_editing": {
|
||||||
|
"title": "Detail editing",
|
||||||
|
"transition": {
|
||||||
|
"message1": "{{count}} transitions have been added, making your video smoother.",
|
||||||
|
"title": "Transition"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"title": "Output Settings",
|
||||||
|
"resolution": "Resolution",
|
||||||
|
"aspect_ratio": "Aspect ratio",
|
||||||
|
"format": "Format",
|
||||||
|
"estimated_size": "Estimated size",
|
||||||
|
"rendering": "Rendering video in progress......",
|
||||||
|
"render_finished": "Rendering completed",
|
||||||
|
"share": "Share",
|
||||||
|
"download": "Download"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"upload_material": "Upload Material",
|
||||||
|
"upload_disable_reason": "Upload material is not available yet in use cases.",
|
||||||
|
"dragAndDropFiles": "Drag and drop files here",
|
||||||
|
"dropFilesHere": "Drop files here",
|
||||||
|
"orClickToUpload": "or click to upload",
|
||||||
|
"supportedFormats": "Supported formats",
|
||||||
|
"maxFileSize": "Maximum file size",
|
||||||
|
"uploadingFiles": "Uploading Files",
|
||||||
|
"addMoreFiles": "Add More",
|
||||||
|
"upload": "Upload",
|
||||||
|
"uploading": "Uploading...",
|
||||||
|
"presetMaterials": "Preset Material Sets",
|
||||||
|
"useThisSet": "Use this set",
|
||||||
|
"chengduTravelSet": "Chengdu traveling materials set",
|
||||||
|
"beijingTravelSet": "Beijing traveling materials set",
|
||||||
|
"shanghaiTravelSet": "Shanghai traveling materials set",
|
||||||
|
"or": "OR"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"faqs": {
|
||||||
|
"title": "'s Frequently Asked Questions",
|
||||||
|
"mobile_title": "Frequently Asked Questions",
|
||||||
|
"title1": "What is Fairclip?",
|
||||||
|
"content1": "Fairclip is a video editing platform for building performant, accessible and beautiful web experiences.",
|
||||||
|
"title2": "How can I apply to the Open Source Discount?",
|
||||||
|
"content2": "The Open Source Discount is available for everyone who is building an open source project. You can apply to the discount by sending an email to support@fairclip.cn",
|
||||||
|
"title3": "Can I use Fairclip for my freelance projects?",
|
||||||
|
"content3": "Yes, you can use Fairclip for your freelance projects. You can purchase the Freelancer License from our website.",
|
||||||
|
"contact_us": "Contact us"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"login": {
|
||||||
|
"title": "Log in",
|
||||||
|
"email": "Email",
|
||||||
|
"account": "Account",
|
||||||
|
"password": "Password",
|
||||||
|
"emailPlaceholder": "Enter your email",
|
||||||
|
"passwordPlaceholder": "Enter your password",
|
||||||
|
"rememberMe": "Remember me",
|
||||||
|
"forgotPassword": "Forgot password?",
|
||||||
|
"loginButton": "Log In",
|
||||||
|
"createAccount": "Create an account",
|
||||||
|
"accountPlaceholder": "Enter your account or email",
|
||||||
|
"signUpMessage": "Don’t have an account?",
|
||||||
|
"signUp": "Sign up"
|
||||||
|
},
|
||||||
|
"agree": {
|
||||||
|
"text": "By signing up, you agree to our",
|
||||||
|
"terms": " Terms",
|
||||||
|
"join": "&",
|
||||||
|
"privacyPolicy": " Privacy Policy."
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"welcome": "Welcome to MemoWake",
|
||||||
|
"slogan": "Preserve your love, laughter and precious moments forever",
|
||||||
|
"notice": "s back to live family portrait"
|
||||||
|
},
|
||||||
|
"forgetPwd": {
|
||||||
|
"title": "forget password",
|
||||||
|
"emailPlaceholder": "Enter your email",
|
||||||
|
"sendEmailBtn": "Send email",
|
||||||
|
"goback": "Go back"
|
||||||
|
},
|
||||||
|
"resetPwd": {
|
||||||
|
"title": "Reset password",
|
||||||
|
"loginButton": "Log in",
|
||||||
|
"signupButton": "Sign up",
|
||||||
|
"resetButton": "Reset",
|
||||||
|
"goback": "Go back"
|
||||||
|
},
|
||||||
|
"signup": {
|
||||||
|
"title": "Sign up",
|
||||||
|
"username": "Username",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"confirmPassword": "Confirm Password",
|
||||||
|
"usernamePlaceholder": "Enter your username",
|
||||||
|
"emailPlaceholder": "Enter your email",
|
||||||
|
"passwordPlaceholder": "Enter your password",
|
||||||
|
"confirmPasswordPlaceholder": "Confirm your password",
|
||||||
|
"agreeTerms": "I agree with the",
|
||||||
|
"terms": "Terms",
|
||||||
|
"and": "and",
|
||||||
|
"privacyPolicy": "Privacy Policy",
|
||||||
|
"signupButton": "Sign Up",
|
||||||
|
"haveAccount": "Have an account?",
|
||||||
|
"login": "Login",
|
||||||
|
"verifyCode": "Verification Code",
|
||||||
|
"verifyCodePlaceholder": "Enter 6-digit code",
|
||||||
|
"sendCode": "Send Code",
|
||||||
|
"resendCode": "Resend",
|
||||||
|
"codeExpireTime": "Code will expire in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"email": {
|
||||||
|
"required": "Email is required",
|
||||||
|
"invalid": "Please enter a valid email address",
|
||||||
|
"missingAt": "Email must contain @",
|
||||||
|
"missingDomain": "Email must contain a domain (e.g. example.com)",
|
||||||
|
"missingUsername": "Email must contain a username before @",
|
||||||
|
"tooShort": "Email is too short",
|
||||||
|
"tooLong": "Email is too long"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"required": "Password is required",
|
||||||
|
"tooShort": "Password must be at least 6 characters",
|
||||||
|
"tooLong": "Password is too long",
|
||||||
|
"noUppercase": "Password must contain at least one uppercase letter",
|
||||||
|
"noLowercase": "Password must contain at least one lowercase letter",
|
||||||
|
"noNumber": "Password must contain at least one number",
|
||||||
|
"noSpecialChar": "Password must contain at least one special character",
|
||||||
|
"mismatch": "Passwords do not match"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"required": "Username is required",
|
||||||
|
"invalidChars": "Username can only contain letters, numbers, and Chinese characters",
|
||||||
|
"tooShort": "Username is too short",
|
||||||
|
"tooLong": "Username is too long"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"required": "Number is required",
|
||||||
|
"notNumber": "Please enter a valid number",
|
||||||
|
"tooSmall": "Number is too small",
|
||||||
|
"tooLarge": "Number is too large",
|
||||||
|
"invalidFormat": "Invalid number format",
|
||||||
|
"decimalNotAllowed": "Decimal numbers are not allowed",
|
||||||
|
"negativeNotAllowed": "Negative numbers are not allowed"
|
||||||
|
},
|
||||||
|
"verifyCode": {
|
||||||
|
"required": "Verification code is required",
|
||||||
|
"invalidLength": "Verification code must be 6 digits",
|
||||||
|
"invalidFormat": "Verification code must contain only numbers",
|
||||||
|
"expired": "Verification code has expired, please request a new one",
|
||||||
|
"incorrect": "Incorrect verification code, please try again"
|
||||||
|
},
|
||||||
|
"terms": {
|
||||||
|
"required": "You must agree to the Terms and Privacy Policy"
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"invalidType": "Invalid file type. Only video files are allowed.",
|
||||||
|
"tooLarge": "File is too large. Maximum size is 500MB."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"continue": "Continue",
|
||||||
|
"material": {
|
||||||
|
"set": {
|
||||||
|
"clips": "clips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"close": "Close",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"video_script": {
|
||||||
|
"title": "Video script",
|
||||||
|
"table": {
|
||||||
|
"scene_index": "Scene index",
|
||||||
|
"scene_name": "Scene name",
|
||||||
|
"shot_description": "Shot description",
|
||||||
|
"shot_size": "Shot size",
|
||||||
|
"shot_index": "Shot index",
|
||||||
|
"shot_duration": "Shot duration",
|
||||||
|
"actor_line": "Actor line"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"coarseVideoCard": {
|
||||||
|
"description": "Clip description"
|
||||||
|
},
|
||||||
|
"EWS": "EWS",
|
||||||
|
"WS": "WS",
|
||||||
|
"FS": "FS",
|
||||||
|
"MWS": "MWS",
|
||||||
|
"MS": "MS",
|
||||||
|
"MCU": "MCU",
|
||||||
|
"CU": "CU",
|
||||||
|
"ECU": "ECU",
|
||||||
|
"2S": "2S",
|
||||||
|
"OTS": "OTS",
|
||||||
|
"POV": "POV",
|
||||||
|
"CI": "CI",
|
||||||
|
"CA": "CA"
|
||||||
|
}
|
||||||
18
i18n/locales/en/landing.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"landing":{
|
||||||
|
"homepage":"Homepage",
|
||||||
|
"name":"MemoWake",
|
||||||
|
"login":"Login",
|
||||||
|
"signUp":"Sign Up",
|
||||||
|
"photo":{
|
||||||
|
"title":"Live Family Portrait",
|
||||||
|
"desc":"Frozen memories in photos, like love, flow again between family members.",
|
||||||
|
"createButton":"Create your live family photo",
|
||||||
|
"more":"More than",
|
||||||
|
"than":"families have already created live photos",
|
||||||
|
"powered":"powered by fairclip"
|
||||||
|
},
|
||||||
|
"gallery":"Gallery",
|
||||||
|
"logout":"Logout"
|
||||||
|
}
|
||||||
|
}
|
||||||
89
i18n/locales/en/login.json
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{"auth": {
|
||||||
|
"telLogin": {
|
||||||
|
"sendCode": "send code",
|
||||||
|
"login": "Login",
|
||||||
|
"codePlaceholder": "Enter verification code",
|
||||||
|
"sending": "Sending...",
|
||||||
|
"codeSeconds": "seconds",
|
||||||
|
"agree": "I agree to the ",
|
||||||
|
"terms": "Terms of Service",
|
||||||
|
"and": " and ",
|
||||||
|
"privacyPolicy": "Privacy Policy",
|
||||||
|
"userAgreement": "User Agreement",
|
||||||
|
"agreement": " and authorize ",
|
||||||
|
"getPhone": " to obtain my phone number",
|
||||||
|
"codeError": "Failed to send verification code, please try again",
|
||||||
|
"phoneRequired": "Please enter your phone number",
|
||||||
|
"phoneInvalid": "Please enter a valid phone number",
|
||||||
|
"codeRequired": "Please enter the verification code",
|
||||||
|
"codeInvalid": "Invalid verification code format",
|
||||||
|
"checkedRequired": "Please agree to the terms",
|
||||||
|
"loginError": "Login failed, please try again"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"title": "Log in",
|
||||||
|
"email": "Email",
|
||||||
|
"account": "Account",
|
||||||
|
"password": "Password",
|
||||||
|
"emailPlaceholder": "Enter your email",
|
||||||
|
"passwordPlaceholder": "Enter your password",
|
||||||
|
"rememberMe": "Remember me",
|
||||||
|
"forgotPassword": "Forgot password?",
|
||||||
|
"loginButton": "Log In",
|
||||||
|
"createAccount": "Create an account",
|
||||||
|
"accountPlaceholder": "Enter your account or email",
|
||||||
|
"signUpMessage": "Don’t have an account?",
|
||||||
|
"signUp": "Sign up",
|
||||||
|
"phoneLogin":"Phone Login"
|
||||||
|
},
|
||||||
|
"agree": {
|
||||||
|
"text": "By signing up, you agree to our",
|
||||||
|
"terms": " Terms",
|
||||||
|
"join": "&",
|
||||||
|
"privacyPolicy": " Privacy Policy."
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"welcome": "Welcome to MemoWake",
|
||||||
|
"slogan": "Preserve your love, laughter and precious moments forever",
|
||||||
|
"notice": "s back to live family portrait"
|
||||||
|
},
|
||||||
|
"forgetPwd": {
|
||||||
|
"title": "forget password",
|
||||||
|
"emailPlaceholder": "Enter your email",
|
||||||
|
"sendEmailBtn": "Send email",
|
||||||
|
"goback": "Go back",
|
||||||
|
"success":"Email sent successfully, please check your email",
|
||||||
|
"sendEmailBtnDisabled": "Email sent"
|
||||||
|
},
|
||||||
|
"resetPwd": {
|
||||||
|
"title": "Reset password",
|
||||||
|
"loginButton": "Log in",
|
||||||
|
"signupButton": "Sign up",
|
||||||
|
"resetButton": "Reset",
|
||||||
|
"goback": "Go back"
|
||||||
|
},
|
||||||
|
"signup": {
|
||||||
|
"title": "Sign up",
|
||||||
|
"username": "Username",
|
||||||
|
"email": "Email",
|
||||||
|
"password": "Password",
|
||||||
|
"confirmPassword": "Confirm Password",
|
||||||
|
"usernamePlaceholder": "Enter your username",
|
||||||
|
"emailPlaceholder": "Enter your email",
|
||||||
|
"passwordPlaceholder": "Enter your password",
|
||||||
|
"confirmPasswordPlaceholder": "Confirm your password",
|
||||||
|
"agreeTerms": "I agree with the",
|
||||||
|
"terms": "Terms",
|
||||||
|
"and": "and",
|
||||||
|
"privacyPolicy": "Privacy Policy",
|
||||||
|
"signupButton": "Sign Up",
|
||||||
|
"haveAccount": "Have an account?",
|
||||||
|
"login": "Login",
|
||||||
|
"verifyCode": "Verification Code",
|
||||||
|
"verifyCodePlaceholder": "Enter 6-digit code",
|
||||||
|
"sendCode": "Send Code",
|
||||||
|
"resendCode": "Resend",
|
||||||
|
"codeExpireTime": "Code will expire in"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
i18n/locales/en/personal.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"photoCount": "Photos",
|
||||||
|
"videoCount": "Videos",
|
||||||
|
"memoryData": "Storage"
|
||||||
|
},
|
||||||
|
"pro": {
|
||||||
|
"title": "Subscribe to MemoWark",
|
||||||
|
"subtitle": "Let the love of family flow again like memories in photos"
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"syncThirdPartyData": "Sync WeChat Data",
|
||||||
|
"myGallery": "My Gallery",
|
||||||
|
"myVideo": "My Created",
|
||||||
|
"AIVideo": "AI Video",
|
||||||
|
"setting": "General Settings",
|
||||||
|
"recommendMemoWake": "Recommend MemoWake",
|
||||||
|
"fiveStarReview": "Rate 5 Stars",
|
||||||
|
"contactUs": "Contact Support",
|
||||||
|
"qualification": "Certifications",
|
||||||
|
"otherAgreement": "Other Agreements"
|
||||||
|
},
|
||||||
|
"personalInfo": {
|
||||||
|
"title": "Personal Information",
|
||||||
|
"avatar": "Avatar",
|
||||||
|
"nickname": "Nickname",
|
||||||
|
"userId": "User ID",
|
||||||
|
"email": "Email",
|
||||||
|
"phone": "Phone",
|
||||||
|
"chat": "WeChat",
|
||||||
|
"changeNickname": "Change Nickname",
|
||||||
|
"nicknameSave": "Save",
|
||||||
|
"phoneBind": "Bind Phone Number"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
i18n/locales/en/upload.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"upload": {
|
||||||
|
"upload": "Upload",
|
||||||
|
"notify": "Notify me",
|
||||||
|
"second": "Lots of people are creating videos right now, so this might take a bit. We'll notify you when your video is ready.",
|
||||||
|
"download": "Download",
|
||||||
|
"processing": "AI Processing",
|
||||||
|
"failed": "AI processing failed",
|
||||||
|
"failedTitle": "AI processing failed",
|
||||||
|
"mustUpload": "Please upload a file first",
|
||||||
|
"mustUploadTitle": "Please upload a file first",
|
||||||
|
"downloadFailedTitle": "Download failed",
|
||||||
|
"downloadFailed": "Please check your network or resource permissions",
|
||||||
|
"mustLogin": "Please login first",
|
||||||
|
"task": "Task",
|
||||||
|
"taskName": "dynamic family portrait",
|
||||||
|
"taskStatus": "processing",
|
||||||
|
"noName": "No Name"
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"title": "My Memory",
|
||||||
|
"uploadTitle": "Upload Material",
|
||||||
|
"uploadFromChat": "Upload from WeChat",
|
||||||
|
"uploadFromDevice": "Upload from Device",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"return": "Return"
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"works": "Push",
|
||||||
|
"createText": "Creation",
|
||||||
|
"create": {
|
||||||
|
"dynamic": "Dynamic Family Portrait"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
325
i18n/locales/zh.json
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
{
|
||||||
|
"navbar": {
|
||||||
|
"useCases": "使用案例",
|
||||||
|
"faq": "常见问题",
|
||||||
|
"getFree": "免费获取FairClip"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"english": "English",
|
||||||
|
"chinese": "简体中文"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"search": "搜索...",
|
||||||
|
"title": "FairClip - AI驱动的视频魔法",
|
||||||
|
"name":"MemoWake",
|
||||||
|
"homepage":"首页",
|
||||||
|
"signup":"注册",
|
||||||
|
"login":"登录"
|
||||||
|
},
|
||||||
|
"imagePreview": {
|
||||||
|
"zoomOut": "缩小",
|
||||||
|
"zoomIn": "放大",
|
||||||
|
"resetView": "重置视图",
|
||||||
|
"rotateCounterClockwise": "逆时针旋转",
|
||||||
|
"rotateClockwise": "顺时针旋转",
|
||||||
|
"close": "关闭预览",
|
||||||
|
"previous": "上一张图片",
|
||||||
|
"next": "下一张图片",
|
||||||
|
"imageCounter": "图片 {{current}}/{{total}}",
|
||||||
|
"preview": "预览图片"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"welcome": "Creativity, Simplified with ",
|
||||||
|
"subtitle": "FairClip是一款AI视频剪辑工具,能够分析原始视频素材,智能处理画面与音频,能用普通视频素材编辑制作成电影级的高质量专业内容",
|
||||||
|
"requestDemo": "申请演示",
|
||||||
|
"useCases": "使用案例",
|
||||||
|
"travelVlog": "旅行Vlog",
|
||||||
|
"travelVlogDesc": "AI驱动的旅行视频编辑",
|
||||||
|
"faq": "常见问题解答",
|
||||||
|
"accordion1": "常见问题1",
|
||||||
|
"accordion2": "常见问题2",
|
||||||
|
"accordion3": "常见问题3",
|
||||||
|
"accordionContent": "首先下载并注册账号,然后登录,接着注册账号并登录,最后注册帐号并登录。首先下载并注册账号,然后登录,接着注册账号并登录,最后注册帐号并登录。首先下载并注册账号,然后登录,接着注册账号并登录,最后注册帐号并登录。",
|
||||||
|
"exploreUseCase": "探索使用案例"
|
||||||
|
},
|
||||||
|
"useCase": {
|
||||||
|
"request_demo": {
|
||||||
|
"title": "感兴趣?",
|
||||||
|
"description": "正在内测中,欢迎加入等待列表。",
|
||||||
|
"pause": "暂停",
|
||||||
|
"resume": "播放",
|
||||||
|
"reset": "重置"
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"select_material": "选择素材",
|
||||||
|
"material_processing": "素材处理",
|
||||||
|
"coarse_editing": "素材粗剪",
|
||||||
|
"streamlining": "精简包装",
|
||||||
|
"exporting": "导出成片",
|
||||||
|
"upload_meterial": "上传素材",
|
||||||
|
"video_analysis": "视频解析",
|
||||||
|
"video_tagging": "视频标签",
|
||||||
|
"video_classification": "视频分类",
|
||||||
|
"select_mainline": "选择主线",
|
||||||
|
"select_content_structure": "选择内容结构",
|
||||||
|
"script_creation": "脚本创作",
|
||||||
|
"strategy_matching": "策略匹配",
|
||||||
|
"material_arrangement": "素材排列",
|
||||||
|
"video_coarse_editing": "视频粗剪",
|
||||||
|
"transition_effect_processing": "过渡效果处理",
|
||||||
|
"music_and_sound_effect_processing": "音乐和音效处理",
|
||||||
|
"subtitle_processing": "字幕处理",
|
||||||
|
"color_adjustment": "颜色调整",
|
||||||
|
"video_streamlining": "视频精剪包装",
|
||||||
|
"bitrate_adjustment": "比特率调整",
|
||||||
|
"video_rendering": "视频渲染",
|
||||||
|
"export_publish": "导出发布",
|
||||||
|
"complete": "完成"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"message1": {
|
||||||
|
"content": "欢迎使用FairClip!我将协助您创建一个旅行Vlog视频。我将使用最合适的短片,采用您偏好的创意叙事、内容结构和编辑风格,完成素材的粗剪和精简包装。"
|
||||||
|
},
|
||||||
|
"process_material": {
|
||||||
|
"message1": "Fairclip正在执行素材处理任务,请耐心等待...",
|
||||||
|
"message2": "素材解析已完成。",
|
||||||
|
"message3": "素材切片与分类已完成。",
|
||||||
|
"stats": {
|
||||||
|
"title": "处理统计",
|
||||||
|
"videos_processed": "成功解析",
|
||||||
|
"total_duration": "总时长",
|
||||||
|
"task_time": "任务耗时",
|
||||||
|
"avg_speed": "平均速度",
|
||||||
|
"videos_count": "{{count}}条素材",
|
||||||
|
"minutes": "{{count}}分钟",
|
||||||
|
"seconds_per_video": "{{count}}秒/素材"
|
||||||
|
},
|
||||||
|
"clips_set": "切片素材集",
|
||||||
|
"complete_match_clip_strategy": "已为当前任务智能匹配剪辑策略。",
|
||||||
|
"video_editing_strategy": "视频剪辑策略",
|
||||||
|
"mainline": "创作主线",
|
||||||
|
"content_structure": "内容结构",
|
||||||
|
"editing_style": "剪辑风格",
|
||||||
|
"select_mainline": "选择创作主线",
|
||||||
|
"select_content_structure": "选择内容结构",
|
||||||
|
"select_editing_style": "选择剪辑风格",
|
||||||
|
"material_first": "素材优先",
|
||||||
|
"travel_mix_cutting": "旅行混剪",
|
||||||
|
"film_style": "电影风格",
|
||||||
|
"video_editing_strategy_note": "* 案例中暂不可修改视频剪辑策略"
|
||||||
|
},
|
||||||
|
"coarse_editing": {
|
||||||
|
"title": "正在执行素材粗剪工作,请耐心等待...",
|
||||||
|
"complete_script": "已完成智能脚本创作。",
|
||||||
|
"script_creation": "脚本创作",
|
||||||
|
"view_full_script": "查看完整脚本",
|
||||||
|
"complete_arrangement": "已完成素材片段编排。"
|
||||||
|
},
|
||||||
|
"detail_editing": {
|
||||||
|
"title": "正在执行精剪包装工作,请耐心等待...",
|
||||||
|
"transition": {
|
||||||
|
"message1": "已增加{{count}}个转场,您的视频更加丝滑。",
|
||||||
|
"title": "转场"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"title": "输出设置",
|
||||||
|
"resolution": "分辨率",
|
||||||
|
"aspect_ratio": "画面比例",
|
||||||
|
"format": "格式",
|
||||||
|
"estimated_size": "预计大小",
|
||||||
|
"rendering": "渲染视频中...",
|
||||||
|
"render_finished": "渲染完成",
|
||||||
|
"share": "分享",
|
||||||
|
"download": "下载"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"upload_material": "上传素材",
|
||||||
|
"upload_disable_reason": "案例中上传素材暂时不可用",
|
||||||
|
"dragAndDropFiles": "拖放文件到此处",
|
||||||
|
"dropFilesHere": "放置文件在此处",
|
||||||
|
"orClickToUpload": "或点击上传",
|
||||||
|
"supportedFormats": "支持的格式",
|
||||||
|
"maxFileSize": "最大文件大小",
|
||||||
|
"uploadingFiles": "上传文件中",
|
||||||
|
"addMoreFiles": "添加更多",
|
||||||
|
"upload": "上传",
|
||||||
|
"uploading": "上传中...",
|
||||||
|
"presetMaterials": "预设素材组",
|
||||||
|
"useThisSet": "使用此组",
|
||||||
|
"chengduTravelSet": "成都旅行素材组",
|
||||||
|
"beijingTravelSet": "北京旅行素材组",
|
||||||
|
"shanghaiTravelSet": "上海旅行素材组",
|
||||||
|
"or": "或者"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"faqs": {
|
||||||
|
"title": "的常见问题",
|
||||||
|
"mobile_title": "常见问题",
|
||||||
|
"title1": "什么是Fairclip?",
|
||||||
|
"content1": "Fairclip是用于构建高性能、可访问性和美丽的网络体验的视频编辑平台。",
|
||||||
|
"title2": "如何申请开源折扣?",
|
||||||
|
"content2": "开源折扣适用于所有正在构建开源项目的用户。您可以通过发送电子邮件到support@fairclip.cn申请折扣。",
|
||||||
|
"title3": "我可以为我的自由职业项目使用Fairclip吗?",
|
||||||
|
"content3": "是的,您可以为您的自由职业项目使用Fairclip。您可以在我们网站上购买Freelancer许可证。",
|
||||||
|
"contact_us": "联系我们"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"login": {
|
||||||
|
"title": "登录",
|
||||||
|
"account": "账号",
|
||||||
|
"email": "邮箱",
|
||||||
|
"password": "密码",
|
||||||
|
"emailPlaceholder": "请输入您的邮箱",
|
||||||
|
"passwordPlaceholder": "请输入您的密码",
|
||||||
|
"rememberMe": "记住我",
|
||||||
|
"forgotPassword": "忘记密码?",
|
||||||
|
"loginButton": "登录",
|
||||||
|
"createAccount": "创建账号",
|
||||||
|
"accountPlaceholder": "请输入您的账号或邮箱",
|
||||||
|
"signUpMessage": "还没有账号?",
|
||||||
|
"signUp": "注册"
|
||||||
|
},
|
||||||
|
"agree": {
|
||||||
|
"text": "注册即表示您同意我们的",
|
||||||
|
"terms": "服务条款",
|
||||||
|
"join": "&",
|
||||||
|
"privacyPolicy": "隐私政策"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"welcome": "欢迎来到FairClip",
|
||||||
|
"slogan": "保存你的爱、笑声和珍贵时刻",
|
||||||
|
"notice": "s 回到鲜活的家庭肖像"
|
||||||
|
},
|
||||||
|
"forgetPwd": {
|
||||||
|
"title": "忘记密码",
|
||||||
|
"emailPlaceholder": "请输入您的邮箱",
|
||||||
|
"sendEmailBtn": "发送邮件",
|
||||||
|
"signupButton": "注册",
|
||||||
|
"goback": "返回登录"
|
||||||
|
},
|
||||||
|
"resetPwd": {
|
||||||
|
"title": "重置密码",
|
||||||
|
"loginButton": "登录",
|
||||||
|
"signupButton": "注册",
|
||||||
|
"resetButton": "重置",
|
||||||
|
"goback": "返回登录"
|
||||||
|
},
|
||||||
|
"signup": {
|
||||||
|
"title": "注册",
|
||||||
|
"username": "用户名",
|
||||||
|
"email": "邮箱",
|
||||||
|
"password": "密码",
|
||||||
|
"confirmPassword": "确认密码",
|
||||||
|
"usernamePlaceholder": "请输入您的用户名",
|
||||||
|
"emailPlaceholder": "请输入您的邮箱",
|
||||||
|
"passwordPlaceholder": "请输入您的密码",
|
||||||
|
"confirmPasswordPlaceholder": "请确认您的密码",
|
||||||
|
"agreeTerms": "我同意",
|
||||||
|
"terms": "服务条款",
|
||||||
|
"and": "和",
|
||||||
|
"privacyPolicy": "隐私政策",
|
||||||
|
"signupButton": "注册",
|
||||||
|
"haveAccount": "已有帐号?",
|
||||||
|
"login": "登录",
|
||||||
|
"verifyCode": "验证码",
|
||||||
|
"verifyCodePlaceholder": "请输入6位验证码",
|
||||||
|
"sendCode": "发送验证码",
|
||||||
|
"resendCode": "重新发送",
|
||||||
|
"codeExpireTime": "验证码将在以下时间后过期"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"email": {
|
||||||
|
"required": "邮箱不能为空",
|
||||||
|
"invalid": "请输入有效的邮箱地址",
|
||||||
|
"missingAt": "邮箱必须包含@符号",
|
||||||
|
"missingDomain": "邮箱必须包含域名(例如:example.com)",
|
||||||
|
"missingUsername": "邮箱必须在@前包含用户名",
|
||||||
|
"tooShort": "邮箱地址太短",
|
||||||
|
"tooLong": "邮箱地址太长"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"required": "密码不能为空",
|
||||||
|
"tooShort": "密码长度必须至少为6个字符",
|
||||||
|
"tooLong": "密码长度过长",
|
||||||
|
"noUppercase": "密码必须包含至少一个大写字母",
|
||||||
|
"noLowercase": "密码必须包含至少一个小写字母",
|
||||||
|
"noNumber": "密码必须包含至少一个数字",
|
||||||
|
"noSpecialChar": "密码必须包含至少一个特殊字符",
|
||||||
|
"mismatch": "两次输入的密码不一致"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"required": "用户名不能为空",
|
||||||
|
"invalidChars": "用户名只能包含字母、数字和汉字",
|
||||||
|
"tooShort": "用户名太短",
|
||||||
|
"tooLong": "用户名太长"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"required": "数字不能为空",
|
||||||
|
"notNumber": "请输入有效的数字",
|
||||||
|
"tooSmall": "数字太小",
|
||||||
|
"tooLarge": "数字太大",
|
||||||
|
"invalidFormat": "数字格式无效",
|
||||||
|
"decimalNotAllowed": "不允许小数",
|
||||||
|
"negativeNotAllowed": "不允许负数"
|
||||||
|
},
|
||||||
|
"verifyCode": {
|
||||||
|
"required": "验证码不能为空",
|
||||||
|
"invalidLength": "验证码必须为6位数字",
|
||||||
|
"invalidFormat": "验证码只能包含数字",
|
||||||
|
"expired": "验证码已过期,请重新获取",
|
||||||
|
"incorrect": "验证码不正确,请重试"
|
||||||
|
},
|
||||||
|
"terms": {
|
||||||
|
"required": "您必须同意服务条款和隐私政策"
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"invalidType": "文件类型无效。只允许视频文件。",
|
||||||
|
"tooLarge": "文件太大。最大大小为500MB。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ai_page_navbar": {
|
||||||
|
"title": "Memo Wake",
|
||||||
|
"login_in": "登录",
|
||||||
|
"login_out": "登出"
|
||||||
|
},
|
||||||
|
"cancel": "取消",
|
||||||
|
"continue": "继续",
|
||||||
|
"material": {
|
||||||
|
"set": {
|
||||||
|
"clips": "片段"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"close": "关闭",
|
||||||
|
"confirm": "确认",
|
||||||
|
"video_script": {
|
||||||
|
"title": "视频脚本",
|
||||||
|
"table": {
|
||||||
|
"scene_index": "场景序号",
|
||||||
|
"scene_name": "场景名称",
|
||||||
|
"shot_index": "镜头序号",
|
||||||
|
"shot_description": "镜头描述",
|
||||||
|
"shot_size": "镜头类型",
|
||||||
|
"shot_duration": "镜头时长",
|
||||||
|
"actor_line": "台词"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"coarseVideoCard": {
|
||||||
|
"description": "片段简介"
|
||||||
|
},
|
||||||
|
"EWS": "极远景",
|
||||||
|
"WS": "全景",
|
||||||
|
"FS": "全身镜头",
|
||||||
|
"MWS": "中远景",
|
||||||
|
"MS": "中景",
|
||||||
|
"MCU": "中近景",
|
||||||
|
"CU": "近景",
|
||||||
|
"ECU": "特写",
|
||||||
|
"2S": "双人镜头",
|
||||||
|
"OTS": "过肩镜头",
|
||||||
|
"POV": "主观镜头",
|
||||||
|
"CI": "插入镜头",
|
||||||
|
"CA": "切出镜头"
|
||||||
|
}
|
||||||
140
i18n/locales/zh/admin.json
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"dashboard": "仪表盘",
|
||||||
|
"users": "用户",
|
||||||
|
"settings": "设置",
|
||||||
|
"profile": "个人资料",
|
||||||
|
"logout": "退出登录",
|
||||||
|
"home": "首页",
|
||||||
|
"tasks": "任务",
|
||||||
|
"projects": "项目",
|
||||||
|
"analytics": "分析",
|
||||||
|
"reports": "报告",
|
||||||
|
"works": "模板",
|
||||||
|
"qualification": "协议",
|
||||||
|
"promptTable": {
|
||||||
|
"camera_direction": "镜头方向",
|
||||||
|
"camera_movement": "镜头运动",
|
||||||
|
"composition": "构图",
|
||||||
|
"music_description": "音乐描述",
|
||||||
|
"perspective": "视角",
|
||||||
|
"scene_name": "场景名称",
|
||||||
|
"scene_sequence": "场景序号",
|
||||||
|
"shot_description": "镜头描述",
|
||||||
|
"shot_duration": "镜头时长",
|
||||||
|
"shot_name": "镜头名称",
|
||||||
|
"shot_sequence": "镜头序号",
|
||||||
|
"shot_sizes": "景别",
|
||||||
|
"sound_effect": "音效",
|
||||||
|
"transition_in": "转场",
|
||||||
|
"voice_over": "旁白"
|
||||||
|
},
|
||||||
|
"formwork": {
|
||||||
|
"works": {
|
||||||
|
"title": "作品模板",
|
||||||
|
"id": "模板id",
|
||||||
|
"name": "模板名称",
|
||||||
|
"description": "模板描述",
|
||||||
|
"model": "模型",
|
||||||
|
"count": "样片数量",
|
||||||
|
"version": "版本号",
|
||||||
|
"prompt": "prompt",
|
||||||
|
"updated_at": "更新时间",
|
||||||
|
"operate": "操作",
|
||||||
|
"view": "查看",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除"
|
||||||
|
},
|
||||||
|
"ability": {
|
||||||
|
"title": "能力模板"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"materialList": "样片list",
|
||||||
|
"version": "当前版本:",
|
||||||
|
"model": "当前模型:",
|
||||||
|
"setVersion": "设为线上版本",
|
||||||
|
"save": "保存",
|
||||||
|
"imagePreview": "图片预览",
|
||||||
|
"videoPreview": "视频预览",
|
||||||
|
"clipPreview": "clip预览",
|
||||||
|
"clipList": "clip片段 list",
|
||||||
|
"caption": "caption/tag预览",
|
||||||
|
"prompt": "prompt调试",
|
||||||
|
"promptPreview": "结果预览",
|
||||||
|
"goodCase": "good case",
|
||||||
|
"badCase": "bad case",
|
||||||
|
"upload": "上传",
|
||||||
|
"run": "运行",
|
||||||
|
"view": "查看",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除",
|
||||||
|
"uploadMaterialList": "上传素材",
|
||||||
|
"uploadMaterialListTitle": "选择样片",
|
||||||
|
"uploadMaterialListoption": "没有想要的样片?来上传新素材吧",
|
||||||
|
"confirm": "确定"
|
||||||
|
},
|
||||||
|
"tasks_page": {
|
||||||
|
"status": {
|
||||||
|
"Created": "创建中",
|
||||||
|
"Processing": "处理中",
|
||||||
|
"Completed": "已完成",
|
||||||
|
"Failed": "已失败",
|
||||||
|
"Pending": "待审核",
|
||||||
|
"Approved": "通过",
|
||||||
|
"Rejected": "拒绝",
|
||||||
|
"Waiting": "等待中"
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"total": "总任务数",
|
||||||
|
"pending": "待操作",
|
||||||
|
"processing": "运行中",
|
||||||
|
"success": "成功",
|
||||||
|
"failed": "失败",
|
||||||
|
"queued": "排队中"
|
||||||
|
},
|
||||||
|
"task_list": "任务列表",
|
||||||
|
"no_preview": "无预览",
|
||||||
|
"no_review_required": "无需审核",
|
||||||
|
"view_details": "查看详情",
|
||||||
|
"approve": "通过审核",
|
||||||
|
"reject": "拒绝",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"no_tasks": "暂无任务数据",
|
||||||
|
"items_per_page": "每页显示",
|
||||||
|
"items": "条",
|
||||||
|
"fetch_error": "获取任务列表失败",
|
||||||
|
"thumbnail": "缩略图",
|
||||||
|
"search": "搜索",
|
||||||
|
"search_placeholder": "输入任务ID搜索",
|
||||||
|
"reset": "重置",
|
||||||
|
"filter": {
|
||||||
|
"status": "状态",
|
||||||
|
"all": "全部"
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"preview": "预览",
|
||||||
|
"task_id": "任务ID",
|
||||||
|
"status": "状态",
|
||||||
|
"method": "生成方式",
|
||||||
|
"review": "审核状态",
|
||||||
|
"actions": "操作"
|
||||||
|
},
|
||||||
|
"download_origin_file": "下载原始文件",
|
||||||
|
"output_video": "输出视频",
|
||||||
|
"video_not_supported": "您的浏览器不支持视频播放",
|
||||||
|
"upload_result": "上传结果文件",
|
||||||
|
"upload_result_title": "上传结果文件",
|
||||||
|
"upload_result_description": "请上传一个结果视频文件,该文件将作为任务的最终输出结果。上传完成后,任务将被标记为拒绝状态。",
|
||||||
|
"upload_and_approve": "上传并批准",
|
||||||
|
"upload_error": "上传错误",
|
||||||
|
"no_file_selected": "请选择要上传的文件",
|
||||||
|
"upload_error_description": "上传结果文件时发生错误",
|
||||||
|
"reject_success": "拒绝成功",
|
||||||
|
"approve_success": "批准成功",
|
||||||
|
"update_status_error": "更新状态失败"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"close": "关闭",
|
||||||
|
"cancel": "取消"
|
||||||
|
}
|
||||||
|
}
|
||||||
112
i18n/locales/zh/common.json
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"navbar": {
|
||||||
|
"useCases": "使用案例",
|
||||||
|
"faq": "常见问题",
|
||||||
|
"getFree": "免费获取FairClip"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"english": "English",
|
||||||
|
"chinese": "简体中文"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"search": "搜索...",
|
||||||
|
"title": "MemoWake - AI驱动的家庭「视频记忆」",
|
||||||
|
"name":"MemoWake",
|
||||||
|
"homepage":"首页",
|
||||||
|
"signup":"注册",
|
||||||
|
"login":"登录",
|
||||||
|
"trade": "沪ICP备2023032876号-4",
|
||||||
|
"logout":"退出登录",
|
||||||
|
"self":"个人中心"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"welcome": "欢迎来到 MemoWake~",
|
||||||
|
"slogan": "让你的爱、欢笑和珍贵时刻永存",
|
||||||
|
"notice": "即将返回动态全家福"
|
||||||
|
},
|
||||||
|
"imagePreview": {
|
||||||
|
"zoomOut": "缩小",
|
||||||
|
"zoomIn": "放大",
|
||||||
|
"resetView": "重置视图",
|
||||||
|
"rotateCounterClockwise": "逆时针旋转",
|
||||||
|
"rotateClockwise": "顺时针旋转",
|
||||||
|
"close": "关闭预览",
|
||||||
|
"previous": "上一张图片",
|
||||||
|
"next": "下一张图片",
|
||||||
|
"imageCounter": "图片 {{current}}/{{total}}",
|
||||||
|
"preview": "预览图片"
|
||||||
|
},
|
||||||
|
"fileUploader": {
|
||||||
|
"uploadingFiles": "上传文件...",
|
||||||
|
"selectedFiles": "已选择文件",
|
||||||
|
"addMoreFiles": "添加更多文件",
|
||||||
|
"clearAll": "清空所有文件",
|
||||||
|
"upload": "上传",
|
||||||
|
"uploading": "上传中...",
|
||||||
|
"dragAndDropFiles": "拖放文件到此处",
|
||||||
|
"dropFilesHere": "放置文件在此处",
|
||||||
|
"orClickToUpload": "或者点击上传",
|
||||||
|
"supportedFormats": "支持的格式",
|
||||||
|
"maxFileSize": "最大文件大小",
|
||||||
|
"uploadProgress": "上传进度",
|
||||||
|
"remove": "删除",
|
||||||
|
"uploadedFiles": "已上传文件",
|
||||||
|
"clickToReplaceFile": "点击替换文件",
|
||||||
|
"invalidFileTitle": "不支持的文件格式",
|
||||||
|
"fileTooLargeTitle": "文件过大",
|
||||||
|
"uploadErrorTitle": "上传失败",
|
||||||
|
"fileTooSmallTitle": "文件过小",
|
||||||
|
"fileTooSmall": "文件过小,请上传大于300像素的图片"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"file": {
|
||||||
|
"invalidType": "不支持的文件格式",
|
||||||
|
"tooLarge": "文件过大"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"required": "邮箱不能为空",
|
||||||
|
"invalid": "请输入有效的邮箱地址",
|
||||||
|
"missingAt": "邮箱必须包含@符号",
|
||||||
|
"missingDomain": "邮箱必须包含域名(例如:example.com)",
|
||||||
|
"missingUsername": "邮箱必须在@前包含用户名",
|
||||||
|
"tooShort": "邮箱地址太短",
|
||||||
|
"tooLong": "邮箱地址太长"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"required": "密码不能为空",
|
||||||
|
"tooShort": "密码长度必须至少为6个字符",
|
||||||
|
"tooLong": "密码长度过长",
|
||||||
|
"noUppercase": "密码必须包含至少一个大写字母",
|
||||||
|
"noLowercase": "密码必须包含至少一个小写字母",
|
||||||
|
"noNumber": "密码必须包含至少一个数字",
|
||||||
|
"noSpecialChar": "密码必须包含至少一个特殊字符",
|
||||||
|
"mismatch": "两次输入的密码不一致"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"required": "用户名不能为空",
|
||||||
|
"invalidChars": "用户名只能包含字母、数字和汉字",
|
||||||
|
"tooShort": "用户名太短",
|
||||||
|
"tooLong": "用户名太长"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"required": "数字不能为空",
|
||||||
|
"notNumber": "请输入有效的数字",
|
||||||
|
"tooSmall": "数字太小",
|
||||||
|
"tooLarge": "数字太大",
|
||||||
|
"invalidFormat": "数字格式无效",
|
||||||
|
"decimalNotAllowed": "不允许小数",
|
||||||
|
"negativeNotAllowed": "不允许负数"
|
||||||
|
},
|
||||||
|
"verifyCode": {
|
||||||
|
"required": "验证码不能为空",
|
||||||
|
"invalidLength": "验证码必须为6位数字",
|
||||||
|
"invalidFormat": "验证码只能包含数字",
|
||||||
|
"expired": "验证码已过期,请重新获取",
|
||||||
|
"incorrect": "验证码不正确,请重试"
|
||||||
|
},
|
||||||
|
"terms": {
|
||||||
|
"required": "您必须同意服务条款和隐私政策"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loading": "加载中..."
|
||||||
|
}
|
||||||
3
i18n/locales/zh/example.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"title": "示例"
|
||||||
|
}
|
||||||
325
i18n/locales/zh/fairclip.json
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
{
|
||||||
|
"navbar": {
|
||||||
|
"useCases": "使用案例",
|
||||||
|
"faq": "常见问题",
|
||||||
|
"getFree": "免费获取FairClip"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"english": "English",
|
||||||
|
"chinese": "简体中文"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"search": "搜索...",
|
||||||
|
"title": "FairClip - AI驱动的视频魔法",
|
||||||
|
"name":"MemoWake",
|
||||||
|
"homepage":"首页",
|
||||||
|
"signup":"注册",
|
||||||
|
"login":"登录"
|
||||||
|
},
|
||||||
|
"imagePreview": {
|
||||||
|
"zoomOut": "缩小",
|
||||||
|
"zoomIn": "放大",
|
||||||
|
"resetView": "重置视图",
|
||||||
|
"rotateCounterClockwise": "逆时针旋转",
|
||||||
|
"rotateClockwise": "顺时针旋转",
|
||||||
|
"close": "关闭预览",
|
||||||
|
"previous": "上一张图片",
|
||||||
|
"next": "下一张图片",
|
||||||
|
"imageCounter": "图片 {{current}}/{{total}}",
|
||||||
|
"preview": "预览图片"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"welcome": "Creativity, Simplified with ",
|
||||||
|
"subtitle": "FairClip是一款AI视频剪辑工具,能够分析原始视频素材,智能处理画面与音频,能用普通视频素材编辑制作成电影级的高质量专业内容",
|
||||||
|
"requestDemo": "申请演示",
|
||||||
|
"useCases": "使用案例",
|
||||||
|
"travelVlog": "旅行Vlog",
|
||||||
|
"travelVlogDesc": "AI驱动的旅行视频编辑",
|
||||||
|
"faq": "常见问题解答",
|
||||||
|
"accordion1": "常见问题1",
|
||||||
|
"accordion2": "常见问题2",
|
||||||
|
"accordion3": "常见问题3",
|
||||||
|
"accordionContent": "首先下载并注册账号,然后登录,接着注册账号并登录,最后注册帐号并登录。首先下载并注册账号,然后登录,接着注册账号并登录,最后注册帐号并登录。首先下载并注册账号,然后登录,接着注册账号并登录,最后注册帐号并登录。",
|
||||||
|
"exploreUseCase": "探索使用案例"
|
||||||
|
},
|
||||||
|
"useCase": {
|
||||||
|
"request_demo": {
|
||||||
|
"title": "感兴趣?",
|
||||||
|
"description": "正在内测中,欢迎加入等待列表。",
|
||||||
|
"pause": "暂停",
|
||||||
|
"resume": "播放",
|
||||||
|
"reset": "重置"
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"select_material": "选择素材",
|
||||||
|
"material_processing": "素材处理",
|
||||||
|
"coarse_editing": "素材粗剪",
|
||||||
|
"streamlining": "精简包装",
|
||||||
|
"exporting": "导出成片",
|
||||||
|
"upload_meterial": "上传素材",
|
||||||
|
"video_analysis": "视频解析",
|
||||||
|
"video_tagging": "视频标签",
|
||||||
|
"video_classification": "视频分类",
|
||||||
|
"select_mainline": "选择主线",
|
||||||
|
"select_content_structure": "选择内容结构",
|
||||||
|
"script_creation": "脚本创作",
|
||||||
|
"strategy_matching": "策略匹配",
|
||||||
|
"material_arrangement": "素材排列",
|
||||||
|
"video_coarse_editing": "视频粗剪",
|
||||||
|
"transition_effect_processing": "过渡效果处理",
|
||||||
|
"music_and_sound_effect_processing": "音乐和音效处理",
|
||||||
|
"subtitle_processing": "字幕处理",
|
||||||
|
"color_adjustment": "颜色调整",
|
||||||
|
"video_streamlining": "视频精剪包装",
|
||||||
|
"bitrate_adjustment": "比特率调整",
|
||||||
|
"video_rendering": "视频渲染",
|
||||||
|
"export_publish": "导出发布",
|
||||||
|
"complete": "完成"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"message1": {
|
||||||
|
"content": "欢迎使用FairClip!我将协助您创建一个旅行Vlog视频。我将使用最合适的短片,采用您偏好的创意叙事、内容结构和编辑风格,完成素材的粗剪和精简包装。"
|
||||||
|
},
|
||||||
|
"process_material": {
|
||||||
|
"message1": "Fairclip正在执行素材处理任务,请耐心等待...",
|
||||||
|
"message2": "素材解析已完成。",
|
||||||
|
"message3": "素材切片与分类已完成。",
|
||||||
|
"stats": {
|
||||||
|
"title": "处理统计",
|
||||||
|
"videos_processed": "成功解析",
|
||||||
|
"total_duration": "总时长",
|
||||||
|
"task_time": "任务耗时",
|
||||||
|
"avg_speed": "平均速度",
|
||||||
|
"videos_count": "{{count}}条素材",
|
||||||
|
"minutes": "{{count}}分钟",
|
||||||
|
"seconds_per_video": "{{count}}秒/素材"
|
||||||
|
},
|
||||||
|
"clips_set": "切片素材集",
|
||||||
|
"complete_match_clip_strategy": "已为当前任务智能匹配剪辑策略。",
|
||||||
|
"video_editing_strategy": "视频剪辑策略",
|
||||||
|
"mainline": "创作主线",
|
||||||
|
"content_structure": "内容结构",
|
||||||
|
"editing_style": "剪辑风格",
|
||||||
|
"select_mainline": "选择创作主线",
|
||||||
|
"select_content_structure": "选择内容结构",
|
||||||
|
"select_editing_style": "选择剪辑风格",
|
||||||
|
"material_first": "素材优先",
|
||||||
|
"travel_mix_cutting": "旅行混剪",
|
||||||
|
"film_style": "电影风格",
|
||||||
|
"video_editing_strategy_note": "* 案例中暂不可修改视频剪辑策略"
|
||||||
|
},
|
||||||
|
"coarse_editing": {
|
||||||
|
"title": "正在执行素材粗剪工作,请耐心等待...",
|
||||||
|
"complete_script": "已完成智能脚本创作。",
|
||||||
|
"script_creation": "脚本创作",
|
||||||
|
"view_full_script": "查看完整脚本",
|
||||||
|
"complete_arrangement": "已完成素材片段编排。"
|
||||||
|
},
|
||||||
|
"detail_editing": {
|
||||||
|
"title": "正在执行精剪包装工作,请耐心等待...",
|
||||||
|
"transition": {
|
||||||
|
"message1": "已增加{{count}}个转场,您的视频更加丝滑。",
|
||||||
|
"title": "转场"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"title": "输出设置",
|
||||||
|
"resolution": "分辨率",
|
||||||
|
"aspect_ratio": "画面比例",
|
||||||
|
"format": "格式",
|
||||||
|
"estimated_size": "预计大小",
|
||||||
|
"rendering": "渲染视频中...",
|
||||||
|
"render_finished": "渲染完成",
|
||||||
|
"share": "分享",
|
||||||
|
"download": "下载"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"upload_material": "上传素材",
|
||||||
|
"upload_disable_reason": "案例中上传素材暂时不可用",
|
||||||
|
"dragAndDropFiles": "拖放文件到此处",
|
||||||
|
"dropFilesHere": "放置文件在此处",
|
||||||
|
"orClickToUpload": "或点击上传",
|
||||||
|
"supportedFormats": "支持的格式",
|
||||||
|
"maxFileSize": "最大文件大小",
|
||||||
|
"uploadingFiles": "上传文件中",
|
||||||
|
"addMoreFiles": "添加更多",
|
||||||
|
"upload": "上传",
|
||||||
|
"uploading": "上传中...",
|
||||||
|
"presetMaterials": "预设素材组",
|
||||||
|
"useThisSet": "使用此组",
|
||||||
|
"chengduTravelSet": "成都旅行素材组",
|
||||||
|
"beijingTravelSet": "北京旅行素材组",
|
||||||
|
"shanghaiTravelSet": "上海旅行素材组",
|
||||||
|
"or": "或者"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"faqs": {
|
||||||
|
"title": "的常见问题",
|
||||||
|
"mobile_title": "常见问题",
|
||||||
|
"title1": "什么是Fairclip?",
|
||||||
|
"content1": "Fairclip是用于构建高性能、可访问性和美丽的网络体验的视频编辑平台。",
|
||||||
|
"title2": "如何申请开源折扣?",
|
||||||
|
"content2": "开源折扣适用于所有正在构建开源项目的用户。您可以通过发送电子邮件到support@fairclip.cn申请折扣。",
|
||||||
|
"title3": "我可以为我的自由职业项目使用Fairclip吗?",
|
||||||
|
"content3": "是的,您可以为您的自由职业项目使用Fairclip。您可以在我们网站上购买Freelancer许可证。",
|
||||||
|
"contact_us": "联系我们"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"login": {
|
||||||
|
"title": "登录",
|
||||||
|
"account": "账号",
|
||||||
|
"email": "邮箱",
|
||||||
|
"password": "密码",
|
||||||
|
"emailPlaceholder": "请输入您的邮箱",
|
||||||
|
"passwordPlaceholder": "请输入您的密码",
|
||||||
|
"rememberMe": "记住我",
|
||||||
|
"forgotPassword": "忘记密码?",
|
||||||
|
"loginButton": "登录",
|
||||||
|
"createAccount": "创建账号",
|
||||||
|
"accountPlaceholder": "请输入您的账号或邮箱",
|
||||||
|
"signUpMessage": "还没有账号?",
|
||||||
|
"signUp": "注册"
|
||||||
|
},
|
||||||
|
"agree": {
|
||||||
|
"text": "注册即表示您同意我们的",
|
||||||
|
"terms": "服务条款",
|
||||||
|
"join": "&",
|
||||||
|
"privacyPolicy": "隐私政策"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"welcome": "欢迎来到FairClip",
|
||||||
|
"slogan": "保存你的爱、笑声和珍贵时刻",
|
||||||
|
"notice": "s 回到鲜活的家庭肖像"
|
||||||
|
},
|
||||||
|
"forgetPwd": {
|
||||||
|
"title": "忘记密码",
|
||||||
|
"emailPlaceholder": "请输入您的邮箱",
|
||||||
|
"sendEmailBtn": "发送邮件",
|
||||||
|
"signupButton": "注册",
|
||||||
|
"goback": "返回登录"
|
||||||
|
},
|
||||||
|
"resetPwd": {
|
||||||
|
"title": "重置密码",
|
||||||
|
"loginButton": "登录",
|
||||||
|
"signupButton": "注册",
|
||||||
|
"resetButton": "重置",
|
||||||
|
"goback": "返回登录"
|
||||||
|
},
|
||||||
|
"signup": {
|
||||||
|
"title": "注册",
|
||||||
|
"username": "用户名",
|
||||||
|
"email": "邮箱",
|
||||||
|
"password": "密码",
|
||||||
|
"confirmPassword": "确认密码",
|
||||||
|
"usernamePlaceholder": "请输入您的用户名",
|
||||||
|
"emailPlaceholder": "请输入您的邮箱",
|
||||||
|
"passwordPlaceholder": "请输入您的密码",
|
||||||
|
"confirmPasswordPlaceholder": "请确认您的密码",
|
||||||
|
"agreeTerms": "我同意",
|
||||||
|
"terms": "服务条款",
|
||||||
|
"and": "和",
|
||||||
|
"privacyPolicy": "隐私政策",
|
||||||
|
"signupButton": "注册",
|
||||||
|
"haveAccount": "已有帐号?",
|
||||||
|
"login": "登录",
|
||||||
|
"verifyCode": "验证码",
|
||||||
|
"verifyCodePlaceholder": "请输入6位验证码",
|
||||||
|
"sendCode": "发送验证码",
|
||||||
|
"resendCode": "重新发送",
|
||||||
|
"codeExpireTime": "验证码将在以下时间后过期"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"email": {
|
||||||
|
"required": "邮箱不能为空",
|
||||||
|
"invalid": "请输入有效的邮箱地址",
|
||||||
|
"missingAt": "邮箱必须包含@符号",
|
||||||
|
"missingDomain": "邮箱必须包含域名(例如:example.com)",
|
||||||
|
"missingUsername": "邮箱必须在@前包含用户名",
|
||||||
|
"tooShort": "邮箱地址太短",
|
||||||
|
"tooLong": "邮箱地址太长"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"required": "密码不能为空",
|
||||||
|
"tooShort": "密码长度必须至少为6个字符",
|
||||||
|
"tooLong": "密码长度过长",
|
||||||
|
"noUppercase": "密码必须包含至少一个大写字母",
|
||||||
|
"noLowercase": "密码必须包含至少一个小写字母",
|
||||||
|
"noNumber": "密码必须包含至少一个数字",
|
||||||
|
"noSpecialChar": "密码必须包含至少一个特殊字符",
|
||||||
|
"mismatch": "两次输入的密码不一致"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"required": "用户名不能为空",
|
||||||
|
"invalidChars": "用户名只能包含字母、数字和汉字",
|
||||||
|
"tooShort": "用户名太短",
|
||||||
|
"tooLong": "用户名太长"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"required": "数字不能为空",
|
||||||
|
"notNumber": "请输入有效的数字",
|
||||||
|
"tooSmall": "数字太小",
|
||||||
|
"tooLarge": "数字太大",
|
||||||
|
"invalidFormat": "数字格式无效",
|
||||||
|
"decimalNotAllowed": "不允许小数",
|
||||||
|
"negativeNotAllowed": "不允许负数"
|
||||||
|
},
|
||||||
|
"verifyCode": {
|
||||||
|
"required": "验证码不能为空",
|
||||||
|
"invalidLength": "验证码必须为6位数字",
|
||||||
|
"invalidFormat": "验证码只能包含数字",
|
||||||
|
"expired": "验证码已过期,请重新获取",
|
||||||
|
"incorrect": "验证码不正确,请重试"
|
||||||
|
},
|
||||||
|
"terms": {
|
||||||
|
"required": "您必须同意服务条款和隐私政策"
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"invalidType": "文件类型无效。只允许视频文件。",
|
||||||
|
"tooLarge": "文件太大。最大大小为500MB。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ai_page_navbar": {
|
||||||
|
"title": "Memo Wake",
|
||||||
|
"login_in": "登录",
|
||||||
|
"login_out": "登出"
|
||||||
|
},
|
||||||
|
"cancel": "取消",
|
||||||
|
"continue": "继续",
|
||||||
|
"material": {
|
||||||
|
"set": {
|
||||||
|
"clips": "片段"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"close": "关闭",
|
||||||
|
"confirm": "确认",
|
||||||
|
"video_script": {
|
||||||
|
"title": "视频脚本",
|
||||||
|
"table": {
|
||||||
|
"scene_index": "场景序号",
|
||||||
|
"scene_name": "场景名称",
|
||||||
|
"shot_index": "镜头序号",
|
||||||
|
"shot_description": "镜头描述",
|
||||||
|
"shot_size": "镜头类型",
|
||||||
|
"shot_duration": "镜头时长",
|
||||||
|
"actor_line": "台词"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"coarseVideoCard": {
|
||||||
|
"description": "片段简介"
|
||||||
|
},
|
||||||
|
"EWS": "极远景",
|
||||||
|
"WS": "全景",
|
||||||
|
"FS": "全身镜头",
|
||||||
|
"MWS": "中远景",
|
||||||
|
"MS": "中景",
|
||||||
|
"MCU": "中近景",
|
||||||
|
"CU": "近景",
|
||||||
|
"ECU": "特写",
|
||||||
|
"2S": "双人镜头",
|
||||||
|
"OTS": "过肩镜头",
|
||||||
|
"POV": "主观镜头",
|
||||||
|
"CI": "插入镜头",
|
||||||
|
"CA": "切出镜头"
|
||||||
|
}
|
||||||
18
i18n/locales/zh/landing.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"landing": {
|
||||||
|
"homepage": "首页",
|
||||||
|
"name": "MemoWake",
|
||||||
|
"login": "登录",
|
||||||
|
"signUp": "注册",
|
||||||
|
"photo": {
|
||||||
|
"title": "动态全家福",
|
||||||
|
"desc": "照片中冻结的回忆,像爱一样,再次在家人之间流动。",
|
||||||
|
"createButton": "创建你的动态全家福",
|
||||||
|
"more": "已有",
|
||||||
|
"than": "个家庭已经制作了动态全家福",
|
||||||
|
"powered": "由 fairclip 提供支持"
|
||||||
|
},
|
||||||
|
"gallery": "作品集",
|
||||||
|
"logout":"退出"
|
||||||
|
}
|
||||||
|
}
|
||||||
91
i18n/locales/zh/login.json
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"auth": {
|
||||||
|
"telLogin":{
|
||||||
|
"sendCode":"获取验证码",
|
||||||
|
"login":"登录",
|
||||||
|
"codePlaceholder":"请输入验证码",
|
||||||
|
"sending":"发送中...",
|
||||||
|
"codeSeconds":"秒",
|
||||||
|
"agree":"我已同意",
|
||||||
|
"terms":"《服务条款》",
|
||||||
|
"and":"和",
|
||||||
|
"privacyPolicy":"《隐私政策》",
|
||||||
|
"userAgreement":"《用户协议》",
|
||||||
|
"agreement":"并授权",
|
||||||
|
"getPhone":"获得本机号码",
|
||||||
|
"codeError":"发送验证码失败,请重试",
|
||||||
|
"phoneRequired":"请输入手机号",
|
||||||
|
"phoneInvalid":"请输入正确的手机号",
|
||||||
|
"codeRequired":"请输入验证码",
|
||||||
|
"codeInvalid":"验证码格式不正确",
|
||||||
|
"checkedRequired":"请勾选协议",
|
||||||
|
"loginError":"登录失败,请重试"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"title": "登录",
|
||||||
|
"account": "账号",
|
||||||
|
"email": "邮箱",
|
||||||
|
"password": "密码",
|
||||||
|
"emailPlaceholder": "请输入您的邮箱",
|
||||||
|
"passwordPlaceholder": "请输入您的密码",
|
||||||
|
"rememberMe": "记住我",
|
||||||
|
"forgotPassword": "忘记密码?",
|
||||||
|
"loginButton": "登录",
|
||||||
|
"createAccount": "创建账号",
|
||||||
|
"accountPlaceholder": "请输入您的账号或邮箱",
|
||||||
|
"signUpMessage": "还没有账号?",
|
||||||
|
"signUp": "注册",
|
||||||
|
"phoneLogin":"手机号登录"
|
||||||
|
},
|
||||||
|
"agree": {
|
||||||
|
"text": "注册即表示您同意我们的",
|
||||||
|
"terms": "服务条款",
|
||||||
|
"join": "&",
|
||||||
|
"privacyPolicy": "隐私政策"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"welcome": "欢迎来到FairClip",
|
||||||
|
"slogan": "保存你的爱、笑声和珍贵时刻",
|
||||||
|
"notice": "s 回到鲜活的家庭肖像"
|
||||||
|
},
|
||||||
|
"forgetPwd": {
|
||||||
|
"title": "忘记密码",
|
||||||
|
"emailPlaceholder": "请输入您的邮箱",
|
||||||
|
"sendEmailBtn": "发送邮件",
|
||||||
|
"signupButton": "注册",
|
||||||
|
"goback": "返回登录",
|
||||||
|
"sendEmailBtnDisabled": "已发送"
|
||||||
|
},
|
||||||
|
"resetPwd": {
|
||||||
|
"title": "重置密码",
|
||||||
|
"loginButton": "登录",
|
||||||
|
"signupButton": "注册",
|
||||||
|
"resetButton": "重置",
|
||||||
|
"goback": "返回登录",
|
||||||
|
"success":"邮件已发送,请注意查收"
|
||||||
|
},
|
||||||
|
"signup": {
|
||||||
|
"title": "注册",
|
||||||
|
"username": "用户名",
|
||||||
|
"email": "邮箱",
|
||||||
|
"password": "密码",
|
||||||
|
"confirmPassword": "确认密码",
|
||||||
|
"usernamePlaceholder": "请输入您的用户名",
|
||||||
|
"emailPlaceholder": "请输入您的邮箱",
|
||||||
|
"passwordPlaceholder": "请输入您的密码",
|
||||||
|
"confirmPasswordPlaceholder": "请确认您的密码",
|
||||||
|
"agreeTerms": "我同意",
|
||||||
|
"terms": "服务条款",
|
||||||
|
"and": "和",
|
||||||
|
"privacyPolicy": "隐私政策",
|
||||||
|
"signupButton": "注册",
|
||||||
|
"haveAccount": "已有帐号?",
|
||||||
|
"login": "登录",
|
||||||
|
"verifyCode": "验证码",
|
||||||
|
"verifyCodePlaceholder": "请输入6位验证码",
|
||||||
|
"sendCode": "发送验证码",
|
||||||
|
"resendCode": "重新发送",
|
||||||
|
"codeExpireTime": "验证码将在以下时间后过期"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
i18n/locales/zh/personal.json
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"photoCount": "照片数量",
|
||||||
|
"videoCount": "视频数量",
|
||||||
|
"memoryData": "内存数据"
|
||||||
|
},
|
||||||
|
"pro": {
|
||||||
|
"title": "开通MemoWark",
|
||||||
|
"subtitle": "家人的爱如照片中的记忆一般,重新流动起来"
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"syncThirdPartyData": "同步微信数据",
|
||||||
|
"myGallery": "我的图库",
|
||||||
|
"myVideo": "我的创作",
|
||||||
|
"AIVideo": "AI视频",
|
||||||
|
"setting": "通用设置",
|
||||||
|
"recommendMemoWake": "推荐MemoWake",
|
||||||
|
"fiveStarReview": "五星好评",
|
||||||
|
"contactUs": "联系客服",
|
||||||
|
"qualification": "资质证照",
|
||||||
|
"otherAgreement": "其他协议",
|
||||||
|
"version": "版本号"
|
||||||
|
},
|
||||||
|
"personalInfo": {
|
||||||
|
"title": "个人信息",
|
||||||
|
"avatar": "头像",
|
||||||
|
"nickname": "昵称",
|
||||||
|
"userId": "用户id",
|
||||||
|
"email": "邮箱",
|
||||||
|
"phone": "手机号",
|
||||||
|
"chat": "微信",
|
||||||
|
"changeNickname": "修改昵称",
|
||||||
|
"nicknameSave": "保存",
|
||||||
|
"phoneBind": "绑定手机号"
|
||||||
|
},
|
||||||
|
"lcenses": {
|
||||||
|
"title": "资质证照",
|
||||||
|
"ICP": "ICP备案:",
|
||||||
|
"licencePhoto": "营业执照",
|
||||||
|
"otherLcenses": "其他协议",
|
||||||
|
"userAgreement": "用户协议",
|
||||||
|
"privacyPolicy": "隐私政策",
|
||||||
|
"aiPolicy": "《AI功能使用规范》",
|
||||||
|
"applyPermission": "申请使用权限"
|
||||||
|
},
|
||||||
|
"generalSetting": {
|
||||||
|
"title": "通用设置",
|
||||||
|
"permissionManagement": "权限管理设置",
|
||||||
|
"pushNotification": "推送权限",
|
||||||
|
"galleryAccess": "相册权限",
|
||||||
|
"personalizedRecommendation": "个性化推荐设置",
|
||||||
|
"deleteAccount": "注销账号",
|
||||||
|
"logout": "退出登录"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
i18n/locales/zh/upload.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"upload": {
|
||||||
|
"upload": "上传",
|
||||||
|
"notify": "通知我",
|
||||||
|
"second": "目前有很多人在创建视频,可能需要一些时间。我们会在你的视频准备好时通知你",
|
||||||
|
"download": "下载动态全家福",
|
||||||
|
"processing": "AI处理中",
|
||||||
|
"failed": "AI处理失败",
|
||||||
|
"failedTitle": "AI处理失败",
|
||||||
|
"mustUpload": "请先上传文件",
|
||||||
|
"mustUploadTitle": "请先上传文件",
|
||||||
|
"downloadFailedTitle": "下载失败",
|
||||||
|
"downloadFailed": "请检查网络或资源权限",
|
||||||
|
"mustLogin": "请先登录",
|
||||||
|
"task": "任务",
|
||||||
|
"taskName": "动态全家福",
|
||||||
|
"taskStatus": "正在处理中",
|
||||||
|
"noName": "未命名作品"
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"title": "My Memory",
|
||||||
|
"uploadTitle": "上传素材",
|
||||||
|
"uploadFromChat": "微信上传",
|
||||||
|
"uploadFromDevice": "本地文件上传",
|
||||||
|
"cancel": "取消",
|
||||||
|
"return": "返回"
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"works": "推送",
|
||||||
|
"createText": "创作",
|
||||||
|
"create": {
|
||||||
|
"dynamic": "动态全家福"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
i18n/translations-generated.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// 自动生成的导入文件,请勿手动修改
|
||||||
|
|
||||||
|
import enAdmin from './locales/en/admin.json';
|
||||||
|
import enCommon from './locales/en/common.json';
|
||||||
|
import enExample from './locales/en/example.json';
|
||||||
|
import enFairclip from './locales/en/fairclip.json';
|
||||||
|
import enLanding from './locales/en/landing.json';
|
||||||
|
import enLogin from './locales/en/login.json';
|
||||||
|
import enPersonal from './locales/en/personal.json';
|
||||||
|
import enUpload from './locales/en/upload.json';
|
||||||
|
import zhAdmin from './locales/zh/admin.json';
|
||||||
|
import zhCommon from './locales/zh/common.json';
|
||||||
|
import zhExample from './locales/zh/example.json';
|
||||||
|
import zhFairclip from './locales/zh/fairclip.json';
|
||||||
|
import zhLanding from './locales/zh/landing.json';
|
||||||
|
import zhLogin from './locales/zh/login.json';
|
||||||
|
import zhPersonal from './locales/zh/personal.json';
|
||||||
|
import zhUpload from './locales/zh/upload.json';
|
||||||
|
|
||||||
|
const translations = {
|
||||||
|
en: {
|
||||||
|
admin: enAdmin,
|
||||||
|
common: enCommon,
|
||||||
|
example: enExample,
|
||||||
|
fairclip: enFairclip,
|
||||||
|
landing: enLanding,
|
||||||
|
login: enLogin,
|
||||||
|
personal: enPersonal,
|
||||||
|
upload: enUpload
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
admin: zhAdmin,
|
||||||
|
common: zhCommon,
|
||||||
|
example: zhExample,
|
||||||
|
fairclip: zhFairclip,
|
||||||
|
landing: zhLanding,
|
||||||
|
login: zhLogin,
|
||||||
|
personal: zhPersonal,
|
||||||
|
upload: zhUpload
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default translations;
|
||||||
19
i18n/utils.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// 自动导入 locales 目录下的所有翻译文件
|
||||||
|
function importAllLocales() {
|
||||||
|
const locales = {
|
||||||
|
en: {
|
||||||
|
common: require('./locales/en/common.json'),
|
||||||
|
example: require('./locales/en/example.json')
|
||||||
|
// 添加更多英文命名空间...
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
common: require('./locales/zh/common.json'),
|
||||||
|
example: require('./locales/zh/example.json')
|
||||||
|
// 添加更多中文命名空间...
|
||||||
|
}
|
||||||
|
// 添加更多语言...
|
||||||
|
};
|
||||||
|
return locales;
|
||||||
|
}
|
||||||
|
|
||||||
|
const translations = importAllLocales();
|
||||||
33
lib/event-util.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
type EventCallback = (...args: any[]) => void;
|
||||||
|
|
||||||
|
class EventEmitter {
|
||||||
|
private events: Map<string, EventCallback[]> = new Map();
|
||||||
|
|
||||||
|
on(event: string, callback: EventCallback) {
|
||||||
|
if (!this.events.has(event)) {
|
||||||
|
this.events.set(event, []);
|
||||||
|
}
|
||||||
|
this.events.get(event)!.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
off(event: string, callback: EventCallback) {
|
||||||
|
if (!this.events.has(event)) return;
|
||||||
|
const callbacks = this.events.get(event)!;
|
||||||
|
const index = callbacks.indexOf(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
callbacks.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(event: string, ...args: any[]) {
|
||||||
|
if (!this.events.has(event)) return;
|
||||||
|
this.events.get(event)!.forEach(callback => callback(...args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const eventEmitter = new EventEmitter();
|
||||||
|
|
||||||
|
export const EVENT_TYPES = {
|
||||||
|
USER_INFO_UPDATED: 'USER_INFO_UPDATED',
|
||||||
|
MATERIAL_IMPORTED: 'MATERIAL_IMPORTED'
|
||||||
|
} as const;
|
||||||
142
lib/server-api-util.ts
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import { useAuth } from '@/contexts/auth-context';
|
||||||
|
import { setCredentials } from '@/features/auth/authSlice';
|
||||||
|
import { store } from '@/store';
|
||||||
|
import { User } from '@/types/user';
|
||||||
|
// 定义错误码常量
|
||||||
|
const ERROR_CODES = {
|
||||||
|
UNAUTHORIZED: 1004010001,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
code: number;
|
||||||
|
data: T | null;
|
||||||
|
message: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PagedResult<T> {
|
||||||
|
items: T[];
|
||||||
|
has_more: boolean;
|
||||||
|
}
|
||||||
|
// 获取.env文件中的变量
|
||||||
|
|
||||||
|
export const API_ENDPOINT = "http://192.168.31.115:18080/api"
|
||||||
|
|
||||||
|
// 更新 access_token 的逻辑 - 用于React组件中
|
||||||
|
export const useAuthToken = async<T>(message: string | null) => {
|
||||||
|
try {
|
||||||
|
const { login } = useAuth();
|
||||||
|
const response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`);
|
||||||
|
const apiResponse: ApiResponse<T> = await response.json();
|
||||||
|
|
||||||
|
// 如果接口报错,页面弹出来错误信息
|
||||||
|
if (apiResponse.code != 0) {
|
||||||
|
console.log(message || 'Unknown error');
|
||||||
|
throw new Error(message || 'Unknown error');
|
||||||
|
} else {
|
||||||
|
const userData = apiResponse.data as User;
|
||||||
|
// 接口未报错,通过auth-context文件的login函数来更新access token
|
||||||
|
login(userData, userData.access_token || "");
|
||||||
|
}
|
||||||
|
return apiResponse;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching data from /iam/access-token-refresh:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用Redux存储token的刷新token函数
|
||||||
|
export const refreshAuthToken = async<T>(message: string | null): Promise<User> => {
|
||||||
|
try {
|
||||||
|
// 退出刷新会重新填充数据
|
||||||
|
let response;
|
||||||
|
response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`);
|
||||||
|
const apiResponse: ApiResponse<T> = await response.json();
|
||||||
|
if (apiResponse.code != 0) {
|
||||||
|
// addToast({
|
||||||
|
// title: message || 'Unknown error',
|
||||||
|
// color: "danger",
|
||||||
|
// })
|
||||||
|
throw new Error(message || 'Unknown error');
|
||||||
|
}
|
||||||
|
|
||||||
|
const userData = apiResponse.data as User;
|
||||||
|
|
||||||
|
if (userData && userData.access_token) {
|
||||||
|
// 使用Redux更新token
|
||||||
|
store.dispatch(setCredentials({
|
||||||
|
user: userData,
|
||||||
|
token: userData.access_token
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return userData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error refreshing token:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 API 错误
|
||||||
|
const handleApiError = (error: unknown, needToast = true, defaultMessage = 'Unknown error') => {
|
||||||
|
const message = error instanceof Error ? error.message : defaultMessage;
|
||||||
|
if (needToast) {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
throw new Error(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理必须携带 token 的接口
|
||||||
|
export const fetchApi = async <T>(
|
||||||
|
path: string,
|
||||||
|
options: RequestInit = {},
|
||||||
|
needToast = true
|
||||||
|
): Promise<T> => {
|
||||||
|
const makeRequest = async (isRetry = false): Promise<ApiResponse<T>> => {
|
||||||
|
try {
|
||||||
|
const token = store.getState().auth.token;
|
||||||
|
const headers = new Headers(options.headers);
|
||||||
|
|
||||||
|
// 添加必要的 headers
|
||||||
|
if (options.method !== 'GET' && !headers.has('Content-Type')) {
|
||||||
|
headers.set('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token != null) {
|
||||||
|
headers.set('Authorization', `Bearer ${token}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${API_ENDPOINT}/v1${path}`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...options,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
const apiResponse: ApiResponse<T> = await response.json();
|
||||||
|
|
||||||
|
// 处理未授权错误(需要刷新 token)
|
||||||
|
if (apiResponse.code === ERROR_CODES.UNAUTHORIZED && !isRetry) {
|
||||||
|
await refreshAuthToken(apiResponse.message);
|
||||||
|
return makeRequest(true); // 重试请求
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理其他错误
|
||||||
|
if (apiResponse.code !== 0) {
|
||||||
|
throw new Error(apiResponse.message || 'Request failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiResponse;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new Error('An unknown error occurred');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await makeRequest();
|
||||||
|
return response.data || ({} as T);
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error, needToast);
|
||||||
|
}
|
||||||
|
};
|
||||||
6
metro.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
const { getDefaultConfig } = require("expo/metro-config");
|
||||||
|
const { withNativeWind } = require('nativewind/metro');
|
||||||
|
|
||||||
|
const config = getDefaultConfig(__dirname)
|
||||||
|
|
||||||
|
module.exports = withNativeWind(config, { input: './global.css' })
|
||||||
1
nativewind-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="nativewind/types" />
|
||||||
1538
package-lock.json
generated
31
package.json
@ -3,47 +3,62 @@
|
|||||||
"main": "expo-router/entry",
|
"main": "expo-router/entry",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "expo start",
|
"start": "expo start --port 5173",
|
||||||
"reset-project": "node ./scripts/reset-project.js",
|
"reset-project": "node ./scripts/reset-project.js",
|
||||||
"android": "expo start --android",
|
"android": "expo start --android --port 5173",
|
||||||
"ios": "expo start --ios",
|
"ios": "expo start --ios --port 5173",
|
||||||
"web": "expo start --web",
|
"web": "expo start --web --port 5173",
|
||||||
"lint": "expo lint"
|
"lint": "expo lint",
|
||||||
|
"prebuild": "npm run generate:translations",
|
||||||
|
"generate:translations": "tsx i18n/generate-imports.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^14.1.0",
|
"@expo/vector-icons": "^14.1.0",
|
||||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||||
"@react-navigation/elements": "^2.3.8",
|
"@react-navigation/elements": "^2.3.8",
|
||||||
"@react-navigation/native": "^7.1.6",
|
"@react-navigation/native": "^7.1.6",
|
||||||
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"expo": "~53.0.12",
|
"expo": "~53.0.12",
|
||||||
"expo-blur": "~14.1.5",
|
"expo-blur": "~14.1.5",
|
||||||
"expo-constants": "~17.1.6",
|
"expo-constants": "~17.1.6",
|
||||||
|
"expo-dev-client": "~5.2.1",
|
||||||
"expo-font": "~13.3.1",
|
"expo-font": "~13.3.1",
|
||||||
"expo-haptics": "~14.1.4",
|
"expo-haptics": "~14.1.4",
|
||||||
"expo-image": "~2.3.0",
|
"expo-image": "~2.3.0",
|
||||||
"expo-linking": "~7.1.5",
|
"expo-linking": "~7.1.5",
|
||||||
|
"expo-localization": "^16.1.5",
|
||||||
"expo-router": "~5.1.0",
|
"expo-router": "~5.1.0",
|
||||||
"expo-splash-screen": "~0.30.9",
|
"expo-splash-screen": "~0.30.9",
|
||||||
"expo-status-bar": "~2.2.3",
|
"expo-status-bar": "~2.2.3",
|
||||||
"expo-symbols": "~0.4.5",
|
"expo-symbols": "~0.4.5",
|
||||||
"expo-system-ui": "~5.0.9",
|
"expo-system-ui": "~5.0.9",
|
||||||
"expo-web-browser": "~14.2.0",
|
"expo-web-browser": "~14.2.0",
|
||||||
|
"i18next": "^25.2.1",
|
||||||
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
|
"i18next-http-backend": "^3.0.2",
|
||||||
|
"nativewind": "^4.1.23",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
|
"react-i18next": "^15.5.3",
|
||||||
"react-native": "0.79.4",
|
"react-native": "0.79.4",
|
||||||
"react-native-gesture-handler": "~2.24.0",
|
"react-native-gesture-handler": "~2.24.0",
|
||||||
"react-native-reanimated": "~3.17.4",
|
"react-native-reanimated": "~3.17.4",
|
||||||
"react-native-safe-area-context": "5.4.0",
|
"react-native-safe-area-context": "^5.4.0",
|
||||||
"react-native-screens": "~4.11.1",
|
"react-native-screens": "~4.11.1",
|
||||||
"react-native-web": "~0.20.0",
|
"react-native-web": "~0.20.0",
|
||||||
"react-native-webview": "13.13.5"
|
"react-native-webview": "13.13.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
|
"@types/node": "^24.0.3",
|
||||||
"@types/react": "~19.0.10",
|
"@types/react": "~19.0.10",
|
||||||
"typescript": "~5.8.3",
|
|
||||||
"eslint": "^9.25.0",
|
"eslint": "^9.25.0",
|
||||||
"eslint-config-expo": "~9.2.0"
|
"eslint-config-expo": "~9.2.0",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.14",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tsx": "^4.20.3",
|
||||||
|
"typescript": "~5.8.3"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
21
provider.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
import { I18nextProvider } from "react-i18next";
|
||||||
|
import { Provider as ReduxProvider } from "react-redux";
|
||||||
|
import { AuthProvider } from "./contexts/auth-context";
|
||||||
|
import i18n from "./i18n";
|
||||||
|
import { LanguageProvider } from "./i18n/LanguageContext";
|
||||||
|
import { store } from "./store";
|
||||||
|
|
||||||
|
export function Provider({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<I18nextProvider i18n={i18n}>
|
||||||
|
<LanguageProvider>
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
<AuthProvider>
|
||||||
|
{children}
|
||||||
|
</AuthProvider>
|
||||||
|
</ReduxProvider>
|
||||||
|
</LanguageProvider>
|
||||||
|
</I18nextProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
15
store.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
import { counterSlice } from './components/steps';
|
||||||
|
import authReducer from './features/auth/authSlice';
|
||||||
|
|
||||||
|
export const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
counter: counterSlice.reducer,
|
||||||
|
auth: authReducer
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 从 store 本身推断 `RootState` 和 `AppDispatch` 类型
|
||||||
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
|
// 推断类型:{posts: PostsState, comments: CommentsState, users: UsersState}
|
||||||
|
export type AppDispatch = typeof store.dispatch;
|
||||||