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:
AyrisAI
2026-08-29 01:53:45 +03:00
co-authored by Claude Sonnet 5
parent 00195ae7d7
commit 6ec4631e82
+150
View File
@@ -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={{