feat(mobile): language management UI in account settings
Restaurant owners can now enable/disable AI-translated menu languages (EN/DE/AR/RU/FR) from the account screen, calling the new /restaurants/:id/languages endpoints. Turkish stays as the fixed base language; adding a language triggers a full AI translation pass, removing one just hides it (translations are kept for a fast re-enable). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
00195ae7d7
commit
6ec4631e82
@@ -28,8 +28,17 @@ interface RestaurantDetail {
|
||||
logo_url: string | null;
|
||||
phone: string | null;
|
||||
address: string | null;
|
||||
enabled_languages?: string[];
|
||||
}
|
||||
|
||||
const SUPPORTED_LANGUAGES: { code: string; label: string; flag: string }[] = [
|
||||
{ code: "en", label: "İngilizce", flag: "🇬🇧" },
|
||||
{ code: "de", label: "Almanca", flag: "🇩🇪" },
|
||||
{ code: "ar", label: "Arapça", flag: "🇸🇦" },
|
||||
{ code: "ru", label: "Rusça", flag: "🇷🇺" },
|
||||
{ code: "fr", label: "Fransızca", flag: "🇫🇷" },
|
||||
];
|
||||
|
||||
export interface TxtRecord {
|
||||
name: string;
|
||||
value: string;
|
||||
@@ -87,6 +96,10 @@ export default function AccountScreen() {
|
||||
const [selectedTheme, setSelectedTheme] = useState("elegant");
|
||||
const [savingTheme, setSavingTheme] = useState(false);
|
||||
|
||||
// Language / AI Translation state
|
||||
const [enabledLanguages, setEnabledLanguages] = useState<string[]>(["tr"]);
|
||||
const [updatingLanguage, setUpdatingLanguage] = useState<string | null>(null);
|
||||
|
||||
// Custom Domain state
|
||||
const [domains, setDomains] = useState<DomainRow[]>([]);
|
||||
const [newDomainInput, setNewDomainInput] = useState("");
|
||||
@@ -118,6 +131,7 @@ export default function AccountScreen() {
|
||||
setAddress(rest.address ?? "");
|
||||
setLogoUrl(rest.logo_url ?? null);
|
||||
setSlug(rest.slug);
|
||||
setEnabledLanguages(rest.enabled_languages ?? ["tr"]);
|
||||
|
||||
// Fetch theme
|
||||
const themeRes = await api.get<{ themeKey: string }>(`/restaurants/${cached.restaurantId}/theme`).catch(() => ({ themeKey: "elegant" }));
|
||||
@@ -214,6 +228,54 @@ export default function AccountScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddLanguage(code: string, label: string) {
|
||||
if (!active || updatingLanguage) return;
|
||||
setUpdatingLanguage(code);
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {});
|
||||
try {
|
||||
const res = await api.post<{ enabledLanguages: string[] }>(
|
||||
`/restaurants/${active.restaurantId}/languages`,
|
||||
{ code },
|
||||
);
|
||||
setEnabledLanguages(res.enabledLanguages);
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
||||
Alert.alert("Menü Çevrildi 🎉", `Menünüz yapay zeka ile ${label} diline çevrildi.`);
|
||||
} catch (err) {
|
||||
Alert.alert("Hata", err instanceof Error ? err.message : "Dil eklenemedi.");
|
||||
} finally {
|
||||
setUpdatingLanguage(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemoveLanguage(code: string, label: string) {
|
||||
if (!active || updatingLanguage) return;
|
||||
Alert.alert(
|
||||
`${label} Dilini Kaldır`,
|
||||
"Bu dil menüde artık gösterilmeyecek. Çeviriler saklanacağı için dili tekrar eklerseniz yeniden çevrilmesi gerekmez.",
|
||||
[
|
||||
{ text: "Vazgeç", style: "cancel" },
|
||||
{
|
||||
text: "Kaldır",
|
||||
style: "destructive",
|
||||
onPress: async () => {
|
||||
setUpdatingLanguage(code);
|
||||
try {
|
||||
const res = await api.delete<{ enabledLanguages: string[] }>(
|
||||
`/restaurants/${active.restaurantId}/languages/${code}`,
|
||||
);
|
||||
setEnabledLanguages(res.enabledLanguages);
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {});
|
||||
} catch (err) {
|
||||
Alert.alert("Hata", err instanceof Error ? err.message : "Dil kaldırılamadı.");
|
||||
} finally {
|
||||
setUpdatingLanguage(null);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
async function handleAddCustomDomain() {
|
||||
if (!active || !newDomainInput.trim()) {
|
||||
Alert.alert("Eksik Bilgi", "Lütfen bağlamak istediğiniz alan adını girin (Örn: menu.kebapciahmet.com)");
|
||||
@@ -605,6 +667,94 @@ export default function AccountScreen() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Language / AI Translation Section */}
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
borderWidth: 1,
|
||||
borderColor: "#E7E5E4",
|
||||
gap: 14,
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
|
||||
<Ionicons name="language-outline" size={20} color="#C8A96B" />
|
||||
<Text style={{ fontSize: 16, fontWeight: "800", color: "#1C1917" }}>
|
||||
Diller (Yapay Zeka Çevirisi)
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={{ fontSize: 12, color: "#78716C", lineHeight: 18 }}>
|
||||
Menünüzü yapay zeka ile otomatik olarak çevirin. Müşterileriniz menüde istedikleri dili seçebilir.
|
||||
</Text>
|
||||
|
||||
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
|
||||
{/* Turkish — always on, base language */}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
backgroundColor: "#1C1917",
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontSize: 13 }}>🇹🇷</Text>
|
||||
<Text style={{ fontSize: 12, fontWeight: "700", color: "#FFFFFF" }}>Türkçe</Text>
|
||||
</View>
|
||||
|
||||
{SUPPORTED_LANGUAGES.map((lang) => {
|
||||
const isEnabled = enabledLanguages.includes(lang.code);
|
||||
const isUpdating = updatingLanguage === lang.code;
|
||||
return (
|
||||
<Pressable
|
||||
key={lang.code}
|
||||
onPress={() =>
|
||||
isEnabled
|
||||
? handleRemoveLanguage(lang.code, lang.label)
|
||||
: handleAddLanguage(lang.code, lang.label)
|
||||
}
|
||||
disabled={isUpdating}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
backgroundColor: isEnabled ? "#FDF8F0" : "#FAF8F5",
|
||||
borderWidth: 1.5,
|
||||
borderColor: isEnabled ? "#C8A96B" : "#E7E5E4",
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 10,
|
||||
opacity: isUpdating ? 0.6 : 1,
|
||||
}}
|
||||
>
|
||||
{isUpdating ? (
|
||||
<ActivityIndicator size="small" color="#C8A96B" />
|
||||
) : (
|
||||
<Text style={{ fontSize: 13 }}>{lang.flag}</Text>
|
||||
)}
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: "700",
|
||||
color: isEnabled ? "#926E27" : "#57534E",
|
||||
}}
|
||||
>
|
||||
{lang.label}
|
||||
</Text>
|
||||
{isEnabled ? (
|
||||
<Ionicons name="close-circle" size={14} color="#C8A96B" />
|
||||
) : (
|
||||
<Ionicons name="add-circle-outline" size={14} color="#A8A29E" />
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Custom Domain Section */}
|
||||
<View
|
||||
style={{
|
||||
|
||||
Reference in New Issue
Block a user