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;
|
logo_url: string | null;
|
||||||
phone: string | null;
|
phone: string | null;
|
||||||
address: 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 {
|
export interface TxtRecord {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
@@ -87,6 +96,10 @@ export default function AccountScreen() {
|
|||||||
const [selectedTheme, setSelectedTheme] = useState("elegant");
|
const [selectedTheme, setSelectedTheme] = useState("elegant");
|
||||||
const [savingTheme, setSavingTheme] = useState(false);
|
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
|
// Custom Domain state
|
||||||
const [domains, setDomains] = useState<DomainRow[]>([]);
|
const [domains, setDomains] = useState<DomainRow[]>([]);
|
||||||
const [newDomainInput, setNewDomainInput] = useState("");
|
const [newDomainInput, setNewDomainInput] = useState("");
|
||||||
@@ -118,6 +131,7 @@ export default function AccountScreen() {
|
|||||||
setAddress(rest.address ?? "");
|
setAddress(rest.address ?? "");
|
||||||
setLogoUrl(rest.logo_url ?? null);
|
setLogoUrl(rest.logo_url ?? null);
|
||||||
setSlug(rest.slug);
|
setSlug(rest.slug);
|
||||||
|
setEnabledLanguages(rest.enabled_languages ?? ["tr"]);
|
||||||
|
|
||||||
// Fetch theme
|
// Fetch theme
|
||||||
const themeRes = await api.get<{ themeKey: string }>(`/restaurants/${cached.restaurantId}/theme`).catch(() => ({ themeKey: "elegant" }));
|
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() {
|
async function handleAddCustomDomain() {
|
||||||
if (!active || !newDomainInput.trim()) {
|
if (!active || !newDomainInput.trim()) {
|
||||||
Alert.alert("Eksik Bilgi", "Lütfen bağlamak istediğiniz alan adını girin (Örn: menu.kebapciahmet.com)");
|
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>
|
||||||
</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 */}
|
{/* Custom Domain Section */}
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
Reference in New Issue
Block a user