diff --git a/.gitignore b/.gitignore index 50616451..40db7a33 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +web.vocechat.* +*.zip diff --git a/.vscode/settings.json b/.vscode/settings.json index 454f0648..65a07b39 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -69,5 +69,6 @@ "[svg]": { "editor.defaultFormatter": "jock.svg" }, - "i18n-ally.localesPaths": ["public/locales"] + "i18n-ally.localesPaths": ["public/locales"], + "i18n-ally.keystyle": "nested" } diff --git a/package.json b/package.json index 101cfc85..3e1e9c10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vocechat-web", - "version": "0.9.31", + "version": "0.9.34", "homepage": "https://voce.chat", "dependencies": { "@metamask/onboarding": "^1.0.1", diff --git a/public/locales/de/common.json b/public/locales/de/common.json index 4985c944..c88ecca8 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -77,7 +77,7 @@ "price": "$49" }, "supreme": { - "title": "VoceChat SDK-Anpassung", + "title": "VoceSpace Private Deployment Video [499$]", "desc": "", "price": "" } diff --git a/public/locales/de/setting.json b/public/locales/de/setting.json index 88d7f712..8ed818a2 100644 --- a/public/locales/de/setting.json +++ b/public/locales/de/setting.json @@ -22,7 +22,23 @@ "license": "Lizenz", "about": "Über", "api_doc": "API-Dokumentation", - "version": "Version" + "version": "Version", + "video": "Videoanrufe" + }, + "vocespace": { + "title": "Vocespace", + "desc": "Selbstgehosteter Vocespace (Automatisierte Bereitstellung)", + "prerequisite": [ + "Voraussetzungen", + "Docker-Umgebung", + "Die Ports 80, 443, 7880, 7881, 3008 und 3000 sind geöffnet", + "Die Domain ist registriert", + "Für Unternehmensanpassungen oder bei Fragen wenden Sie sich bitte an han@privoce.com oder fügen Sie uns auf WeChat hinzu: Privoce" + ], + "sub_desc": "Jedes Vocespace-Konto bietet unbegrenzte Gesprächsminuten, 5 gleichzeitige Teilnehmer und eine kostenlose 4K-Auflösung. Es ist keine Kontoerstellung erforderlich. Befolgen Sie die unten stehenden Anweisungen, um Ihren Vocespace korrekt zu konfigurieren.", + "domain_desc": "Diese Domain benötigt einen DNS-A-Eintrag, der auf die IP-Adresse des aktuellen Servers verweist, auf dem Sie VoceChat hosten.", + "enable": "Aktivieren", + "how_to": "Nach der Aktivierung legen Sie bitte eine benutzerdefinierte Domain fest. Klicken Sie auf „Speichern“, und das System aktiviert VoceSpace automatisch." }, "channel": { "leave": "Kanal verlassen", diff --git a/public/locales/de/welcome.json b/public/locales/de/welcome.json index 501447fe..2fa1da67 100644 --- a/public/locales/de/welcome.json +++ b/public/locales/de/welcome.json @@ -7,6 +7,8 @@ "download": "Mobile Apps herunterladen", "help": "Fragen? Besuche unser Hilfe-Center", "sign_in_tip": "Zum Senden anmelden", + "vocespace": " VoceSpace Videoanruf", + "license": "VoceSpace Upgrade-Lizenz", "onboarding": { "title": "VoceChat-Einrichtung", "welcome": "Willkommen bei VoceChat!", diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 9b36f5cf..44624e16 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -77,7 +77,7 @@ "price": "$49" }, "supreme": { - "title": "VoceChat SDK Customization and Integration", + "title": "VoceSpace Private Deployment Video [499$]", "desc": "", "price": "" } diff --git a/public/locales/en/setting.json b/public/locales/en/setting.json index 8b63536f..84cec3cd 100644 --- a/public/locales/en/setting.json +++ b/public/locales/en/setting.json @@ -15,6 +15,7 @@ "bot": "Bot & Webhook", "firebase": "Firebase", "agora": "Agora", + "vocespace": "Video", "smtp": "SMTP", "login_method": "Login Methods", "third_app": "Third-party Login", @@ -22,7 +23,8 @@ "license": "License", "about": "About", "api_doc": "API Documentation", - "version": "Version" + "version": "Version", + "video": "Video" }, "channel": { "leave": "Leave Channel", @@ -267,6 +269,21 @@ "enable": "Enable", "how_to": "How to setup Agora?" }, + "vocespace": { + "title": "Vocespace", + "desc": "Use self-hosted Vocespace (Automated Deployment)", + "prerequisite": [ + "Prerequisites", + "Docker environment", + "Ports 80, 443, 7880, 7881, 3008, 3000 are open", + "The domain has been registered", + "For enterprises customization or need help, please contact han@privoce.com or add WeChat: Privoce" + ], + "sub_desc": "Each Vocespace account will enjoy no limit minutes, 5 person concurrent and 4k resolution for free. No account creation required. Follow the instruction below to configure your Vocespace correctly.", + "domain_desc": "This domain needs to have a DNS A record pointing to the current server IP you are hosting VoceChat.", + "enable": "Enable", + "how_to": "After enabling, please set a custom domain then click save, and the system will automatically enable VoceSpace." + }, "third_app": { "key": "API Secure Key", "update": "Update Secret", diff --git a/public/locales/en/welcome.json b/public/locales/en/welcome.json index db34ce19..f4b49cc0 100644 --- a/public/locales/en/welcome.json +++ b/public/locales/en/welcome.json @@ -7,6 +7,8 @@ "download": "Download Mobile apps", "help": "Got questions? Visit our help center", "sign_in_tip": "Please sign in to send message", + "vocespace": "VoceSpace Video Call", + "license": "VoceSpace Upgrade License", "onboarding": { "title": "VoceChat Setup", "welcome": "Welcome to your VoceChat!", diff --git a/public/locales/es/common.json b/public/locales/es/common.json index e8119836..b938fc31 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -73,7 +73,7 @@ "price": "$49" }, "supreme": { - "title": "Personalización e Integración de VoceChat SDK", + "title": "Vídeo de implementación privada de VoceSpace [499$]", "desc": "", "price": "" } diff --git a/public/locales/es/setting.json b/public/locales/es/setting.json index d21a758f..fa047ee8 100644 --- a/public/locales/es/setting.json +++ b/public/locales/es/setting.json @@ -22,7 +22,23 @@ "license": "Licencia", "about": "Acerca de", "api_doc": "Documentación de API", - "version": "Versión" + "version": "Versión", + "video": "Video" + }, + "vocespace": { + "title": "Vocespace", + "desc": "Vocespace implementado de forma privada (implementación automática)", + "sub_desc": "Cada cuenta de Vocespace disfrutará de llamadas ilimitadas gratuitas, 5 usuarios simultáneos y resolución 4K. No es necesario crear una cuenta. Siga las instrucciones a continuación para configurar correctamente su Vocespace.", + "prerequisite": [ + "Prerrequisitos", + "Entorno Docker", + "Puertos 80, 443, 7880, 7881, 3008 y 3000 abiertos", + "Nombre de dominio que resuelve a la dirección IP local (para usuarios en China continental, como Alibaba Cloud y Tencent Cloud, se requiere el registro ICP)", + "Para personalización empresarial o asistencia, contacte con han@privoce.com o agregue WeChat: Privoce" + ], + "domain_desc": "Los nombres de dominio personalizados deben resolver a la dirección IP local (para usuarios en China continental, como Alibaba Cloud y Tencent Cloud, se requiere el registro ICP)", + "enable": "Habilitar", + "how_to": "Después de habilitar, configure el nombre de dominio y guarde. Los pasos restantes se completarán automáticamente." }, "channel": { "leave": "Abandonar Canal", diff --git a/public/locales/es/welcome.json b/public/locales/es/welcome.json index f7abee0a..e2c1769b 100644 --- a/public/locales/es/welcome.json +++ b/public/locales/es/welcome.json @@ -7,6 +7,8 @@ "download": "Descarga las aplicaciones móviles", "help": "¿Tienes preguntas? Visita nuestro centro de ayuda", "sign_in_tip": "Por favor, inicia sesión para enviar mensajes", + "vocespace": "Llamada de video VoceSpace", + "license": "VoceSpace Licencia de Actualización", "onboarding": { "title": "Configuración de VoceChat", "welcome": "¡Bienvenido a tu VoceChat!", diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json index 8e50c7da..b44e421f 100644 --- a/public/locales/fr/common.json +++ b/public/locales/fr/common.json @@ -1,91 +1,91 @@ -{ - "chat": "Chat", - "members": "Membres", - "favs": "Éléments Enregistrés", - "files": "Fichiers", - "setting": "Paramètres", - "more": "Plus", - "action": { - "login": "Se Connecter", - "re_login": "Déconnexion", - "logout": "Se Déconnecter", - "change_avatar": "Changer", - "cancel": "Annuler", - "dismiss": "Rejeter", - "search": "Rechercher", - "search_user": "Rechercher par nom", - "new_channel": "Nouveau Canal", - "new_private_channel": "Nouveau Canal Privé", - "new_msg": "Nouveau Message", - "invite_people": "Inviter des Personnes", - "search_people": "Rechercher des Personnes", - "create": "Créer", - "open": "Ouvrir", - "update": "Mettre à Jour", - "upload": "Téléverser", - "done": "Fait", - "add": "Ajouter", - "yes": "Oui", - "delete_channel": "Supprimer le Canal", - "remove": "Retirer", - "remove_user": "Retirer l'Utilisateur", - "edit": "Modifier", - "edit_msg": "Modifier le Message", - "add_reaction": "Ajouter une Réaction", - "add_to_fav": "Ajouter aux Favoris", - "select": "Sélectionner", - "forward": "Transférer", - "reply": "Répondre", - "pin": "Épingler", - "unpin": "Désépingler", - "send": "Envoyer", - "send_msg": "Message", - "copy": "Copier", - "copy_email": "Copier l'Email", - "mute": "Couper le son", - "unmute": "Réactiver le son", - "mark_read": "Marquer comme Lu", - "hide_session": "Masquer la Session", - "reload": "Recharger", - "install": "Installer", - "download_origin": "Télécharger l'original", - "close": "Fermer" - }, - "status": { - "uploading": "Téléversement" - }, - "tip": { - "update": "Mise à Jour Réussie !", - "delete": "Suppression Réussie !", - "reg": "Inscription Réussie !", - "login": "Connexion Réussie !", - "logout": "Déconnexion Réussie !", - "pin": "Message Épinglé avec Succès !", - "pwa_install_title": "Installer l'application web sur le bureau ?", - "pwa_install_desc": "Ajoutez au bureau pour un accès rapide à cette application." - }, - "new_version": "<0>Nouvelle Version Disponible", - "mobile_app": "Découvrez notre <0>Application Mobile", - "price": { - "pro": { - "title": "VoceChat Pro", - "desc": "", - "price": "$49" - }, - "supreme": { - "title": "Personnalisation et Intégration de VoceChat SDK", - "desc": "", - "price": "" - } - }, - "server_update": { - "version_needed": "Cette fonction nécessite la version serveur :<0>{{version}} au minimum 🚨", - "current_version": "Votre version actuelle :<0>{{version}}", - "update_tip": "Veuillez mettre à niveau le Serveur !", - "howto": "Comment Mettre à Jour le Serveur VoceChat" - }, - "inactive": { - "title": "Un seul onglet actif est autorisé pour VoceChat", - "desc": "Veuillez recharger cette page pour continuer à utiliser cet onglet ou le fermer" - } -} \ No newline at end of file +{ + "chat": "Chat", + "members": "Membres", + "favs": "Éléments Enregistrés", + "files": "Fichiers", + "setting": "Paramètres", + "more": "Plus", + "action": { + "login": "Se Connecter", + "re_login": "Déconnexion", + "logout": "Se Déconnecter", + "change_avatar": "Changer", + "cancel": "Annuler", + "dismiss": "Rejeter", + "search": "Rechercher", + "search_user": "Rechercher par nom", + "new_channel": "Nouveau Canal", + "new_private_channel": "Nouveau Canal Privé", + "new_msg": "Nouveau Message", + "invite_people": "Inviter des Personnes", + "search_people": "Rechercher des Personnes", + "create": "Créer", + "open": "Ouvrir", + "update": "Mettre à Jour", + "upload": "Téléverser", + "done": "Fait", + "add": "Ajouter", + "yes": "Oui", + "delete_channel": "Supprimer le Canal", + "remove": "Retirer", + "remove_user": "Retirer l'Utilisateur", + "edit": "Modifier", + "edit_msg": "Modifier le Message", + "add_reaction": "Ajouter une Réaction", + "add_to_fav": "Ajouter aux Favoris", + "select": "Sélectionner", + "forward": "Transférer", + "reply": "Répondre", + "pin": "Épingler", + "unpin": "Désépingler", + "send": "Envoyer", + "send_msg": "Message", + "copy": "Copier", + "copy_email": "Copier l'Email", + "mute": "Couper le son", + "unmute": "Réactiver le son", + "mark_read": "Marquer comme Lu", + "hide_session": "Masquer la Session", + "reload": "Recharger", + "install": "Installer", + "download_origin": "Télécharger l'original", + "close": "Fermer" + }, + "status": { + "uploading": "Téléversement" + }, + "tip": { + "update": "Mise à Jour Réussie !", + "delete": "Suppression Réussie !", + "reg": "Inscription Réussie !", + "login": "Connexion Réussie !", + "logout": "Déconnexion Réussie !", + "pin": "Message Épinglé avec Succès !", + "pwa_install_title": "Installer l'application web sur le bureau ?", + "pwa_install_desc": "Ajoutez au bureau pour un accès rapide à cette application." + }, + "new_version": "<0>Nouvelle Version Disponible", + "mobile_app": "Découvrez notre <0>Application Mobile", + "price": { + "pro": { + "title": "VoceChat Pro", + "desc": "", + "price": "$49" + }, + "supreme": { + "title": "Vidéo de déploiement privé VoceSpace [499$]", + "desc": "", + "price": "" + } + }, + "server_update": { + "version_needed": "Cette fonction nécessite la version serveur :<0>{{version}} au minimum 🚨", + "current_version": "Votre version actuelle :<0>{{version}}", + "update_tip": "Veuillez mettre à niveau le Serveur !", + "howto": "Comment Mettre à Jour le Serveur VoceChat" + }, + "inactive": { + "title": "Un seul onglet actif est autorisé pour VoceChat", + "desc": "Veuillez recharger cette page pour continuer à utiliser cet onglet ou le fermer" + } +} diff --git a/public/locales/fr/setting.json b/public/locales/fr/setting.json index 5ff1a51a..cb802f66 100644 --- a/public/locales/fr/setting.json +++ b/public/locales/fr/setting.json @@ -1,282 +1,298 @@ -{ - "title": "Paramètres", - "save_change": "Sauvegarder les Modifications", - "reset": "Réinitialiser", - "save_tip": "Vous avez des modifications non enregistrées !", - "nav": { - "general": "Général", - "overview": "Aperçu", - "members": "Membres", - "data_management": "Gestion des Données", - "auto_delete_msg": "Auto-suppression des Messages", - "user": "Utilisateur", - "my_account": "Mon Compte", - "config": "Configuration", - "bot": "Bot & Webhook", - "firebase": "Firebase", - "agora": "Agora", - "smtp": "SMTP", - "login_method": "Méthodes de Connexion", - "third_app": "Connexion Tiers", - "widget": "Widget", - "license": "Licence", - "about": "À Propos", - "api_doc": "Documentation API", - "version": "Version" - }, - "channel": { - "leave": "Quitter le Canal", - "leave_desc": "Êtes-vous sûr de vouloir quitter ce canal ?", - "transfer_desc": "Vous devez transférer la propriété de votre canal à quelqu'un d'autre avant de quitter le canal.", - "delete": "Supprimer le Canal", - "delete_desc": "Êtes-vous sûr de vouloir supprimer ce canal ?", - "id": "ID du Canal", - "name": "Nom du Canal", - "topic": "Sujet du Canal", - "topic_placeholder": "Faites savoir à tout le monde comment utiliser ce canal.", - "visibility": "Visibilité du Canal", - "public": "Public", - "private": "Privé" - }, - "dm": { - "delete": "Supprimer l'Utilisateur", - "delete_desc": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?" - }, - "overview": { - "title_feat": "Fonctionnalités", - "name": "Nom du Serveur", - "desc": "Description du Serveur", - "upload_desc": "La taille minimale est de 128x128, nous recommandons au moins 512x512 pour le serveur. Taille maximale limitée à 5 Mo.", - "sign_up": { - "title": "Inscription par Défaut", - "desc": "Qui peut s'inscrire sur ce serveur.", - "everyone": "Tout le monde", - "invite": "Lien d'Invitation Seulement" - }, - "guest_mode": { - "title": "Mode Invité", - "desc": "S'il est activé, les visiteurs verront les canaux publics sur ce serveur.", - "enable": "Activer", - "disable": "Désactivé" - }, - "url": { - "title": "Mettre à Jour l'URL de Frontend", - "desc": "Mettre à jour l'URL de frontend" - }, - "lang": { - "title": "Langues", - "desc": "Définir la langue du système" - }, - "theme": { - "title": "Thème", - "desc": "Choisissez votre thème", - "auto": "Automatique", - "dark": "Sombre", - "light": "Clair" - }, - "message_sound": { - "title": "Son des Messages", - "desc": "Jouez un son lors de la réception d'un nouveau message.", - "enable": "Activer", - "disable": "Désactiver" - }, - "online_status": { - "title": "Statut En Ligne", - "desc": "S'il est activé, les membres verront le statut en ligne/hors ligne des autres.", - "enable": "Activer", - "disable": "Désactiver" - }, - "chat_layout": { - "title": "Mise en Page du Chat", - "desc": "Quel côté pour afficher les messages envoyés par vous-même", - "left": "Gauche", - "self_right": "Droite" - }, - "contact_verify": { - "title": "Vérification des Contacts", - "desc": "Par défaut, tous les membres du serveur peuvent se voir. Si vous souhaitez que les membres du serveur ajoutent et gèrent leurs propres contacts, veuillez activer la fonctionnalité de contact (l'administrateur sera dans la liste des contacts par défaut).", - "enable": "Activer", - "disable": "Désactiver" - } - }, - "data": { - "clear_files": { - "title": "Fichiers et Données d'Images", - "desc": "Cliquez sur le bouton ci-dessous pour supprimer toutes les données de fichiers et d'images de ce serveur.", - "btn": "Effacer les Données de Fichiers" - }, - "clear_msgs": { - "title": "Toutes les Données de Messages", - "desc": "Cliquez sur le bouton ci-dessous pour supprimer toutes les données de chat, y compris les textes et les fichiers de ce serveur.", - "btn": "Effacer les Données de Chats" - }, - "auto_delete_file": { - "title": "Auto-suppression des Données de Fichiers", - "desc": "Définir le temps d'expiration pour toutes les données de fichiers de ce serveur.", - "confirm_desc": "Tous les fichiers plus anciens que le temps défini seront supprimés.", - "off": "Désactivé", - "day1": "1 Jour", - "day7": "7 Jours", - "day30": "30 Jours", - "day90": "90 Jours", - "day180": "180 Jours" - } - }, - "license": { - "signed": "Signé", - "domain": "Domaines", - "user_limit": "Limite d'Utilisateur", - "expire": "Expiré À", - "create": "Créé À", - "value": "Valeur de la Licence", - "renew": "Renouveler la Licence", - "update": "Mettre à Jour Manuellement", - "update_placeholder": "Veuillez saisir la nouvelle valeur de la licence", - "renew_select": "Veuillez sélectionner le prix", - "tip": { - "title": "Une chance d'obtenir une mise à niveau de licence gratuite !", - "user_test": "Obtenez une mise à niveau de licence gratuite grâce à un test utilisateur de 25 minutes", - "contact": "Réservez un créneau ici : " - }, - "payment_success": "Paiement Réussi !", - "back_home": "Retour à l'Accueil", - "tip_renewing": "Renouvellement de la Licence, ne fermez pas la fenêtre !", - "tip_renewed": "Licence Renouvelée avec Succès !", - "tip_renew_error": "ID de Session Stripe Invalidé", - "tip_domain": "La licence est liée au domaine (sans port), veuillez confirmer ou mettre à jour vos paramètres :", - "tip_port": "Remplissez votre nom de domaine sans port.", - "tip_confirm": "Commencer le Paiement" - }, - "bot": { - "add_api_key": "Ajouter une Clé API", - "no_api_key": "Aucune Clé API pour l'instant", - "bot_tip_title": "Les bots peuvent envoyer des messages aux canaux.", - "bot_tip_desc": "Veuillez noter que nous ne montrons pas la clé API d'un Bot à nouveau après sa génération. Vous pouvez toujours générer une nouvelle clé API pour un Bot en cliquant sur \"Ajouter une Clé API\" et cela n'annulera pas les clés API précédentes.", - "webhook_tip_title": "Les Webhooks peuvent recevoir des messages dans un canal et devraient avoir une URL fournie par vous.", - "webhook_tip_desc": "Veuillez fournir une URL de webhook qui retournera un code 200.", - "manage": "Gérer", - "how_to_use": "Comment Utiliser", - "manage_desc": "Vous pouvez supprimer un bot à tout moment, l'utilisateur bot sera immédiatement supprimé et toutes les API seront annulées.", - "create_title": "Créer un Bot", - "create_desc": "Donnez un nom à votre bot. Vous pouvez également ajouter une URL de webhook d’endpoint pour recevoir des données.", - "create_key_title": "Créer une Clé API pour le Bot", - "create_key_desc": "Créer une Clé API pour authentifier votre communication", - "create_key_warning": "Assurez-vous de copier la Clé API maintenant. Vous ne pourrez pas la voir à nouveau !", - "key_copy_and_close": "Copier et Fermer", - "delete_title": "Supprimer le Bot", - "delete_desc": "Êtes-vous sûr de vouloir supprimer le Bot ?", - "delete_key_title": "Supprimer la Clé API du Bot", - "delete_key_desc": "Êtes-vous sûr de vouloir supprimer cette Clé API de Bot ?", - "webhook_title": "Mettre à Jour l'URL du Webhook", - "webhook_desc": "Veuillez saisir une URL valide", - "col_avatar": "Avatar", - "col_name": "Nom du Bot", - "col_api_key": "Clé API (Messages Entrants)", - "col_webhook": "URL du Webhook d'Endpoint (Sortant)", - "col_opt": "Opérations", - "col_key_name": "Nom", - "col_key_value": "Clé", - "col_key_create_time": "Créé À", - "col_key_last_used": "Dernière Utilisation" - }, - "auto_delete_msg": { - "title": "Supprimer les messages envoyés par moi-même après une certaine période.", - "desc": "Cela supprimera les données de messages stockées à la fois sur le serveur et du côté client.", - "off": "Désactivé", - "5_min": "5 minutes", - "10_min": "10 minutes", - "1_hour": "1 heure", - "1_day": "1 jour", - "1_week": "1 semaine", - "clear": "Effacer tous les messages du canal", - "clear_title": "Effacer tous les messages dans ce canal.", - "clear_desc": "Cela supprimera toutes les données de messages stockées à la fois sur le serveur et du côté client." - }, - "firebase": { - "disable": "Désactiver", - "use_official": "Utiliser la Configuration Officielle", - "custom": "Personnalisé", - "enable": "Activer", - "token_url": "URL du Token", - "project_id": "ID du Projet", - "private_key": "Clé Privée", - "client_email": "Email du Client" - }, - "smtp": { - "enable": "Activer", - "host": "Hôte", - "port": "Port", - "from": "De", - "username": "Nom d'utilisateur", - "password": "Mot de Passe", - "how_to": "Comment configurer SMTP ?", - "send_test_email": "Envoyer un Email de Test" - }, - "agora": { - "enable": "Activer", - "how_to": "Comment configurer Agora ?" - }, - "third_app": { - "key": "Clé Sécurisée API", - "update": "Mettre à Jour le Secret", - "update_tip": "Êtes-vous sûr de vouloir mettre à jour le secret API ? Le secret précédent sera invalidé", - "key_tip": "La clé de sécurité convenue entre le serveur et l'application tierce est utilisée pour chiffrer les données de communication.", - "how_to": "Comment utiliser ?" - }, - "login": { - "password": "Mot de Passe", - "password_desc": "Permet aux membres de se connecter avec un mot de passe.", - "magic_link": "Lien Magique", - "magic_link_desc": "Permet aux membres de se connecter avec un Lien Magique.", - "google": "Google", - "google_desc": "Permet aux membres de se connecter avec Google.", - "github": "GitHub", - "github_desc": "Permet aux membres de se connecter avec GitHub.", - "metamask": "Metamask", - "metamask_desc": "Permet aux membres de se connecter avec Metamask.", - "oidc": "OIDC", - "oidc_desc": "Permet aux membres de se connecter avec OIDC.", - "oidc_custom": "Personnalisé", - "more_details": "Besoin de plus de détails ? Consultez notre <0>documentation." - }, - "widget": { - "tip": "Étendre VoceChat en intégrant le widget SDK VoceChat !", - "code": "Exemple de Code", - "code_comment": "mettez ce fragment de code dans votre fichier html", - "config": "Description de la Configuration", - "param_key": "Clé de Paramètre", - "default_value": "Valeur par Défaut", - "remark": "Remarques", - "custom_style_tip": "si vous souhaitez personnaliser plus précisément la position du widget, utilisez l'ID de l'élément du widget dans votre fichier HTML ou CSS, comme ceci", - "param_id": "ID du Widget (version serveur >= v0.3.12)", - "param_host": "Affecter l'utilisateur discutant avec le visiteur (ID Utilisateur)", - "param_title": "titre de la fenêtre de chat popup", - "param_logo": "URL de l'icône du logo de la fenêtre de chat popup", - "param_auto_reg": "true : Enregistrer automatiquement un nouvel utilisateur s'il n'existe pas, false : l'utilisateur doit saisir son email et son nom pour s'inscrire", - "param_login_token": "Si vous utilisez les informations existantes d'utilisateur de votre propre plateforme pour générer un nouveau compte VoceChat, ceci est le token de connexion généré par votre plateforme, pour plus de détails, veuillez vous référer à : https://doc.voce.chat/zh-cn/login-with-other-account. Ne remplissez pas ceci pour des scénarios par défaut (aucun système d'utilisateurs tiers) !", - "param_theme_color": "La couleur de thème du widget", - "param_close_width": "La largeur lorsque le widget est fermé", - "param_close_height": "La hauteur lorsque le widget est fermé", - "param_open_width": "La largeur lorsque le widget est ouvert", - "param_open_height": "La hauteur lorsque le widget est ouvert", - "param_welcome": "Message de bienvenue personnalisé", - "param_position": "Position du widget : gauche/droite", - "share_link": "Partagez le lien du widget afin que les gens puissent discuter avec vous directement", - "widget_faq": "Activer Laisser Tout le Monde Rejoindre en Premier" - }, - "version": { - "client_version": "Version Client", - "server_version": "Version Serveur", - "build_time": "Horodatage de Construction", - "sync": "Synchroniser avec la dernière version du client web" - }, - "api_doc": { - "desc": "Nous avons une documentation API détaillée sur laquelle vous pouvez baser votre propre outil de chat.", - "access": "URL d'accès :", - "use_method": "Méthode d'Utilisation :", - "step_1": "Étape Une : Changez pour votre propre Serveur", - "step_2": "Étape Deux : Entrez le token de connexion", - "step_2_desc": "Vous pouvez obtenir le token en accédant à l'en-tête http X-API-Key ou en copiant simplement le texte ci-dessous :", - "last": "Chaque API a un bouton 'Essayez-le', et vous pouvez déboguer l'API avec cela" - } -} +{ + "title": "Paramètres", + "save_change": "Sauvegarder les Modifications", + "reset": "Réinitialiser", + "save_tip": "Vous avez des modifications non enregistrées !", + "nav": { + "general": "Général", + "overview": "Aperçu", + "members": "Membres", + "data_management": "Gestion des Données", + "auto_delete_msg": "Auto-suppression des Messages", + "user": "Utilisateur", + "my_account": "Mon Compte", + "config": "Configuration", + "bot": "Bot & Webhook", + "firebase": "Firebase", + "agora": "Agora", + "smtp": "SMTP", + "login_method": "Méthodes de Connexion", + "third_app": "Connexion Tiers", + "widget": "Widget", + "license": "Licence", + "about": "À Propos", + "api_doc": "Documentation API", + "version": "Version", + "video": "Vidéo" + }, + "vocespace": { + "title": "Vocespace", + "desc": "Vocespace déployé en mode privé (déploiement automatique)", + "sub_desc": "Chaque compte Vocespace bénéficie d'appels illimités gratuits, de 5 utilisateurs simultanés et d'une résolution 4K. Aucune création de compte n'est requise. Suivez les instructions ci-dessous pour configurer correctement votre Vocespace.", + "prerequisite": [ + "Prérequis", + "Environnement Docker", + "Les ports 80, 443, 7880, 7881, 3008 et 3000 sont ouverts", + "Nom de domaine pointant vers l'adresse IP locale (pour les utilisateurs de Chine continentale, tels qu'Alibaba Cloud et Tencent Cloud, un enregistrement ICP est requis)", + "Pour toute personnalisation ou assistance en entreprise, veuillez contacter han@privoce.com ou ajouter Privoce sur WeChat" + ], + "domain_desc": "Les noms de domaine personnalisés doivent pointer vers l'adresse IP locale (pour les utilisateurs de Chine continentale, tels qu'Alibaba Cloud et Tencent Cloud, un enregistrement ICP est requis)", + "enable": "Activer", + "how_to": "Après l'activation, veuillez définir le nom de domaine et enregistrer. Les étapes suivantes seront effectuées automatiquement." + }, + "channel": { + "leave": "Quitter le Canal", + "leave_desc": "Êtes-vous sûr de vouloir quitter ce canal ?", + "transfer_desc": "Vous devez transférer la propriété de votre canal à quelqu'un d'autre avant de quitter le canal.", + "delete": "Supprimer le Canal", + "delete_desc": "Êtes-vous sûr de vouloir supprimer ce canal ?", + "id": "ID du Canal", + "name": "Nom du Canal", + "topic": "Sujet du Canal", + "topic_placeholder": "Faites savoir à tout le monde comment utiliser ce canal.", + "visibility": "Visibilité du Canal", + "public": "Public", + "private": "Privé" + }, + "dm": { + "delete": "Supprimer l'Utilisateur", + "delete_desc": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?" + }, + "overview": { + "title_feat": "Fonctionnalités", + "name": "Nom du Serveur", + "desc": "Description du Serveur", + "upload_desc": "La taille minimale est de 128x128, nous recommandons au moins 512x512 pour le serveur. Taille maximale limitée à 5 Mo.", + "sign_up": { + "title": "Inscription par Défaut", + "desc": "Qui peut s'inscrire sur ce serveur.", + "everyone": "Tout le monde", + "invite": "Lien d'Invitation Seulement" + }, + "guest_mode": { + "title": "Mode Invité", + "desc": "S'il est activé, les visiteurs verront les canaux publics sur ce serveur.", + "enable": "Activer", + "disable": "Désactivé" + }, + "url": { + "title": "Mettre à Jour l'URL de Frontend", + "desc": "Mettre à jour l'URL de frontend" + }, + "lang": { + "title": "Langues", + "desc": "Définir la langue du système" + }, + "theme": { + "title": "Thème", + "desc": "Choisissez votre thème", + "auto": "Automatique", + "dark": "Sombre", + "light": "Clair" + }, + "message_sound": { + "title": "Son des Messages", + "desc": "Jouez un son lors de la réception d'un nouveau message.", + "enable": "Activer", + "disable": "Désactiver" + }, + "online_status": { + "title": "Statut En Ligne", + "desc": "S'il est activé, les membres verront le statut en ligne/hors ligne des autres.", + "enable": "Activer", + "disable": "Désactiver" + }, + "chat_layout": { + "title": "Mise en Page du Chat", + "desc": "Quel côté pour afficher les messages envoyés par vous-même", + "left": "Gauche", + "self_right": "Droite" + }, + "contact_verify": { + "title": "Vérification des Contacts", + "desc": "Par défaut, tous les membres du serveur peuvent se voir. Si vous souhaitez que les membres du serveur ajoutent et gèrent leurs propres contacts, veuillez activer la fonctionnalité de contact (l'administrateur sera dans la liste des contacts par défaut).", + "enable": "Activer", + "disable": "Désactiver" + } + }, + "data": { + "clear_files": { + "title": "Fichiers et Données d'Images", + "desc": "Cliquez sur le bouton ci-dessous pour supprimer toutes les données de fichiers et d'images de ce serveur.", + "btn": "Effacer les Données de Fichiers" + }, + "clear_msgs": { + "title": "Toutes les Données de Messages", + "desc": "Cliquez sur le bouton ci-dessous pour supprimer toutes les données de chat, y compris les textes et les fichiers de ce serveur.", + "btn": "Effacer les Données de Chats" + }, + "auto_delete_file": { + "title": "Auto-suppression des Données de Fichiers", + "desc": "Définir le temps d'expiration pour toutes les données de fichiers de ce serveur.", + "confirm_desc": "Tous les fichiers plus anciens que le temps défini seront supprimés.", + "off": "Désactivé", + "day1": "1 Jour", + "day7": "7 Jours", + "day30": "30 Jours", + "day90": "90 Jours", + "day180": "180 Jours" + } + }, + "license": { + "signed": "Signé", + "domain": "Domaines", + "user_limit": "Limite d'Utilisateur", + "expire": "Expiré À", + "create": "Créé À", + "value": "Valeur de la Licence", + "renew": "Renouveler la Licence", + "update": "Mettre à Jour Manuellement", + "update_placeholder": "Veuillez saisir la nouvelle valeur de la licence", + "renew_select": "Veuillez sélectionner le prix", + "tip": { + "title": "Une chance d'obtenir une mise à niveau de licence gratuite !", + "user_test": "Obtenez une mise à niveau de licence gratuite grâce à un test utilisateur de 25 minutes", + "contact": "Réservez un créneau ici : " + }, + "payment_success": "Paiement Réussi !", + "back_home": "Retour à l'Accueil", + "tip_renewing": "Renouvellement de la Licence, ne fermez pas la fenêtre !", + "tip_renewed": "Licence Renouvelée avec Succès !", + "tip_renew_error": "ID de Session Stripe Invalidé", + "tip_domain": "La licence est liée au domaine (sans port), veuillez confirmer ou mettre à jour vos paramètres :", + "tip_port": "Remplissez votre nom de domaine sans port.", + "tip_confirm": "Commencer le Paiement" + }, + "bot": { + "add_api_key": "Ajouter une Clé API", + "no_api_key": "Aucune Clé API pour l'instant", + "bot_tip_title": "Les bots peuvent envoyer des messages aux canaux.", + "bot_tip_desc": "Veuillez noter que nous ne montrons pas la clé API d'un Bot à nouveau après sa génération. Vous pouvez toujours générer une nouvelle clé API pour un Bot en cliquant sur \"Ajouter une Clé API\" et cela n'annulera pas les clés API précédentes.", + "webhook_tip_title": "Les Webhooks peuvent recevoir des messages dans un canal et devraient avoir une URL fournie par vous.", + "webhook_tip_desc": "Veuillez fournir une URL de webhook qui retournera un code 200.", + "manage": "Gérer", + "how_to_use": "Comment Utiliser", + "manage_desc": "Vous pouvez supprimer un bot à tout moment, l'utilisateur bot sera immédiatement supprimé et toutes les API seront annulées.", + "create_title": "Créer un Bot", + "create_desc": "Donnez un nom à votre bot. Vous pouvez également ajouter une URL de webhook d’endpoint pour recevoir des données.", + "create_key_title": "Créer une Clé API pour le Bot", + "create_key_desc": "Créer une Clé API pour authentifier votre communication", + "create_key_warning": "Assurez-vous de copier la Clé API maintenant. Vous ne pourrez pas la voir à nouveau !", + "key_copy_and_close": "Copier et Fermer", + "delete_title": "Supprimer le Bot", + "delete_desc": "Êtes-vous sûr de vouloir supprimer le Bot ?", + "delete_key_title": "Supprimer la Clé API du Bot", + "delete_key_desc": "Êtes-vous sûr de vouloir supprimer cette Clé API de Bot ?", + "webhook_title": "Mettre à Jour l'URL du Webhook", + "webhook_desc": "Veuillez saisir une URL valide", + "col_avatar": "Avatar", + "col_name": "Nom du Bot", + "col_api_key": "Clé API (Messages Entrants)", + "col_webhook": "URL du Webhook d'Endpoint (Sortant)", + "col_opt": "Opérations", + "col_key_name": "Nom", + "col_key_value": "Clé", + "col_key_create_time": "Créé À", + "col_key_last_used": "Dernière Utilisation" + }, + "auto_delete_msg": { + "title": "Supprimer les messages envoyés par moi-même après une certaine période.", + "desc": "Cela supprimera les données de messages stockées à la fois sur le serveur et du côté client.", + "off": "Désactivé", + "5_min": "5 minutes", + "10_min": "10 minutes", + "1_hour": "1 heure", + "1_day": "1 jour", + "1_week": "1 semaine", + "clear": "Effacer tous les messages du canal", + "clear_title": "Effacer tous les messages dans ce canal.", + "clear_desc": "Cela supprimera toutes les données de messages stockées à la fois sur le serveur et du côté client." + }, + "firebase": { + "disable": "Désactiver", + "use_official": "Utiliser la Configuration Officielle", + "custom": "Personnalisé", + "enable": "Activer", + "token_url": "URL du Token", + "project_id": "ID du Projet", + "private_key": "Clé Privée", + "client_email": "Email du Client" + }, + "smtp": { + "enable": "Activer", + "host": "Hôte", + "port": "Port", + "from": "De", + "username": "Nom d'utilisateur", + "password": "Mot de Passe", + "how_to": "Comment configurer SMTP ?", + "send_test_email": "Envoyer un Email de Test" + }, + "agora": { + "enable": "Activer", + "how_to": "Comment configurer Agora ?" + }, + "third_app": { + "key": "Clé Sécurisée API", + "update": "Mettre à Jour le Secret", + "update_tip": "Êtes-vous sûr de vouloir mettre à jour le secret API ? Le secret précédent sera invalidé", + "key_tip": "La clé de sécurité convenue entre le serveur et l'application tierce est utilisée pour chiffrer les données de communication.", + "how_to": "Comment utiliser ?" + }, + "login": { + "password": "Mot de Passe", + "password_desc": "Permet aux membres de se connecter avec un mot de passe.", + "magic_link": "Lien Magique", + "magic_link_desc": "Permet aux membres de se connecter avec un Lien Magique.", + "google": "Google", + "google_desc": "Permet aux membres de se connecter avec Google.", + "github": "GitHub", + "github_desc": "Permet aux membres de se connecter avec GitHub.", + "metamask": "Metamask", + "metamask_desc": "Permet aux membres de se connecter avec Metamask.", + "oidc": "OIDC", + "oidc_desc": "Permet aux membres de se connecter avec OIDC.", + "oidc_custom": "Personnalisé", + "more_details": "Besoin de plus de détails ? Consultez notre <0>documentation." + }, + "widget": { + "tip": "Étendre VoceChat en intégrant le widget SDK VoceChat !", + "code": "Exemple de Code", + "code_comment": "mettez ce fragment de code dans votre fichier html", + "config": "Description de la Configuration", + "param_key": "Clé de Paramètre", + "default_value": "Valeur par Défaut", + "remark": "Remarques", + "custom_style_tip": "si vous souhaitez personnaliser plus précisément la position du widget, utilisez l'ID de l'élément du widget dans votre fichier HTML ou CSS, comme ceci", + "param_id": "ID du Widget (version serveur >= v0.3.12)", + "param_host": "Affecter l'utilisateur discutant avec le visiteur (ID Utilisateur)", + "param_title": "titre de la fenêtre de chat popup", + "param_logo": "URL de l'icône du logo de la fenêtre de chat popup", + "param_auto_reg": "true : Enregistrer automatiquement un nouvel utilisateur s'il n'existe pas, false : l'utilisateur doit saisir son email et son nom pour s'inscrire", + "param_login_token": "Si vous utilisez les informations existantes d'utilisateur de votre propre plateforme pour générer un nouveau compte VoceChat, ceci est le token de connexion généré par votre plateforme, pour plus de détails, veuillez vous référer à : https://doc.voce.chat/zh-cn/login-with-other-account. Ne remplissez pas ceci pour des scénarios par défaut (aucun système d'utilisateurs tiers) !", + "param_theme_color": "La couleur de thème du widget", + "param_close_width": "La largeur lorsque le widget est fermé", + "param_close_height": "La hauteur lorsque le widget est fermé", + "param_open_width": "La largeur lorsque le widget est ouvert", + "param_open_height": "La hauteur lorsque le widget est ouvert", + "param_welcome": "Message de bienvenue personnalisé", + "param_position": "Position du widget : gauche/droite", + "share_link": "Partagez le lien du widget afin que les gens puissent discuter avec vous directement", + "widget_faq": "Activer Laisser Tout le Monde Rejoindre en Premier" + }, + "version": { + "client_version": "Version Client", + "server_version": "Version Serveur", + "build_time": "Horodatage de Construction", + "sync": "Synchroniser avec la dernière version du client web" + }, + "api_doc": { + "desc": "Nous avons une documentation API détaillée sur laquelle vous pouvez baser votre propre outil de chat.", + "access": "URL d'accès :", + "use_method": "Méthode d'Utilisation :", + "step_1": "Étape Une : Changez pour votre propre Serveur", + "step_2": "Étape Deux : Entrez le token de connexion", + "step_2_desc": "Vous pouvez obtenir le token en accédant à l'en-tête http X-API-Key ou en copiant simplement le texte ci-dessous :", + "last": "Chaque API a un bouton 'Essayez-le', et vous pouvez déboguer l'API avec cela" + } +} diff --git a/public/locales/fr/welcome.json b/public/locales/fr/welcome.json index 8c1a532f..9c5a05b8 100644 --- a/public/locales/fr/welcome.json +++ b/public/locales/fr/welcome.json @@ -1,39 +1,41 @@ -{ - "title": "Bienvenue sur le serveur {{name}}", - "desc": "Voici quelques étapes pour vous aider à commencer.", - "invite": "Invitez vos amis ou coéquipiers", - "start_by_channel": "Créez un Canal pour Commencer une Conversation", - "start_by_dm": "Envoyez un Message Direct", - "download": "Téléchargez les applications mobiles", - "help": "Vous avez des questions ? Visitez notre centre d'aide", - "sign_in_tip": "Veuillez vous connecter pour envoyer un message", - "onboarding": { - "title": "Configuration de VoceChat", - "welcome": "Bienvenue dans votre VoceChat !", - "welcome_desc": "Tout dans cet espace vous appartient. Configurons votre espace !", - "start": "Commencer", - "new_server": "Nommez votre nouveau serveur", - "server_desc": "Les serveurs sont des environnements partagés où les équipes peuvent travailler sur des projets et discuter.", - "placeholder_server": "Entrez le nom du serveur", - "create_server": "Créer un Serveur", - "admin_title": "Maintenant, configurons votre compte administrateur", - "admin_desc": "Vous êtes le 1er utilisateur et l'administrateur de votre espace !", - "sign": "S'inscrire", - "invite_title": "Dernière étape : invitez les autres !", - "invite_desc": "Tout d'abord, qui peut s'inscrire sur ce serveur ?", - "confirm": "Confirmer", - "done_welcome": "Bienvenue dans {{serverName}}", - "done_title": "Fièrement présenté par VoceChat", - "done_desc": "Plus de paramètres, y compris la résolution de domaine, les privilèges, la sécurité et les invitations sont disponibles dans <0>Paramètres", - "enter": "Entrer", - "last_tip": "Maintenant, invitons les autres !", - "last_desc": "Envoyez le lien d'invitation à vos futurs membres de la communauté :", - "done": "Fait", - "welcome_page": "Page de Bienvenue", - "set_name": "Définir le Nom", - "admin_account": "Compte Administrateur", - "who_sign_up": "Qui Peut S'inscrire", - "invites": "Invitations", - "update_domain_tip": "Lien de domaine incorrect ? Veuillez le mettre à jour ici :" - } -} \ No newline at end of file +{ + "title": "Bienvenue sur le serveur {{name}}", + "desc": "Voici quelques étapes pour vous aider à commencer.", + "invite": "Invitez vos amis ou coéquipiers", + "start_by_channel": "Créez un Canal pour Commencer une Conversation", + "start_by_dm": "Envoyez un Message Direct", + "download": "Téléchargez les applications mobiles", + "help": "Vous avez des questions ? Visitez notre centre d'aide", + "sign_in_tip": "Veuillez vous connecter pour envoyer un message", + "vocespace": "Appel Vidéo VoceSpace", + "license": "VoceSpace Licence de Mise à Niveau", + "onboarding": { + "title": "Configuration de VoceChat", + "welcome": "Bienvenue dans votre VoceChat !", + "welcome_desc": "Tout dans cet espace vous appartient. Configurons votre espace !", + "start": "Commencer", + "new_server": "Nommez votre nouveau serveur", + "server_desc": "Les serveurs sont des environnements partagés où les équipes peuvent travailler sur des projets et discuter.", + "placeholder_server": "Entrez le nom du serveur", + "create_server": "Créer un Serveur", + "admin_title": "Maintenant, configurons votre compte administrateur", + "admin_desc": "Vous êtes le 1er utilisateur et l'administrateur de votre espace !", + "sign": "S'inscrire", + "invite_title": "Dernière étape : invitez les autres !", + "invite_desc": "Tout d'abord, qui peut s'inscrire sur ce serveur ?", + "confirm": "Confirmer", + "done_welcome": "Bienvenue dans {{serverName}}", + "done_title": "Fièrement présenté par VoceChat", + "done_desc": "Plus de paramètres, y compris la résolution de domaine, les privilèges, la sécurité et les invitations sont disponibles dans <0>Paramètres", + "enter": "Entrer", + "last_tip": "Maintenant, invitons les autres !", + "last_desc": "Envoyez le lien d'invitation à vos futurs membres de la communauté :", + "done": "Fait", + "welcome_page": "Page de Bienvenue", + "set_name": "Définir le Nom", + "admin_account": "Compte Administrateur", + "who_sign_up": "Qui Peut S'inscrire", + "invites": "Invitations", + "update_domain_tip": "Lien de domaine incorrect ? Veuillez le mettre à jour ici :" + } +} diff --git a/public/locales/jp/common.json b/public/locales/jp/common.json index 3b0eeb4a..1e7f59cd 100644 --- a/public/locales/jp/common.json +++ b/public/locales/jp/common.json @@ -48,6 +48,39 @@ "status": { "uploading": "アップロード中" }, - "new_version": "<0>新しいバージョンが利用可能になりました", - "mobile_app": "<0>アプリのダウンロードがおすすめ" + "tip": { + "email_msg_tip": "お住まいの地域では通知がブロックされています。メール通知をご利用いただくには、Pro にアップグレードしてください。", + "update": "更新に成功しました!", + "delete": "削除に成功しました!", + "reg": "登録に成功しました!", + "login": "ログインに成功しました!", + "logout": "ログアウトに成功しました!", + "pin": "メッセージをピン留めに成功しました!", + "pwa_install_title": "デスクトップにウェブアプリをインストールしますか?", + "pwa_install_desc": "このアプリにすばやくアクセスするには、デスクトップに追加してください。" + }, + "new_version": "<0>新バージョンをご利用いただけます", + "mobile_app": "<0>モバイルアプリをご覧ください", + "price": { + "pro": { + "title": "VoceChat Pro", + "desc": "", + "price": "$49" + }, + "supreme": { + "title": "VoceSpace プライベート導入ビデオ [$499]", + "desc": "", + "price": "" + } + }, + "server_update": { + "version_needed": "この機能には、サーバーバージョン<0>{{version}} 🚨以上が必要です", + "current_version": "現在のバージョン: <0>{{version}} 0>", + "update_tip": "サーバーをアップグレードしてください!", + "howto": "VoceChatサーバーのアップデート方法" + }, + "inactive": { + "title": "アクティブなタブは1つだけですVoceChat", + "desc": "このタブを引き続き使用するには、このページを再読み込みするか、タブを閉じてください。" + } } diff --git a/public/locales/jp/setting.json b/public/locales/jp/setting.json index fcd96ae7..cecd20e5 100644 --- a/public/locales/jp/setting.json +++ b/public/locales/jp/setting.json @@ -18,7 +18,24 @@ "license": "ライセンス", "about": "このアプリについて", "api_doc": "API ドキュメント", - "version": "Version" + "version": "Version", + "video": "ビデオ" + }, + "vocespace": { + "title": "Vocespace", + "desc": "Vocespace をプライベートに導入(自動導入)", + "sub_desc": "Vocespace アカウントごとに、通話時間無制限、同時接続ユーザー5名、4K解像度のサービスを無料でご利用いただけます。アカウント作成は不要です。Vocespace を適切に設定するには、以下の手順に従ってください。", + "prerequisite": [ + "前提条件", + "Docker 環境", + "ポート 80、443、7880、7881、3008、3000 が開いている", + "ローカル IP アドレスに解決されるドメイン名(Alibaba Cloud や Tencent Cloud など、中国本土のユーザーの場合は ICP 申請が必要です)", + "エンタープライズカスタマイズやサポートについては、han@privoce.com までお問い合わせいただくか、WeChat: Privoce を追加してください" + ], + + "domain_desc": "カスタムドメイン名はローカル IP アドレスに解決される必要があります(Alibaba Cloud や Tencent Cloud など、中国本土のユーザーの場合は ICP 申請が必要です)", + "enable": "有効にする", + "how_to": "有効にした後、ドメイン名を設定して保存してください。残りの手順は自動的に完了します。" }, "channel": { "leave": "チャンネルを離れる", diff --git a/public/locales/jp/welcome.json b/public/locales/jp/welcome.json index 96cdf957..b0c6d601 100644 --- a/public/locales/jp/welcome.json +++ b/public/locales/jp/welcome.json @@ -7,6 +7,8 @@ "download": "アプリをダウンロードしましょう", "help": "まだ聞きたいことがある? ヘルプにアクセスしましょう", "sign_in_tip": "Please sign in to send message", + "vocespace": " VoceSpace ビデオ通話", + "license": "VoceSpace アップグレードライセンス", "onboarding": { "title": "VoceChat初期化", "welcome": "あなたのVoceChatへようこそ!", diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json index 9de24ccf..c0ca3060 100644 --- a/public/locales/pt/common.json +++ b/public/locales/pt/common.json @@ -73,7 +73,7 @@ "price": "$49" }, "supreme": { - "title": "VoceChat SDK Customization and Integration", + "title": "VoceSpace Private Deployment Video [499$]", "desc": "", "price": "" } diff --git a/public/locales/pt/setting.json b/public/locales/pt/setting.json index fc85d1a2..79a5a5bc 100644 --- a/public/locales/pt/setting.json +++ b/public/locales/pt/setting.json @@ -22,7 +22,23 @@ "license": "License", "about": "About", "api_doc": "API Documentation", - "version": "Version" + "version": "Version", + "video": "Video" + }, + "vocespace": { + "title": "Vocespace", + "desc": "Vocespace déployé en mode privé (déploiement automatique)", + "sub_desc": "Chaque compte Vocespace bénéficie d'appels illimités gratuits, de 5 utilisateurs simultanés et d'une résolution 4K. Aucune création de compte n'est requise. Suivez les instructions ci-dessous pour configurer correctement votre Vocespace.", + "prerequisite": [ + "Prérequis", + "Environnement Docker", + "Les ports 80, 443, 7880, 7881, 3008 et 3000 sont ouverts", + "Nom de domaine pointant vers l'adresse IP locale (pour les utilisateurs de Chine continentale, tels qu'Alibaba Cloud et Tencent Cloud, un enregistrement ICP est requis)", + "Pour toute personnalisation ou assistance en entreprise, veuillez contacter han@privoce.com ou ajouter Privoce sur WeChat" + ], + "domain_desc": "Les noms de domaine personnalisés doivent pointer vers l'adresse IP locale (pour les utilisateurs de Chine continentale, tels qu'Alibaba Cloud et Tencent Cloud, un enregistrement ICP est requis)", + "enable": "Activer", + "how_to": "Après l'activation, veuillez définir le nom de domaine et enregistrer. Les étapes suivantes seront effectuées automatiquement." }, "channel": { "leave": "Leave Channel", diff --git a/public/locales/pt/welcome.json b/public/locales/pt/welcome.json index 14216e63..07143f06 100644 --- a/public/locales/pt/welcome.json +++ b/public/locales/pt/welcome.json @@ -7,6 +7,8 @@ "download": "Download Mobile apps", "help": "Tem perguntas? Visite nossa central de ajuda", "sign_in_tip": "Faça login para enviar mensagem", + "vocespace": "Chamada de vídeo VoceSpace", + "license": "VoceSpace Licença de Atualização", "onboarding": { "title": "Configuração do VoceChat", "welcome": "Bem-vindo ao seu VoceChat!", diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 0783c367..730d3e7a 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -77,7 +77,7 @@ "price": "$49" }, "supreme": { - "title": "VoceChat SDK Customization and Integration", + "title": "Видеоинструкция по развертыванию VoceSpace Private [499$]", "desc": "", "price": "" } diff --git a/public/locales/ru/setting.json b/public/locales/ru/setting.json index 6d2ffa57..c64d0876 100644 --- a/public/locales/ru/setting.json +++ b/public/locales/ru/setting.json @@ -22,7 +22,23 @@ "license": "Лицензия", "about": "About", "api_doc": "API Documentation", - "version": "Версия" + "version": "Версия", + "video": "Видео" + }, + "vocespace": { + "title": "Vocespace", + "desc": "Vocespace развернут в частном порядке (автоматическое развертывание)", + "sub_desc": "Каждая учетная запись Vocespace будет пользоваться бесплатным неограниченным временем звонков, 5 одновременными пользователями и разрешением 4K. Создание учетной записи не требуется. Следуйте приведенным ниже инструкциям для правильной настройки Vocespace.", + "prerequisite": [ + "Предварительные условия", + "Среда Docker", + "Открыты порты 80, 443, 7880, 7881, 3008, 3000", + "Доменное имя, разрешающееся в локальный IP-адрес (для пользователей в материковом Китае, таких как Alibaba Cloud и Tencent Cloud, требуется подача заявки на ICP)", + "Для корпоративной настройки или получения помощи, пожалуйста, свяжитесь с han@privoce.com или добавьте нас в WeChat: Privoce" + ], + "domain_desc": "Пользовательские доменные имена должны разрешаться в локальный IP-адрес (для пользователей в материковом Китае, таких как Alibaba Cloud и Tencent Cloud, требуется подача заявки на ICP)", + "enable": "Включить", + "how_to": "После включения, пожалуйста, укажите доменное имя и сохраните. Остальные шаги будут выполнены автоматически." }, "channel": { "leave": "Покинуть канал", diff --git a/public/locales/ru/welcome.json b/public/locales/ru/welcome.json index 73346d66..0433bea3 100644 --- a/public/locales/ru/welcome.json +++ b/public/locales/ru/welcome.json @@ -7,6 +7,8 @@ "download": "Загрузить мобильные приложения", "help": "Есть вопросы? Посетите наш справочный центр", "sign_in_tip": "Пожалуйста, войдите, чтобы отправить сообщение", + "vocespace": "Видеозвонок VoceSpace", + "license": "VoceSpace Лицензия на обновление", "onboarding": { "title": "Настройка VoceChat", "welcome": "Добро пожаловать в ваш VoceChat!", diff --git a/public/locales/tr/common.json b/public/locales/tr/common.json index cf4df2c3..d8dde563 100644 --- a/public/locales/tr/common.json +++ b/public/locales/tr/common.json @@ -71,7 +71,7 @@ "price": "$49" }, "supreme": { - "title": "VoceChat SDK Customization and Integration", + "title": "VoceSpace Özel Dağıtım Videosu [499$]", "desc": "", "price": "" } diff --git a/public/locales/tr/setting.json b/public/locales/tr/setting.json index dc4abbaf..529dec02 100644 --- a/public/locales/tr/setting.json +++ b/public/locales/tr/setting.json @@ -23,7 +23,23 @@ "api_doc": "API Documentation", "version": "Sürüm", "faq": "SSS", - "feedback": "Geri Bildirim" + "feedback": "Geri Bildirim", + "video": "Video" + }, + "vocespace": { + "title": "Vocespace", + "desc": "Vocespace özel olarak dağıtıldı (otomatik dağıtım)", + "sub_desc": "Her Vocespace hesabı ücretsiz sınırsız arama süresi, 5 eş zamanlı kullanıcı ve 4K çözünürlük hizmetinden yararlanacaktır. Hesap oluşturma gerekmez. Vocespace'inizi doğru şekilde yapılandırmak için aşağıdaki talimatları izleyin.", + "prerequisite": [ + "Önkoşullar", + "Docker ortamı", + "80, 443, 7880, 7881, 3008, 3000 portları açık olmalı", + "Yerel IP adresine çözümlenen alan adı (Alibaba Cloud ve Tencent Cloud gibi Çin anakarasındaki kullanıcılar için ICP dosyalama gereklidir)", + "Kurumsal özelleştirme veya yardım için lütfen han@privoce.com ile iletişime geçin veya WeChat: Privoce ekleyin" + ], + "domain_desc": "Özel alan adları yerel IP adresine çözümlenmelidir (Alibaba Cloud ve Tencent Cloud gibi Çin anakarasındaki kullanıcılar için ICP dosyalama gereklidir)", + "enable": "Etkinleştir", + "how_to": "Etkinleştirdikten sonra lütfen alan adını ayarlayın ve kaydedin. Kalan adımlar otomatik olarak tamamlanacaktır." }, "channel": { "leave": "Kanalı Terket", diff --git a/public/locales/tr/welcome.json b/public/locales/tr/welcome.json index 9fb34c54..1564b143 100644 --- a/public/locales/tr/welcome.json +++ b/public/locales/tr/welcome.json @@ -7,6 +7,8 @@ "download": "Mobil uygulamaları indirin", "help": "Sorunuz var mı? Yardım merkezimizi ziyaret edin", "sign_in_tip": "İleti göndermek için lütfen oturum açın", + "vocespace": " VoceSpace Video Görüşmesi", + "license": "VoceSpace Yükseltme Lisansı", "onboarding": { "title": "VoceChat Kurulumu", "welcome": "VoceChat'e Hoşgeldiniz!", diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 58c8738d..1ed2ef97 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -77,7 +77,7 @@ "price": "$49" }, "supreme": { - "title": "VoceChat SDK 定制与集成", + "title": "VoceSpace 私有部署音视频[499$]", "desc": "", "price": "" } diff --git a/public/locales/zh/setting.json b/public/locales/zh/setting.json index 4d9a6938..4d9bdb83 100644 --- a/public/locales/zh/setting.json +++ b/public/locales/zh/setting.json @@ -22,7 +22,8 @@ "about": "关于", "api_doc": "API 文档", "version": "版本信息", - "feedback": "反馈" + "feedback": "反馈", + "video": "音视频" }, "channel": { "leave": "离开", @@ -267,6 +268,21 @@ "enable": "开启", "how_to": "如何配置 Agora?" }, + "vocespace": { + "title": "Vocespace", + "desc": "使用私有部署的Vocespace(自动部署)", + "sub_desc": "每个Vocespace账户将享受免费无限制通话时长、5人并发及4K分辨率服务。无需创建账户。按照以下说明正确配置您的Vocespace。", + "prerequisite": [ + "预备条件", + "Docker环境", + "端口 80, 443, 7880, 7881, 3008, 3000 已开放", + "解析到本机IP的域名(中国大陆用户,如阿里云和腾讯云,需备案)", + "企业定制或需要帮助,请联系 han@privoce.com 或添加微信:Privoce" + ], + "domain_desc": "自定义域名需要是解析到本机IP的域名 (中国大陆用户,如阿里云和腾讯云,需备案)", + "enable": "启用", + "how_to": "启用后,请设置域名并保存,其余步骤会自动化完成" + }, "third_app": { "key": "API 安全密钥", "update": "更新安全密钥", diff --git a/public/locales/zh/welcome.json b/public/locales/zh/welcome.json index f1c9cb7c..76222140 100644 --- a/public/locales/zh/welcome.json +++ b/public/locales/zh/welcome.json @@ -7,6 +7,8 @@ "download": "下载移动端APP", "help": "还有问题?访问我们的帮助中心", "sign_in_tip": "请登录后发消息", + "vocespace": " VoceSpace 视频通话", + "license": "VoceSpace 升级证书", "onboarding": { "title": "VoceChat初始化", "welcome": "欢迎来到你的VoceChat!", diff --git a/src/app/services/server.ts b/src/app/services/server.ts index a46192b3..15cb92d0 100644 --- a/src/app/services/server.ts +++ b/src/app/services/server.ts @@ -18,7 +18,8 @@ import { Server, SMTPConfig, SystemCommon, - TestEmailDTO + TestEmailDTO, + VocespaceConfig, } from "@/types/server"; import { User } from "@/types/user"; import { compareVersion, encodeBase64 } from "@/utils"; @@ -26,7 +27,7 @@ import BASE_URL, { ContentTypes, IS_OFFICIAL_DEMO, KEY_SERVER_VERSION, - PAYMENT_URL_PREFIX + PAYMENT_URL_PREFIX, } from "../config"; import { updateInfo } from "../slices/server"; import { updateCallInfo, upsertVoiceList } from "../slices/voice"; @@ -48,29 +49,29 @@ export const serverApi = createApi({ } catch { console.error("get server info error"); } - } + }, }), getThirdPartySecret: builder.query({ query: () => ({ url: `/admin/system/third_party_secret`, - responseHandler: "text" + responseHandler: "text", }), - keepUnusedDataFor: 0 + keepUnusedDataFor: 0, }), updateThirdPartySecret: builder.mutation({ query: () => ({ url: `/admin/system/third_party_secret`, method: "POST", - responseHandler: "text" - }) + responseHandler: "text", + }), }), getServerVersion: builder.query({ query: () => ({ headers: { - accept: "text/plain" + accept: "text/plain", }, url: `/admin/system/version`, - responseHandler: "text" + responseHandler: "text", }), async onQueryStarted(data, { dispatch, queryFulfilled }) { try { @@ -80,60 +81,63 @@ export const serverApi = createApi({ } catch { console.error("get server version error"); } - } + }, }), getFirebaseConfig: builder.query({ - query: () => ({ url: `/admin/fcm/config` }) + query: () => ({ url: `/admin/fcm/config` }), }), getGoogleAuthConfig: builder.query({ - query: () => ({ url: `/admin/google_auth/config` }) + query: () => ({ url: `/admin/google_auth/config` }), }), updateGoogleAuthConfig: builder.mutation({ query: (data) => ({ url: `/admin/google_auth/config`, method: "POST", - body: data - }) + body: data, + }), }), getGithubAuthConfig: builder.query({ - query: () => ({ url: `/admin/github_auth/config` }) + query: () => ({ url: `/admin/github_auth/config` }), }), updateGithubAuthConfig: builder.mutation({ query: (data) => ({ url: `/admin/github_auth/config`, method: "POST", - body: data - }) + body: data, + }), }), sendTestEmail: builder.mutation({ query: (data) => ({ url: `/admin/system/send_mail`, method: "POST", - body: data - }) + body: data, + }), }), updateFirebaseConfig: builder.mutation({ query: (data) => ({ url: `/admin/fcm/config`, method: "POST", - body: data - }) + body: data, + }), + }), + getVocespaceConfig: builder.query({ + query: () => ({ url: `/admin/vocespace/config` }), }), getAgoraConfig: builder.query({ - query: () => ({ url: `/admin/agora/config` }) + query: () => ({ url: `/admin/agora/config` }), }), getAgoraChannels: builder.query< AgoraVoicingListResponse, { page_no: number; page_size: number } >({ query: (param = { page_no: 0, page_size: 100 }) => ({ - url: `/admin/agora/channel/${param.page_no}/${param.page_size}` + url: `/admin/agora/channel/${param.page_no}/${param.page_size}`, }), async onQueryStarted(data, { dispatch, queryFulfilled, getState }) { try { const { voice: { callingFrom }, - authData + authData, } = getState() as RootState; const { data: resp } = await queryFulfilled; const { success } = resp; @@ -146,7 +150,7 @@ export const serverApi = createApi({ id: +id, context, memberCount: count, - channelName: data.channel_name + channelName: data.channel_name, }; }); dispatch(upsertVoiceList(arr)); @@ -162,7 +166,7 @@ export const serverApi = createApi({ } catch { console.error("get voice list error"); } - } + }, }), getAgoraUsersByChannel: builder.query({ query: (channel_name) => ({ url: `/admin/agora/channel/user/${channel_name}/false` }), @@ -171,24 +175,31 @@ export const serverApi = createApi({ return resp.data.users ?? []; } return []; - } + }, }), updateAgoraConfig: builder.mutation({ query: (data) => ({ url: `/admin/agora/config`, method: "POST", - body: data - }) + body: data, + }), + }), + updateVocespaceConfig: builder.mutation({ + query: (data) => ({ + url: `/admin/vocespace/config`, + method: "POST", + body: data, + }), }), getAgoraStatus: builder.query({ - query: () => ({ url: `/admin/agora/enabled` }) + query: () => ({ url: `/admin/agora/enabled` }), }), generateAgoraToken: builder.mutation({ query: (data) => ({ url: `/admin/agora/token`, method: "POST", - body: data - }) + body: data, + }), }), getSystemCommon: builder.query({ query: () => ({ url: `/admin/system/common` }), @@ -206,13 +217,13 @@ export const serverApi = createApi({ } catch { console.error("get server common error"); } - } + }, }), updateSystemCommon: builder.mutation>({ query: (data) => ({ url: `/admin/system/common`, method: "PUT", - body: data + body: data, }), async onQueryStarted(data, { dispatch, queryFulfilled }) { try { @@ -221,20 +232,20 @@ export const serverApi = createApi({ } catch { console.error("update server common error"); } - } + }, }), getSMTPConfig: builder.query({ - query: () => ({ url: `/admin/smtp/config` }) + query: () => ({ url: `/admin/smtp/config` }), }), getSMTPStatus: builder.query({ - query: () => ({ url: `/admin/smtp/enabled` }) + query: () => ({ url: `/admin/smtp/enabled` }), }), updateSMTPConfig: builder.mutation({ query: (data) => ({ url: `/admin/smtp/config`, method: "POST", - body: data - }) + body: data, + }), }), getLoginConfig: builder.query({ query: () => ({ url: `/admin/login/config` }), @@ -248,43 +259,43 @@ export const serverApi = createApi({ } catch { console.error("get login config error"); } - } + }, }), getFiles: builder.query({ query: (params) => ({ url: `/admin/system/files?${new URLSearchParams( params as Record - ).toString()}` - }) + ).toString()}`, + }), }), updateLoginConfig: builder.mutation>({ query: (data) => ({ url: `/admin/login/config`, method: "POST", - body: data - }) + body: data, + }), }), updateLogo: builder.mutation({ query: (data) => ({ headers: { - "content-type": "image/png" + "content-type": "image/png", }, url: `/admin/system/organization/logo`, method: "POST", - body: data + body: data, }), async onQueryStarted(data, { dispatch, queryFulfilled }) { try { await queryFulfilled; dispatch( updateInfo({ - logo: `${BASE_URL}/resource/organization/logo?t=${+new Date()}` + logo: `${BASE_URL}/resource/organization/logo?t=${+new Date()}`, }) ); } catch { console.error("update server logo error"); } - } + }, }), updateServer: builder.mutation>({ query: (data) => ({ @@ -293,7 +304,7 @@ export const serverApi = createApi({ compareVersion(localStorage.getItem(KEY_SERVER_VERSION) ?? "", "0.3.8") > 0 ? "PUT" : "POST", - body: data + body: data, }), async onQueryStarted(data, { dispatch, queryFulfilled, getState }) { const rootStore = getState() as RootState; @@ -304,34 +315,34 @@ export const serverApi = createApi({ } catch { dispatch(updateInfo({ name: prevName, description: prevDesc })); } - } + }, }), createAdmin: builder.mutation({ query: (data) => ({ url: "/admin/system/create_admin", method: "POST", - body: data - }) + body: data, + }), }), getFrontendUrl: builder.query({ query: () => ({ url: `/admin/system/frontend_url`, - responseHandler: "text" - }) + responseHandler: "text", + }), }), updateFrontendUrl: builder.mutation({ query: (url) => ({ url: `/admin/system/update_frontend_url`, method: "POST", headers: { - "content-type": "text/plain" + "content-type": "text/plain", }, - body: url - }) + body: url, + }), }), getLicense: builder.query({ query: () => ({ - url: `/license` + url: `/license`, }), async onQueryStarted(data, { dispatch, queryFulfilled, getState }) { // vocechat 官方 demo 则忽略 @@ -340,7 +351,7 @@ export const serverApi = createApi({ const { upgraded: prevValue } = rootStore.server; try { const { - data: { user_limit } + data: { user_limit }, } = await queryFulfilled; const currValue = user_limit > 20; if (prevValue !== currValue) { @@ -349,67 +360,67 @@ export const serverApi = createApi({ } catch { console.error("get license failed "); } - } + }, }), getLicensePaymentUrl: builder.mutation({ query: (data) => ({ url: `${PAYMENT_URL_PREFIX}/vocechat/payment/create`, method: "POST", - body: data - }) + body: data, + }), }), getGeneratedLicense: builder.query<{ license: string }, string>({ query: (session_id) => ({ - url: `${PAYMENT_URL_PREFIX}/vocechat/licenses/${session_id}` - }) + url: `${PAYMENT_URL_PREFIX}/vocechat/licenses/${session_id}`, + }), }), checkLicense: builder.mutation({ query: (license) => ({ url: "/license/check", method: "POST", - body: { license } - }) + body: { license }, + }), }), upsertLicense: builder.mutation({ query: (license) => ({ url: "/license", method: "PUT", - body: { license } - }) + body: { license }, + }), }), clearAllMessages: builder.query({ query: () => ({ url: "/admin/system/message/clear", - method: "DELETE" - }) + method: "DELETE", + }), }), clearAllFiles: builder.query({ query: () => ({ url: "/resource/file/delete", - method: "DELETE" - }) + method: "DELETE", + }), }), getWidgetExtCSS: builder.query({ query: () => ({ url: "/resource/widget-extra.css", - responseHandler: "text" - }) + responseHandler: "text", + }), }), updateWidgetExtCSS: builder.mutation({ query: (css) => ({ url: "/resource/widget-extra.css", method: "PUT", - body: { data: css } - }) + body: { data: css }, + }), }), getBotRelatedChannels: builder.query({ query: ({ api_key, public_only = false }) => ({ url: public_only ? `/bot?public_only=${public_only}` : `/bot`, headers: { - "x-api-key": api_key - } - }) + "x-api-key": api_key, + }, + }), }), sendMessageByBot: builder.mutation< number, @@ -426,14 +437,14 @@ export const serverApi = createApi({ headers: { "x-api-key": api_key, "content-type": ContentTypes[type], - "X-Properties": properties ? encodeBase64(JSON.stringify(properties)) : "" + "X-Properties": properties ? encodeBase64(JSON.stringify(properties)) : "", }, url: cid ? `/bot/send_to_group/${cid}` : `/bot/send_to_user/${uid}`, method: "POST", - body: content - }) - }) - }) + body: content, + }), + }), + }), }); export const { @@ -484,5 +495,8 @@ export const { useLazyGetAgoraUsersByChannelQuery, useLazyClearAllFilesQuery, useLazyClearAllMessagesQuery, - useLazyGetFilesQuery + useLazyGetFilesQuery, + useGetVocespaceConfigQuery, + useLazyGetVocespaceConfigQuery, + useUpdateVocespaceConfigMutation, } = serverApi; diff --git a/src/components/BlankPlaceholder.tsx b/src/components/BlankPlaceholder.tsx index fb760055..df6b6a35 100644 --- a/src/components/BlankPlaceholder.tsx +++ b/src/components/BlankPlaceholder.tsx @@ -1,6 +1,6 @@ -import { FC, useState } from "react"; +import { FC, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { NavLink } from "react-router-dom"; +import { NavLink, useNavigate } from "react-router-dom"; import Linkify from "linkify-react"; import { useAppSelector } from "@/app/store"; @@ -15,6 +15,8 @@ import UsersModal from "./UsersModal"; import { shallowEqual } from "react-redux"; import { KEY_ADMIN_ONLY_INVITE } from "../app/config"; import useServerExtSetting from "../hooks/useServerExtSetting"; +import useLicense from "@/hooks/useLicense"; +import { compareVersion } from "@/utils"; interface Props { type?: "chat" | "user"; @@ -25,6 +27,7 @@ const classes = { boxTip: "px-5 text-xs md:text-sm text-slate-600 dark:text-gray-100 font-bold text-center", }; const BlankPlaceholder: FC = ({ type = "chat" }) => { + const navigate = useNavigate(); const { getExtSetting } = useServerExtSetting(); const onlyAdminCanInvite = getExtSetting(KEY_ADMIN_ONLY_INVITE); const { t } = useTranslation("welcome"); @@ -44,6 +47,19 @@ const BlankPlaceholder: FC = ({ type = "chat" }) => { }; const chatTip = type == "chat" ? t("start_by_channel") : t("start_by_dm"); const chatHandler = type == "chat" ? toggleChannelModalVisible : toggleUserListVisible; + + const { license: licenseInfo } = useLicense(true); + + const toLicensePage = () => { + navigate("/setting/license"); + }; + const currentVersion = useAppSelector((store) => store.server.version, shallowEqual); + console.warn("currentVersion", currentVersion, compareVersion(currentVersion, "0.5.6")); + const showVoceSpace = useMemo( + () => compareVersion(currentVersion, "0.5.6") >= 0, + [currentVersion] + ); + return ( <>
@@ -84,18 +100,99 @@ const BlankPlaceholder: FC = ({ type = "chat" }) => { )}

-
+
{(isAdmin || !onlyAdminCanInvite) && ( )} - - {!server.upgraded && ( + + {licenseInfo?.user_limit == 20 && showVoceSpace ? ( + + ) : ( + + )} + + {/* Vocespace nav to */} + {showVoceSpace ? ( + + + + + + + + + + +
{t("vocespace")}
+
+ ) : ( + + +
{t("download")}
+
+ )} + {/* {!server.upgraded && ( <> = ({ type = "chat" }) => {
{t("help")}
- )} + )} */} + + + +
{t("help")}
+
{createChannelVisible && ( )} + {userListVisible && } {inviteModalVisible && } diff --git a/src/components/styled/Modal.tsx b/src/components/styled/Modal.tsx index 3dba827b..c23b664d 100644 --- a/src/components/styled/Modal.tsx +++ b/src/components/styled/Modal.tsx @@ -1,5 +1,6 @@ import { FC, ReactNode } from "react"; import clsx from "clsx"; +import { useTranslation } from "react-i18next"; interface Props { title?: string; @@ -8,6 +9,7 @@ interface Props { children?: ReactNode; className?: string; compact?: boolean; + footer?: ReactNode; } const StyledModal: FC = ({ @@ -16,7 +18,8 @@ const StyledModal: FC = ({ description = "", buttons, children, - className + className, + footer, }) => { return (
= ({ )} {children} {buttons &&
{buttons}
} + {footer && footer}
); }; diff --git a/src/hooks/useConfig.ts b/src/hooks/useConfig.ts index f07ea670..f2dc75eb 100644 --- a/src/hooks/useConfig.ts +++ b/src/hooks/useConfig.ts @@ -8,16 +8,27 @@ import { useGetFirebaseConfigQuery, useGetLoginConfigQuery, useGetSMTPConfigQuery, + useGetVocespaceConfigQuery, useUpdateAgoraConfigMutation, useUpdateFirebaseConfigMutation, useUpdateLoginConfigMutation, - useUpdateSMTPConfigMutation + useUpdateSMTPConfigMutation, + useUpdateVocespaceConfigMutation, } from "@/app/services/server"; -import { AgoraConfig, FirebaseConfig, LoginConfig, SMTPConfig } from "@/types/server"; +import { + AgoraConfig, + FirebaseConfig, + LoginConfig, + SMTPConfig, + VocespaceConfig, +} from "@/types/server"; // config: smtp agora login firebase -type ConfigType = "smtp" | "agora" | "login" | "firebase"; -type ConfigMap = Record; +type ConfigType = "smtp" | "agora" | "login" | "firebase" | "vocespace"; +type ConfigMap = Record< + ConfigType, + AgoraConfig | FirebaseConfig | LoginConfig | SMTPConfig | VocespaceConfig +>; type valuesOf = T[keyof T]; let originalValue: valuesOf | undefined = undefined; // type valueOf = T[config]; @@ -31,21 +42,29 @@ export default function useConfig(config: keyof ConfigMap = "smtp") { useUpdateSMTPConfigMutation(); const [updateAgoraConfig, { isSuccess: AgoraUpdated, isLoading: AgoraUpdating }] = useUpdateAgoraConfigMutation(); + const [updateVocespaceConfig, { isSuccess: VocespaceUpdated, isLoading: VocespaceUpdating }] = + useUpdateVocespaceConfigMutation(); const [updateFirebaseConfig, { isSuccess: FirebaseUpdated, isLoading: FirebaseUpdating }] = useUpdateFirebaseConfigMutation(); const { refetch: getAgoraConfig, data: agoraConfig } = useGetAgoraConfigQuery(undefined, { - skip: config !== "agora" + skip: config !== "agora", }); + const { refetch: getVocespaceConfig, data: vocespaceConfig } = useGetVocespaceConfigQuery( + undefined, + { + skip: config !== "vocespace", + } + ); const { refetch: getLoginConfig, data: loginConfig } = useGetLoginConfigQuery(undefined, { - skip: config !== "login" + skip: config !== "login", }); const { refetch: getSMTPConfig, data: smtpConfig } = useGetSMTPConfigQuery(undefined, { - skip: config !== "smtp" + skip: config !== "smtp", }); const { refetch: getFirebaseConfig, data: firebaseConfig } = useGetFirebaseConfigQuery( undefined, { - skip: config !== "firebase" + skip: config !== "firebase", } ); @@ -53,25 +72,29 @@ export default function useConfig(config: keyof ConfigMap = "smtp") { login: updateLoginConfig, smtp: updateSMTPConfig, agora: updateAgoraConfig, - firebase: updateFirebaseConfig + firebase: updateFirebaseConfig, + vocespace: updateVocespaceConfig, }; const requests = { smtp: getSMTPConfig, agora: getAgoraConfig, firebase: getFirebaseConfig, - login: getLoginConfig + login: getLoginConfig, + vocespace: getVocespaceConfig, }; const updates = { login: LoginUpdated, smtp: SMTPUpdated, agora: AgoraUpdated, - firebase: FirebaseUpdated + firebase: FirebaseUpdated, + vocespace: VocespaceUpdated, }; const updatings = { login: LoginUpdating, smtp: SMTPUpdating, agora: AgoraUpdating, - firebase: FirebaseUpdating + firebase: FirebaseUpdating, + vocespace: VocespaceUpdating, }; const updateConfig = updateFns[config]; const refetch = requests[config]; @@ -98,12 +121,12 @@ export default function useConfig(config: keyof ConfigMap = "smtp") { } }, [updated]); useEffect(() => { - const _config = smtpConfig || firebaseConfig || loginConfig || agoraConfig; + const _config = smtpConfig || firebaseConfig || loginConfig || agoraConfig || vocespaceConfig; if (_config) { originalValue = _config; setValues(_config); } - }, [smtpConfig, firebaseConfig, loginConfig, agoraConfig]); + }, [smtpConfig, firebaseConfig, loginConfig, agoraConfig, vocespaceConfig]); useEffect(() => { // 空对象 if (!values || Object.keys(values).length == 0) return; @@ -124,6 +147,7 @@ export default function useConfig(config: keyof ConfigMap = "smtp") { agoraConfig, values, setValues, - toggleEnable + toggleEnable, + refetch, }; } diff --git a/src/routes/chat/ChannelChat/index.tsx b/src/routes/chat/ChannelChat/index.tsx index 5e948656..95abd8dd 100644 --- a/src/routes/chat/ChannelChat/index.tsx +++ b/src/routes/chat/ChannelChat/index.tsx @@ -53,7 +53,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) { dispatch( updateChannelVisibleAside({ id: cid, - aside: visibleAside !== "members" ? "members" : null + aside: visibleAside !== "members" ? "members" : null, }) ); }; diff --git a/src/routes/chat/VoiceChat/index.tsx b/src/routes/chat/VoiceChat/index.tsx index b5db47e6..33794c67 100644 --- a/src/routes/chat/VoiceChat/index.tsx +++ b/src/routes/chat/VoiceChat/index.tsx @@ -2,36 +2,62 @@ import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; import { shallowEqual, useDispatch } from "react-redux"; -import { useGetAgoraStatusQuery } from "@/app/services/server"; +import { useGetAgoraStatusQuery, useGetVocespaceConfigQuery } from "@/app/services/server"; import { updateChannelVisibleAside, updateDMVisibleAside } from "@/app/slices/footprint"; import { updateCallInfo } from "@/app/slices/voice"; import { useAppSelector } from "@/app/store"; import { ChatContext } from "@/types/common"; import Tooltip from "@/components/Tooltip"; import { useVoice } from "@/components/Voice"; -import { isInIframe } from "@/utils"; +import { compareVersion, isInIframe } from "@/utils"; import IconHeadphone from "@/assets/icons/headphone.svg"; +import { useEffect, useMemo, useState } from "react"; +import useSendMessage from "@/hooks/useSendMessage"; +import { useNavigate } from "react-router-dom"; +import { VocespaceConfig } from "@/types/server"; type Props = { context?: ChatContext; id: number; }; + const isIframe = isInIframe(); const VoiceChat = ({ id, context = "channel" }: Props) => { + const [chatType, setChatType] = useState<"agora" | "vocespace">("vocespace"); + const currentVersion = useAppSelector((store) => store.server.version, shallowEqual); + const showVoceSpace = useMemo(() => { + return compareVersion(currentVersion, "0.5.6") >= 0; + }, [currentVersion]); + const { joinVoice, joined, joining = false, joinedAtThisContext } = useVoice({ id, context }); const dispatch = useDispatch(); const loginUid = useAppSelector((store) => store.authData.user?.uid ?? 0, shallowEqual); const visibleAside = useAppSelector( - (store) => (context == "channel" ? store.footprint.channelAsides[id] : null), + (store) => (context == "channel" ? "voice" : null), shallowEqual ); + const voiceList = useAppSelector((store) => store.voice.list, shallowEqual); const { data: enabled } = useGetAgoraStatusQuery(); + const [vocespaceConfig, setVoceSpaceConfig] = useState(undefined); const { t } = useTranslation("chat"); + const { data: conf } = showVoceSpace ? useGetVocespaceConfigQuery() : { data: undefined }; + + useEffect(() => { + if (!showVoceSpace) { + setChatType("agora"); + } else { + if (conf) { + setVoceSpaceConfig(conf); + setChatType("vocespace"); + } + } + }, [showVoceSpace]); + const toggleDashboard = () => { const data = { id, - aside: visibleAside == "voice" ? null : ("voice" as const) + aside: visibleAside == "voice" ? null : ("voice" as const), }; dispatch(context == "channel" ? updateChannelVisibleAside(data) : updateDMVisibleAside(data)); }; @@ -43,7 +69,7 @@ const VoiceChat = ({ id, context = "channel" }: Props) => { joinVoice(); const data = { id, - aside: "voice" as const + aside: "voice" as const, }; dispatch(context == "channel" ? updateChannelVisibleAside(data) : updateDMVisibleAside(data)); // 实时显示calling box @@ -55,19 +81,57 @@ const VoiceChat = ({ id, context = "channel" }: Props) => { // todo toast.error("Voice is not supported in iframe"); }; - if (loginUid == 0 || !enabled) return null; + if (loginUid == 0) return null; const visible = visibleAside == "voice"; const memberCount = voiceList.find((v) => v.context == context && v.id == id)?.memberCount ?? 0; const badgeClass = `absolute -top-2 -right-2 w-4 h-4 rounded-full bg-primary-400 text-white `; + const { sendMessage } = useSendMessage({ context, from: loginUid, to: id }); + const replying_mid = useAppSelector( + (store) => store.message.replying[`${context}_${id}`], + shallowEqual + ); + + const navigate = useNavigate(); + + const sendVoceSpaceMsg = async (url: string) => { + await sendMessage({ + reply_mid: replying_mid, + type: "text", + content: `Join Vocespace Meeting: ${url}`, + from_uid: loginUid, + }); + }; + + const handleSendVocespaceRequest = async () => { + if (vocespaceConfig && vocespaceConfig.state && vocespaceConfig.state === "undeployed") { + await sendVoceSpaceMsg(`https://vocespace.com/${context}_${id}`); + } else if (vocespaceConfig && vocespaceConfig?.enabled && vocespaceConfig.state === "success") { + let url = vocespaceConfig.url; + if (url.includes(":7880")) { + url = `http://${url.replace(":7880", ":3008")}/${context}_${id}`; + } else { + url = `https://${url}/${context}_${id}`; + } + await sendVoceSpaceMsg(url); + } else { + // 跳转到/setting/video + navigate("/setting/video"); + } + }; + const handleOnClick = async () => { + if (chatType === "agora" && enabled) { + return isIframe ? handleInIframe() : joinedAtThisContext ? toggleDashboard() : handleJoin(); + } else { + // 向当前频道中发送Vocespace链接 + await handleSendVocespaceRequest(); + } + }; + return (
  • - - {visible ? null : ( + + {/* {visible ? null : ( <> {memberCount > 0 && ( { + - )} + )} */} + <> + {memberCount > 0 && ( + + {memberCount} + + )} + + + + +
  • ); diff --git a/src/routes/setting/License/LicensePriceListModal.tsx b/src/routes/setting/License/LicensePriceListModal.tsx index 5b903377..7a9bef23 100644 --- a/src/routes/setting/License/LicensePriceListModal.tsx +++ b/src/routes/setting/License/LicensePriceListModal.tsx @@ -72,7 +72,7 @@ const LicensePriceListModal: FC = ({ closeModal }) => { user_limit: Number(user_limit), expire: type == "subscription" ? getExpireDay(sub_dur) : getExpireDay("year"), // 本地,则*通配符 - domain: host.startsWith("localhost") ? "*" : host + domain: host.startsWith("localhost") ? "*" : host, }; console.log(metadata); // return; @@ -82,7 +82,7 @@ const LicensePriceListModal: FC = ({ closeModal }) => { priceId, metadata, cancel_url: location.href, - success_url: `${location.origin}/#/cb/payment_success` + success_url: `${location.origin}/#/cb/payment_success`, }); if ("error" in resp) { toast.error("Payment link initialized failed!"); @@ -103,13 +103,18 @@ const LicensePriceListModal: FC = ({ closeModal }) => { setPopUpVisible((prev) => !prev); }; const handleTalk = () => { - window.open("https://calendly.com/hansu", "_blank"); + window.open("https://buy.stripe.com/bJeaEX9ex2PUer2aLe6c00O", "_blank"); }; const isBooking = selectPrice.includes("booking"); return ( + {t("vocespace.prerequisite.4")} + + } // className="!min-w-[480px]" title={t("license.renew")} description={t("license.renew_select")} @@ -119,7 +124,12 @@ const LicensePriceListModal: FC = ({ closeModal }) => { {ct("action.cancel")} {isBooking ? ( - + ) : ( = ({ closeModal }) => { {" "} {t("license.renew")} - {/* */} )} diff --git a/src/routes/setting/config/Agora.tsx b/src/routes/setting/config/Agora.tsx index fb42d2ca..05b0cb61 100644 --- a/src/routes/setting/config/Agora.tsx +++ b/src/routes/setting/config/Agora.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent } from "react"; +import { ChangeEvent, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { AgoraConfig } from "@/types/server"; @@ -9,10 +9,32 @@ import Toggle from "@/components/styled/Toggle"; import useConfig from "@/hooks/useConfig"; import HowToTip from "./HowToTip"; import { ConfigTip } from "@/components/ConfigTip"; +import { ConfigVocespace } from "./Vocespace"; +import clsx from "clsx"; +import { shallowEqual } from "react-redux"; +import { useAppSelector } from "@/app/store"; +import { compareVersion } from "@/utils"; export default function ConfigAgora() { const { t } = useTranslation("setting", { keyPrefix: "agora" }); + const [isAgora, setIsAgora] = useState(false); + const currentVersion = useAppSelector((store) => store.server.version, shallowEqual); + const showVoceSpace = useMemo(() => { + return compareVersion(currentVersion, "0.5.6") >= 0; + }, [currentVersion]); + useEffect(() => { + if (showVoceSpace) { + setIsAgora(false); + } else { + setIsAgora(true); + } + }, [showVoceSpace]); const { changed, reset, values, setValues, toggleEnable, updateConfig } = useConfig("agora"); + const getClass = (selected: boolean) => + clsx( + `cursor-pointer p-2 box-border flex-center`, + selected && `border border-solid border-primary-400 shadow rounded-lg` + ); const handleUpdate = () => { // const { token_url, description } = values; const _values = values as AgoraConfig; @@ -30,6 +52,7 @@ export default function ConfigAgora() { return { ...prev, [type]: newValue }; }); }; + if (!values) return null; const { url, @@ -38,95 +61,114 @@ export default function ConfigAgora() { app_certificate, customer_id, customer_secret, - enabled = false + enabled = false, } = values as AgoraConfig; const _url = url || "https://api.agora.io"; return (
    - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    + {showVoceSpace && ( +
      +
    • setIsAgora(false)}> + +
    • +
    • setIsAgora(true)}> + +
    • +
    + )} - {changed && } - {/* */} + {isAgora ? ( + <> + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + {changed && } + + ) : ( + + )}
    ); } diff --git a/src/routes/setting/config/Vocespace.tsx b/src/routes/setting/config/Vocespace.tsx new file mode 100644 index 00000000..26d3296f --- /dev/null +++ b/src/routes/setting/config/Vocespace.tsx @@ -0,0 +1,199 @@ +import { ConfigTip } from "@/components/ConfigTip"; +import { useTranslation } from "react-i18next"; +import HowToTip from "./HowToTip"; +import Toggle from "@/components/styled/Toggle"; +import Input from "@/components/styled/Input"; +import Label from "@/components/styled/Label"; +import SaveTip from "@/components/SaveTip"; +import useConfig from "@/hooks/useConfig"; +import { VocespaceConfig } from "@/types/server"; +import { ChangeEvent, useEffect, useRef, useState } from "react"; +import { isNull } from "lodash"; + +export function ConfigVocespace() { + const { t } = useTranslation("setting", { keyPrefix: "vocespace" }); + const [fetchData, setFetchData] = useState(null); + useEffect(() => { + if (!isNull(fetchData?.state) && fetchData?.state !== "in_progress") { + // 停止轮询 + if (interval.current) { + clearInterval(interval.current); + } + } + }, [fetchData]); + const { changed, reset, values, setValues, toggleEnable, updateConfig, refetch } = + useConfig("vocespace"); + // 定时器,用来每隔2分钟获取一次最新的配置状态,只要发现有state字段就能知道自动化部署是否成功,没有则继续轮询 + const interval = useRef(null); + + const handleUpdate = async () => { + const _values = values as VocespaceConfig; + if (_values.url.trim() === "" && _values.enabled) { + alert("Custom domain is required when enabling Vocespace."); + return; + } + + // 后端快速返回结果,只要检测环境可以执行就立即返回成功,不然会一直pending等待,导致前端超时 + const { error } = await updateConfig(_values); + if (error) { + alert(`Auto Deploy Failed: ${error}`); + } else { + // 创建一个轮询定时器,每隔2分钟获取一次最新的配置状态 + if (interval.current) { + clearInterval(interval.current); + } + // 先fetch一次,避免等待2分钟的空档期 + const { data } = await refetch(); + setFetchData(data as VocespaceConfig); + interval.current = setInterval(async () => { + console.warn("Fetching Vocespace Config for deployment status..."); + const { data } = await refetch(); + setFetchData(data as VocespaceConfig); + }, 2 * 60 * 1000); + } + }; + + const handleChange = (evt: ChangeEvent) => { + const newValue = evt.target.value; + const { type = "" } = evt.target.dataset; + setValues((prev) => { + if (!prev) return prev; + return { ...prev, [type]: newValue }; + }); + }; + if (!values) return null; + const { url, license, enabled = false, password, state } = values as VocespaceConfig; + + return ( +
    + +
    +
    + + + +
    +
    + +
      +
    1. {t("prerequisite.1")}
    2. +
    3. {t("prerequisite.2")}
    4. +
    5. {t("prerequisite.3")}
    6. +
    +
    + +
    +
    + +

    {t("domain_desc")}

    +
    + + +
    +
    + + +
    +
    +
    + {state ? ( +
    + {state === "success" && ( + + )} + {state === "in_progress" && ( + + )} + {state === "failed" && ( + + )} + {state === "undeployed" && ( + + )} +
    + ) : ( + + )} +
    + {t("prerequisite.4")} +
    +
    + {changed && } + {/* */} +
    + ); +} + +function StateDot({ color, className, word }: { color: string; className?: string; word: string }) { + return ( +
    +
    + {word} +
    + ); +} diff --git a/src/routes/setting/navs.tsx b/src/routes/setting/navs.tsx index 2dd3a893..12d1b7d6 100644 --- a/src/routes/setting/navs.tsx +++ b/src/routes/setting/navs.tsx @@ -16,6 +16,7 @@ import MyAccount from "./MyAccount"; import Overview from "./Overview"; import Widget from "./Widget"; import { shallowEqual } from "react-redux"; +import { ConfigVocespace } from "./config/Vocespace"; const dataManagementNav = { name: "data_management", @@ -54,7 +55,7 @@ const navs = [ component: , }, { - name: "agora", + name: "video", component: , }, { diff --git a/src/types/server.ts b/src/types/server.ts index 50c3c8e4..8bd46787 100644 --- a/src/types/server.ts +++ b/src/types/server.ts @@ -41,6 +41,15 @@ export interface AgoraConfig { customer_id: string; customer_secret: string; } + +export interface VocespaceConfig { + enabled: boolean; + password: string; + url: string; + license: string; + state: "in_progress" | "success" | "failed" | "undeployed" | ""; +} + export interface AgoraVoicingListResponse { success: boolean; data: {