[feature-136] add translations

This commit is contained in:
MathieuG-P
2023-05-20 23:21:07 +02:00
parent bdc893d8a5
commit 9d230461de
16 changed files with 692 additions and 356 deletions
+150 -73
View File
@@ -598,8 +598,148 @@
"unlink-folder": "Ordner Verknüpfung Aufheben",
"link-all": "Alle verlinken"
}
}
},
"maps": {
"map-filter-panel": {
"duration": "Länge",
"tags": "Stichworte",
"specificities": "Allgemein",
"requirements": "Anforderungen"
},
"models": {
"map-types": {
"accuracy": "Präzision",
"balanced": "Ausgeglichen",
"challenge": "Herausforderung",
"dancestyle": "Tanz",
"fitness": "Fitness",
"speed": "Geschwindigkeit",
"tech": "Tech"
},
"map-styles": {
"dance": "Tanz",
"swing": "Swing",
"nightcore": "Nightcore",
"folk": "Folk",
"family": "Familie",
"ambient": "umgebung",
"funk": "Funk",
"jazz": "Jazz",
"soul": "Soul",
"speedcore": "Speedcore",
"punk": "Punk",
"rb": "R&B",
"holiday": "Urlaub",
"vocaloid": "Vokaloid",
"jrock": "J-Rock",
"trance": "Trance",
"drumbass": "Basstrommel",
"comedy": "Komödie",
"instrumental": "Instrumental",
"hardcore": "Hardcore",
"kpop": "K-Pop",
"indie": "Indie",
"techno": "Techno",
"house": "House",
"game": "Videospiele",
"film": "Filme",
"alt": "Alternativ",
"dubstep": "Dubstep",
"metal": "Metal",
"anime": "Anime",
"hiphop": "HipHop",
"jpop": "J-Pop",
"rock": "Rock",
"pop": "Pop",
"electronic": "Elektronisch",
"classical-orchestral": "Klassisch & Orchestra"
},
"map-specificities": {
"automapper": "KI",
"ranked": "ranked",
"curated": "curated",
"verified": "verifiziert",
"fullSpread": "full spread"
},
"difficulties": {
"Easy": "einfach",
"Normal": "normal",
"Hard": "hard",
"Expert": "expert",
"ExpertPlus": "expert+"
},
"map-item": {
"by": "Von {songAutor}",
"mapped-by": "Gemapped von"
}
},
"models":{
"models": "Modelle",
"types": {
"singular": {
"avatar": "Avatar",
"saber": "Säbel",
"platform": "Plattform",
"bloq": "Block"
},
"plural": {
"avatar": "Avatare",
"saber": "Säbel",
"platform": "Plattformen",
"bloq": "Blöcke"
}
},
"sorts": {
"name": "Name",
"date": "Datum",
"author": "Autor"
},
"panel": {
"actions": {
"search": "Ein Modell suchen",
"drop-down": {
"delete": "Modelle löschen",
"export": "Modelle exportieren"
},
"link-models": "Modelle verlinken",
"unlink-models": "Modelle trennen"
},
"grid":{
"loading": "Modelle laden...",
"no-models": "Keine Modelle",
"download-models": "Modelle herunterladen"
}
},
"modals": {
"delete-model" :{
"title": "Das Modell löschen",
"desc": "Sind Sie sicher, dass Sie das Modell {modelName} löschen möchten?",
"linked-annotation": "Dieses Modell wird aus allen verlinkten Versionen entfernt."
},
"delete-models": {
"title": "Modelle löschen",
"desc": "Sind Sie sicher, dass Sie die {nb} Modelle löschen möchten?",
"linked-annotation": "Diese Modelle werden aus allen verlinkten Versionen entfernt."
},
"download-models": {
"search-btn": "Suche",
"search-placeholder": "Ein Modell suchen",
"search-tips": {
"header": {
"tag": "Tag",
"desc": "Beschreibung"
},
"author-desc": "Nur Modelle vom angegebenen Autor anzeigen.",
"hash-desc": "Nur Modelle mit dem angegebenen Hash anzeigen.",
"tag-desc": "Nur Modelle mit dem angegebenen Tag anzeigen.",
"name-desc": "Nur Modelle mit dem angegebenen Namen anzeigen.",
"discordid-desc": "Nur Modelle vom angegebenen Discord-Benutzer anzeigen.",
"status-desc": "Nur Modelle mit dem angegebenen Status anzeigen. (nur Profil, und nur für den Autor)"
},
"no-models": "Keine Modelle gefunden.",
"no-internet": "Keine Internetverbindung.",
"error-occured": "Ein Fehler ist aufgetreten, versuche es später erneut."
},
"link-models": {
"avatar": {
"title": "Avatare verbinden",
@@ -680,79 +820,16 @@
"valid-btn": "Bloqs trennen"
}
}
}
},
"maps": {
"map-filter-panel": {
"duration": "Länge",
"tags": "Stichworte",
"specificities": "Allgemein",
"requirements": "Anforderungen"
},
"map-types": {
"accuracy": "Präzision",
"balanced": "Ausgeglichen",
"challenge": "Herausforderung",
"dancestyle": "Tanz",
"fitness": "Fitness",
"speed": "Geschwindigkeit",
"tech": "Tech"
},
"map-styles": {
"dance": "Tanz",
"swing": "Swing",
"nightcore": "Nightcore",
"folk": "Folk",
"family": "Familie",
"ambient": "umgebung",
"funk": "Funk",
"jazz": "Jazz",
"soul": "Soul",
"speedcore": "Speedcore",
"punk": "Punk",
"rb": "R&B",
"holiday": "Urlaub",
"vocaloid": "Vokaloid",
"jrock": "J-Rock",
"trance": "Trance",
"drumbass": "Basstrommel",
"comedy": "Komödie",
"instrumental": "Instrumental",
"hardcore": "Hardcore",
"kpop": "K-Pop",
"indie": "Indie",
"techno": "Techno",
"house": "House",
"game": "Videospiele",
"film": "Filme",
"alt": "Alternativ",
"dubstep": "Dubstep",
"metal": "Metal",
"anime": "Anime",
"hiphop": "HipHop",
"jpop": "J-Pop",
"rock": "Rock",
"pop": "Pop",
"electronic": "Elektronisch",
"classical-orchestral": "Klassisch & Orchestra"
},
"map-specificities": {
"automapper": "KI",
"ranked": "ranked",
"curated": "curated",
"verified": "verifiziert",
"fullSpread": "full spread"
},
"difficulties": {
"Easy": "einfach",
"Normal": "normal",
"Hard": "hard",
"Expert": "expert",
"ExpertPlus": "expert+"
},
"map-item": {
"by": "Von {songAutor}",
"mapped-by": "Gemapped von"
"notifications": {
"prevent-for-mods": {
"title": "Erforderliche Mods",
"desc": "Stellen Sie sicher, dass Sie die notwendigen Mods installiert haben, um Modelle in BeatSaber zu verwenden",
"go-to-mods": "Gehe zu Mods"
},
"export-success": {
"title": "Export abgeschlossen 🎉"
}
}
},
"beat-saver": {
+150 -73
View File
@@ -598,8 +598,148 @@
"unlink-folder": "Unlink Folder",
"link-all": "Link all"
}
}
},
"maps": {
"map-filter-panel": {
"duration": "Duration",
"tags": "tags",
"specificities": "general",
"requirements": "requirements"
},
"models": {
"map-types": {
"accuracy": "accuracy",
"balanced": "balanced",
"challenge": "challenge",
"dancestyle": "dance",
"fitness": "fitness",
"speed": "speed",
"tech": "tech"
},
"map-styles": {
"dance": "dance",
"swing": "swing",
"nightcore": "nightcore",
"folk": "folk",
"family": "family",
"ambient": "ambient",
"funk": "funk",
"jazz": "jazz",
"soul": "soul",
"speedcore": "speedcore",
"punk": "punk",
"rb": "r&b",
"holiday": "holiday",
"vocaloid": "vocaloid",
"jrock": "j-rock",
"trance": "trance",
"drumbass": "drum & bass",
"comedy": "comedy",
"instrumental": "instrumental",
"hardcore": "hardcore",
"kpop": "k-pop",
"indie": "indie",
"techno": "techno",
"house": "house",
"game": "video game",
"film": "film",
"alt": "alternative",
"dubstep": "dubstep",
"metal": "metal",
"anime": "anime",
"hiphop": "hiphop",
"jpop": "j-pop",
"rock": "rock",
"pop": "pop",
"electronic": "electronic",
"classical-orchestral": "Classical & Orchestral"
},
"map-specificities": {
"automapper": "AI",
"ranked": "ranked",
"curated": "curated",
"verified": "verified",
"fullSpread": "full spread"
},
"difficulties": {
"Easy": "easy",
"Normal": "normal",
"Hard": "hard",
"Expert": "expert",
"ExpertPlus": "expert+"
},
"map-item": {
"by": "By {songAutor}",
"mapped-by": "mapped by"
}
},
"models":{
"models": "Models",
"types": {
"singular": {
"avatar": "avatar",
"saber": "saber",
"platform": "platform",
"bloq": "block"
},
"plural": {
"avatar": "avatars",
"saber": "sabers",
"platform": "platforms",
"bloq": "blocks"
}
},
"sorts": {
"name": "Name",
"date": "Date",
"author": "Author"
},
"panel": {
"actions": {
"search": "Search a model",
"drop-down": {
"delete": "Delete models",
"export": "Export models"
},
"link-models": "Link models",
"unlink-models": "Unlink models"
},
"grid":{
"loading": "Loading models...",
"no-models": "No models",
"download-models": "Download models"
}
},
"modals": {
"delete-model" :{
"title": "Delete the model",
"desc": "Are you sure you want to delete the model {modelName}?",
"linked-annotation": "This model will be removed from all linked versions."
},
"delete-models": {
"title": "Delete models",
"desc": "Are you sure you want to delete the {nb} models?",
"linked-annotation": "These models will be removed from all linked versions."
},
"download-models": {
"search-btn": "Search",
"search-placeholder": "Search a model",
"search-tips": {
"header": {
"tag": "Tag",
"desc": "Description"
},
"author-desc": "Show only models from the specified author.",
"hash-desc": "Show only models with the specified hash.",
"tag-desc": "Show only models with the specified tag.",
"name-desc": "Show only models with the specified name.",
"discordid-desc": "Show only models from the specified discord user.",
"status-desc": "Show only models with the specified status. (profile only, and only for the author)"
},
"no-models": "No models found.",
"no-internet": "No internet connection.",
"error-occured": "An error occurred, try again later."
},
"link-models": {
"avatar": {
"title": "Link Avatars",
@@ -680,79 +820,16 @@
"valid-btn": "Unlink Bloqs"
}
}
}
},
"maps": {
"map-filter-panel": {
"duration": "Duration",
"tags": "tags",
"specificities": "general",
"requirements": "requirements"
},
"map-types": {
"accuracy": "accuracy",
"balanced": "balanced",
"challenge": "challenge",
"dancestyle": "dance",
"fitness": "fitness",
"speed": "speed",
"tech": "tech"
},
"map-styles": {
"dance": "dance",
"swing": "swing",
"nightcore": "nightcore",
"folk": "folk",
"family": "family",
"ambient": "ambient",
"funk": "funk",
"jazz": "jazz",
"soul": "soul",
"speedcore": "speedcore",
"punk": "punk",
"rb": "r&b",
"holiday": "holiday",
"vocaloid": "vocaloid",
"jrock": "j-rock",
"trance": "trance",
"drumbass": "drum & bass",
"comedy": "comedy",
"instrumental": "instrumental",
"hardcore": "hardcore",
"kpop": "k-pop",
"indie": "indie",
"techno": "techno",
"house": "house",
"game": "video game",
"film": "film",
"alt": "alternative",
"dubstep": "dubstep",
"metal": "metal",
"anime": "anime",
"hiphop": "hiphop",
"jpop": "j-pop",
"rock": "rock",
"pop": "pop",
"electronic": "electronic",
"classical-orchestral": "Classical & Orchestral"
},
"map-specificities": {
"automapper": "AI",
"ranked": "ranked",
"curated": "curated",
"verified": "verified",
"fullSpread": "full spread"
},
"difficulties": {
"Easy": "easy",
"Normal": "normal",
"Hard": "hard",
"Expert": "expert",
"ExpertPlus": "expert+"
},
"map-item": {
"by": "By {songAutor}",
"mapped-by": "mapped by"
"notifications": {
"prevent-for-mods": {
"title": "Required mods",
"desc": "Make sure you've installed the necessary mods to use models in BeatSaber",
"go-to-mods": "Go to mods"
},
"export-success": {
"title": "Export completed 🎉"
}
}
},
"beat-saver": {
+150 -73
View File
@@ -597,8 +597,148 @@
"unlink-folder": "Desenlazar Carpeta",
"link-all": "Enlazar todo"
}
}
},
"maps": {
"map-filter-panel": {
"duration": "Duración",
"tags": "tags",
"specificities": "general",
"requirements": "requisitos"
},
"models": {
"map-types": {
"accuracy": "precisión",
"balanced": "equilibrado",
"challenge": "desafío",
"dancestyle": "baile",
"fitness": "fitness",
"speed": "speed",
"tech": "tec"
},
"map-styles": {
"dance": "baile",
"swing": "swing",
"nightcore": "nightcore",
"folk": "folk",
"family": "familia",
"ambient": "ambiente",
"funk": "funk",
"jazz": "jazz",
"soul": "soul",
"speedcore": "speedcore",
"punk": "punk",
"rb": "r&b",
"holiday": "vacaciones",
"vocaloid": "vocaloid",
"jrock": "j-rock",
"trance": "trance",
"drumbass": "drum & bass",
"comedy": "comedia",
"instrumental": "instrumental",
"hardcore": "hardcore",
"kpop": "k-pop",
"indie": "indie",
"techno": "tecno",
"house": "house",
"game": "videojuego",
"film": "film",
"alt": "alternativa",
"dubstep": "dubstep",
"metal": "metal",
"anime": "anime",
"hiphop": "hiphop",
"jpop": "j-pop",
"rock": "rock",
"pop": "pop",
"electronic": "electrónico",
"classical-orchestral": "Clásico y orquestal"
},
"map-specificities": {
"automapper": "IA",
"ranked": "rankado",
"curated": "recomendado",
"verified": "verificado",
"fullSpread": "panel completo"
},
"difficulties": {
"Easy": "fácil",
"Normal": "normal",
"Hard": "difícil",
"Expert": "experto",
"ExpertPlus": "experto+"
},
"map-item": {
"by": "Por {songAutor}",
"mapped-by": "mapeado por"
}
},
"models":{
"models": "Modelos",
"types": {
"singular": {
"avatar": "avatar",
"saber": "sable",
"platform": "plataforma",
"bloq": "bloque"
},
"plural": {
"avatar": "avatares",
"saber": "sables",
"platform": "plataformas",
"bloq": "bloques"
}
},
"sorts": {
"name": "Nombre",
"date": "Fecha",
"author": "Autor"
},
"panel": {
"actions": {
"search": "Buscar un modelo",
"drop-down": {
"delete": "Eliminar modelos",
"export": "Exportar modelos"
},
"link-models": "Vincular modelos",
"unlink-models": "Desvincular los modelos"
},
"grid":{
"loading": "Cargando modelos...",
"no-models": "No hay modelos",
"download-models": "Descargar modelos"
}
},
"modals": {
"delete-model" :{
"title": "Eliminar el modelo",
"desc": "¿Estás seguro de que quieres eliminar el modelo {modelName}?",
"linked-annotation": "Este modelo será eliminado de todas las versiones vinculadas."
},
"delete-models": {
"title": "Eliminar modelos",
"desc": "¿Estás seguro de que quieres eliminar los {nb} modelos?",
"linked-annotation": "Estos modelos serán eliminados de todas las versiones vinculadas."
},
"download-models": {
"search-btn": "Buscar",
"search-placeholder": "Buscar un modelo",
"search-tips": {
"header": {
"tag": "Etiqueta",
"desc": "Descripción"
},
"author-desc": "Mostrar solo modelos del autor especificado.",
"hash-desc": "Mostrar solo modelos con el hash especificado.",
"tag-desc": "Mostrar solo modelos con la etiqueta especificada.",
"name-desc": "Mostrar solo modelos con el nombre especificado.",
"discordid-desc": "Mostrar solo modelos del usuario de discord especificado.",
"status-desc": "Mostrar solo modelos con el estado especificado. (solo perfil, y solo para el autor)"
},
"no-models": "No se encontraron modelos.",
"no-internet": "Sin conexión a Internet.",
"error-occured": "Ocurrió un error, inténtalo de nuevo más tarde."
},
"link-models": {
"avatar": {
"title": "Vincular avatares",
@@ -679,79 +819,16 @@
"valid-btn": "Desvincular bloqs"
}
}
}
},
"maps": {
"map-filter-panel": {
"duration": "Duración",
"tags": "tags",
"specificities": "general",
"requirements": "requisitos"
},
"map-types": {
"accuracy": "precisión",
"balanced": "equilibrado",
"challenge": "desafío",
"dancestyle": "baile",
"fitness": "fitness",
"speed": "speed",
"tech": "tec"
},
"map-styles": {
"dance": "baile",
"swing": "swing",
"nightcore": "nightcore",
"folk": "folk",
"family": "familia",
"ambient": "ambiente",
"funk": "funk",
"jazz": "jazz",
"soul": "soul",
"speedcore": "speedcore",
"punk": "punk",
"rb": "r&b",
"holiday": "vacaciones",
"vocaloid": "vocaloid",
"jrock": "j-rock",
"trance": "trance",
"drumbass": "drum & bass",
"comedy": "comedia",
"instrumental": "instrumental",
"hardcore": "hardcore",
"kpop": "k-pop",
"indie": "indie",
"techno": "tecno",
"house": "house",
"game": "videojuego",
"film": "film",
"alt": "alternativa",
"dubstep": "dubstep",
"metal": "metal",
"anime": "anime",
"hiphop": "hiphop",
"jpop": "j-pop",
"rock": "rock",
"pop": "pop",
"electronic": "electrónico",
"classical-orchestral": "Clásico y orquestal"
},
"map-specificities": {
"automapper": "IA",
"ranked": "rankado",
"curated": "recomendado",
"verified": "verificado",
"fullSpread": "panel completo"
},
"difficulties": {
"Easy": "fácil",
"Normal": "normal",
"Hard": "difícil",
"Expert": "experto",
"ExpertPlus": "experto+"
},
"map-item": {
"by": "Por {songAutor}",
"mapped-by": "mapeado por"
"notifications": {
"prevent-for-mods": {
"title": "Mods necesarios",
"desc": "Asegúrate de haber instalado los mods necesarios para usar modelos en BeatSaber",
"go-to-mods": "Ir a mods"
},
"export-success": {
"title": "Exportación completada 🎉"
}
}
},
"beat-saver": {
+153 -74
View File
@@ -12,7 +12,9 @@
"delete": "Supprimer",
"accept": "Accepter",
"refuse": "Refuser",
"apply": "Appliquer"
"apply": "Appliquer",
"copy": "Copier",
"copied": "Copié !"
},
"nav-bar":{
"add-version": "Ajouter une version",
@@ -597,8 +599,148 @@
"unlink-folder": "Délier le dossier",
"link-all": "Tout lier"
}
}
},
"maps": {
"map-filter-panel": {
"duration": "Durée",
"tags": "tags",
"specificities": "général",
"requirements": "requis"
},
"models": {
"map-types": {
"accuracy": "précision",
"balanced": "équilibrée",
"challenge": "challenge",
"dancestyle": "dance",
"fitness": "fitness",
"speed": "vitesse",
"tech": "tech"
},
"map-styles": {
"dance": "dance",
"swing": "swing",
"nightcore": "nightcore",
"folk": "folk",
"family": "famille",
"ambient": "ambiante",
"funk": "funk",
"jazz": "jazz",
"soul": "soul",
"speedcore": "speedcore",
"punk": "punk",
"rb": "r&b",
"holiday": "vacance",
"vocaloid": "vocaloid",
"jrock": "j-rock",
"trance": "trance",
"drumbass": "drum & bass",
"comedy": "comédie",
"instrumental": "instrumental",
"hardcore": "hardcore",
"kpop": "k-pop",
"indie": "indé",
"techno": "techno",
"house": "house",
"game": "jeu vidéo",
"film": "film",
"alt": "alternative",
"dubstep": "dubstep",
"metal": "metal",
"anime": "anime",
"hiphop": "hiphop",
"jpop": "j-pop",
"rock": "rock",
"pop": "pop",
"electronic": "éléctronique",
"classical-orchestral": "Classique & Orchestral"
},
"map-specificities": {
"automapper": "IA",
"ranked": "classée",
"curated": "recommandée",
"verified": "vérifiée",
"fullSpread": "panel complet"
},
"difficulties": {
"Easy": "facile",
"Normal": "normal",
"Hard": "difficile",
"Expert": "expert",
"ExpertPlus": "expert+"
},
"map-item": {
"by": "Par {songAutor}",
"mapped-by": "mappée par"
}
},
"models":{
"models": "Modèles",
"types": {
"singular": {
"avatar": "avatar",
"saber": "sabre",
"platform": "plateforme",
"bloq": "bloc"
},
"plural": {
"avatar": "avatars",
"saber": "sabres",
"platform": "plateformes",
"bloq": "blocs"
}
},
"sorts": {
"name": "nom",
"date": "date",
"author": "auteur"
},
"panel": {
"actions": {
"search": "Rechercher un modèle",
"drop-down": {
"delete": "Supprimer les modèles",
"export": "Exporter les modèles"
},
"link-models": "Lier les modèles",
"unlink-models": "Délier les modèles"
},
"grid":{
"loading": "Chargement des modèles...",
"no-models": "Aucun modèle",
"download-models": "Télécharger des modèles"
}
},
"modals": {
"delete-model" :{
"title": "Supprimer le modèle",
"desc": "Est-tu sûr de vouloir supprimer le modèle {modelName} ?",
"linked-annotation": "Ce modèle sera supprimé de toutes les versions liées."
},
"delete-models": {
"title": "Supprimer les modèles",
"desc": "Est-tu sûr de vouloir supprimer les {nb} modèles ?",
"linked-annotation": "Ces modèles seront supprimés de toutes les versions liées."
},
"download-models": {
"search-btn": "Rechercher",
"search-placeholder": "Rechercher un modèle",
"search-tips": {
"header": {
"tag": "Tag",
"desc": "Description"
},
"author-desc": "Afficher seulement les modèles de l'auteur spécifié.",
"hash-desc": "Afficher seulement les modèles avec le hash spécifié.",
"tag-desc": "Afficher seulement les modèles avec le tag spécifié.",
"name-desc": "Afficher seulement les modèles avec le nom spécifié.",
"discordid-desc": "Afficher seulement les modèles de l'utilisateur discord spécifié.",
"status-desc": "Afficher seulement les modèles avec le status spécifié. (profil seulement, et seulement pour l'auteur)"
},
"no-models": "Aucun modèles trouvés.",
"no-internet": "Pas de connexion internet.",
"error-occured": "Une erreur est survenue, réessaye plus tard."
},
"link-models":{
"avatar": {
"title": "Lier les avatars",
@@ -679,79 +821,16 @@
"valid-btn": "Délier les bloqs"
}
}
}
},
"maps": {
"map-filter-panel": {
"duration": "Durée",
"tags": "tags",
"specificities": "général",
"requirements": "requis"
},
"map-types": {
"accuracy": "précision",
"balanced": "équilibrée",
"challenge": "challenge",
"dancestyle": "dance",
"fitness": "fitness",
"speed": "vitesse",
"tech": "tech"
},
"map-styles": {
"dance": "dance",
"swing": "swing",
"nightcore": "nightcore",
"folk": "folk",
"family": "famille",
"ambient": "ambiante",
"funk": "funk",
"jazz": "jazz",
"soul": "soul",
"speedcore": "speedcore",
"punk": "punk",
"rb": "r&b",
"holiday": "vacance",
"vocaloid": "vocaloid",
"jrock": "j-rock",
"trance": "trance",
"drumbass": "drum & bass",
"comedy": "comédie",
"instrumental": "instrumental",
"hardcore": "hardcore",
"kpop": "k-pop",
"indie": "indé",
"techno": "techno",
"house": "house",
"game": "jeu vidéo",
"film": "film",
"alt": "alternative",
"dubstep": "dubstep",
"metal": "metal",
"anime": "anime",
"hiphop": "hiphop",
"jpop": "j-pop",
"rock": "rock",
"pop": "pop",
"electronic": "éléctronique",
"classical-orchestral": "Classique & Orchestral"
},
"map-specificities": {
"automapper": "IA",
"ranked": "classée",
"curated": "recommandée",
"verified": "vérifiée",
"fullSpread": "panel complet"
},
"difficulties": {
"Easy": "facile",
"Normal": "normal",
"Hard": "difficile",
"Expert": "expert",
"ExpertPlus": "expert+"
},
"map-item": {
"by": "Par {songAutor}",
"mapped-by": "mappée par"
"notifications": {
"prevent-for-mods": {
"title": "Mods nécessaires",
"desc": "Assurez-toi d'avoir installé les mods nécessaires pour utiliser des modèles dans BeatSaber",
"go-to-mods": "Aller aux mods"
},
"export-success": {
"title": "Export terminé 🎉"
}
}
},
"beat-saver": {
@@ -2,6 +2,7 @@ import { motion } from "framer-motion"
import { forwardRef } from "react"
import { useThemeColor } from "renderer/hooks/use-theme-color.hook"
import { BsmIcon } from "../svgs/bsm-icon.component"
import { useTranslation } from "renderer/hooks/use-translation.hook"
type Props = {
className?: string,
@@ -13,6 +14,7 @@ type Props = {
export const LinkButton = motion(forwardRef((props: Props, ref) => {
const t = useTranslation()
const color = useThemeColor("first-color");
const linkedColor = (() => {
@@ -22,7 +24,7 @@ export const LinkButton = motion(forwardRef((props: Props, ref) => {
})()
return (
<div ref={ref} className={props.className} title={props.title} onClick={e => {e.preventDefault(); !props.disabled && props.onClick?.()}} style={{pointerEvents: props.disabled ? "none" : "auto"}}>
<div ref={ref} className={props.className} title={props.title ? t(props.title) : undefined} onClick={e => {e.preventDefault(); !props.disabled && props.onClick?.()}} style={{pointerEvents: props.disabled ? "none" : "auto"}}>
<span className="absolute top-0 left-0 h-full w-full rounded-full brightness-50 opacity-75 dark:opacity-20 dark:filter-none" style={{backgroundColor: linkedColor}}/>
<BsmIcon className="p-1 absolute top-0 left-0 h-full w-full !bg-transparent -rotate-45 brightness-150" icon={props.linked ? "link" : "unlink"} style={{color: linkedColor}} />
</div>
@@ -120,7 +120,7 @@ export function MapsPlaylistsPanel({version}: Props) {
initial={{rotate: 0}}
className="block p-0.5 h-[calc(100%-5px)] aspect-square blur-0 hover:brightness-75"
linked={mapsLinked}
title={t(mapsLinked ? "pages.version-viewer.maps.tabs.maps.actions.link-maps.tooltips.unlink" : "pages.version-viewer.maps.tabs.maps.actions.link-maps.tooltips.link")}
title={mapsLinked ? "pages.version-viewer.maps.tabs.maps.actions.link-maps.tooltips.unlink" : "pages.version-viewer.maps.tabs.maps.actions.link-maps.tooltips.link"}
onClick={() => onClickLink(index)}
/>
)}
@@ -23,13 +23,16 @@ export const DeleteModelsModal: ModalComponent<void, {models: BsmLocalModel[], l
const isMultiple = data.models.length > 1;
const title = useConstant(() => isMultiple ? t("models.modals.delete-models.title") : t("models.modals.delete-model.title"));
const desc = useConstant(() => isMultiple ? t("models.modals.delete-models.desc", {nb: `${data.models.length}`}) : t("models.modals.delete-model.desc", {modelName: data.models[0].model?.name ?? data.models[0].fileName}));
const linkedAnnotation = useConstant(() => data.linked ? (isMultiple ? t("models.modals.delete-models.linked-annotation") : t("models.modals.delete-model.linked-annotation")) : undefined);
return (
// TODO TRANSLATIONS
<form className="text-gray-800 dark:text-gray-200">
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{isMultiple ? "Supprimer les modèles" : "Supprimer le modèle"}</h1>
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{title}</h1>
<BsmImage className="mx-auto h-24" image={BeatConflict}/>
<p className="max-w-sm w-full">{isMultiple ? `Es-tu sur de vouloir supprimer les ${data.models.length} modèles ?` : `Es-tu sur de vouloir supprimer le modèle ${data.models[0].model?.name ?? data.models[0].fileName}`}</p>
{data.linked && <p className="text-sm italic mt-2 cursor-help w-fit" title="TODO TITLE">{isMultiple ? "Ces modèles seront égalements supprimées des versions utilisant les maps partagées" : "Ce modèle sera également supprimée des versions utilisant les maps partagées"}</p>}
<p className="max-w-sm w-full">{desc}</p>
{data.linked && <p className="text-sm italic mt-2 w-fit">{linkedAnnotation}</p>}
{!isMultiple && (
<div className="flex items-center relative py-2 gap-1 mt-1">
<BsmCheckbox className="h-5 relative z-[1]" checked={remember} onChange={(val) => setRemember(val)}/>
@@ -29,17 +29,18 @@ export const DownloadModelsModal: ModalComponent<void, {version: BSVersion, type
const modelSaber = useService(ModelSaberService);
const os = useService(OsDiagnosticService);
const t = useTranslation();
const modelTypesOptions = useConstant<BsmSelectOption<MSModelType>[]>(() => MODEL_TYPES.map(type => ({
text: type.toString(), // TODO TRANSLATE
text: `models.types.plural.${type}`,
value: type
})));
const querySortsOptions = useConstant<BsmSelectOption<MSGetSort>[]>(() => MS_GET_QUERY_SORTS.map(sort => ({
text: sort.toString(), // TODO TRANSLATE
text: `models.sorts.${sort}`,
value: sort
})));
const t = useTranslation();
const currentDownload = useObservable(modelsDownloader.currentDownload$(), null);
const downloadQueue = useObservable(modelsDownloader.getQueue$(), []);
const [msModels, msModels$] = useBehaviorSubject<MSModel[]>([]);
@@ -127,43 +128,50 @@ export const DownloadModelsModal: ModalComponent<void, {version: BSVersion, type
return !!ownedModels.some(owned => owned.hash === model.hash);
}
const filterTipsHTML = useConstant(() => (
const renderFilterTips = useConstant(() => (
<div className="w-fit flex">
<table className="w-fit whitespace-nowrap grow shrink-0 text-sm">
<tbody>
<tr className="font-bold"><td className="mr-3 block">Tag</td><td>Description</td></tr>
<tr><td className="mr-3 block">author:</td><td>Only show models by the specified author.</td></tr>
<tr><td className="mr-3 block">hash:</td><td>Only show models with the specified hash.</td></tr>
<tr><td className="mr-3 block">tag:</td><td>Only show models with the specified tag.</td></tr>
<tr><td className="mr-3 block">name:</td><td>Only show models with the specified name.</td></tr>
<tr><td className="mr-3 block">discordid:</td><td>Only show models by the user with the specified discordid.</td></tr>
<tr><td className="mr-3 block">status:</td><td>Only show models with the specified approval status. (profile only, and only for the author)</td></tr>
<tr className="font-bold"><td className="mr-3 block">{t("models.modals.download-models.search-tips.header.tag")}</td><td>{t("models.modals.download-models.search-tips.header.desc")}</td></tr>
<tr><td className="mr-3 block">author:</td><td>{t("models.modals.download-models.search-tips.author-desc")}</td></tr>
<tr><td className="mr-3 block">tag:</td><td>{t("models.modals.download-models.search-tips.tag-desc")}</td></tr>
<tr><td className="mr-3 block">hash:</td><td>{t("models.modals.download-models.search-tips.hash-desc")}</td></tr>
<tr><td className="mr-3 block">name:</td><td>{t("models.modals.download-models.search-tips.name-desc")}</td></tr>
<tr><td className="mr-3 block">discordid:</td><td>{t("models.modals.download-models.search-tips.discordid-desc")}</td></tr>
<tr><td className="mr-3 block">status:</td><td>{t("models.modals.download-models.search-tips.status-desc")}</td></tr>
</tbody>
</table>
</div>
))
));
const renderLoadingStatus = () => {
if(isLoading) { return <span className="text-lg">{t("models.panel.grid.loading")}</span> }
if(isOnline) { return <span className="text-lg">{t("models.modals.download-models.no-models")}</span> }
if(!isOnline) { return <span className="text-lg">{t("models.modals.download-models.no-internet")}</span> }
return <span className="text-lg">{t("models.modals.download-models.error-occured")}</span>
}
return (
<form className="text-gray-800 dark:text-gray-200 flex flex-col max-w-[95vw] w-[970px] h-[85vh] gap-3" onSubmit={e => {e.preventDefault(); search()}}>
<div className="flex h-9 gap-2 shrink-0">
<BsmSelect className="bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-1 pb-0.5 text-center" options={modelTypesOptions} selected={currentType} onChange={(value) => currentType$.next(value)}/>
<BsmSelect className="bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-1 pb-0.5 text-center capitalize" options={modelTypesOptions} selected={currentType} onChange={(value) => currentType$.next(value)}/>
<div className="h-ful grow relative flex justify-center items-center">
<input className="h-full w-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2 pb-0.5" type="text" name="" id="" placeholder="TODO TRANSLATE Rechercher un modèle" value={searhInput} onChange={e => searhInput$.next(e.target.value)}/>
<Tippy placement="bottom" content={filterTipsHTML} allowHTML={true} maxWidth={Infinity}>
<input className="h-full w-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2 pb-0.5" type="text" name="" id="" placeholder={t("models.modals.download-models.search-placeholder")} value={searhInput} onChange={e => searhInput$.next(e.target.value)}/>
<Tippy placement="bottom" content={renderFilterTips} allowHTML={true} maxWidth={Infinity}>
<div className="absolute right-0 h-full w-fit p-1 cursor-pointer">
<BsmButton className="h-full rounded-full p-1 aspect-square" typeColor="primary" icon="info" withBar={false}/>
</div>
</Tippy>
</div>
<BsmButton className="shrink-0 rounded-full py-1 px-3 !bg-light-main-color-1 dark:!bg-main-color-1 flex justify-center items-center capitalize" icon="search" type="submit" text="modals.download-maps.search-btn" withBar={false} onClick={e => {e.preventDefault(); search()}}/>
<BsmSelect className="bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-1 pb-0.5 text-center" options={querySortsOptions} onChange={(value) => currentSort$.next(value)}/>
<BsmButton className="shrink-0 rounded-full py-1 px-3 !bg-light-main-color-1 dark:!bg-main-color-1 flex justify-center items-center capitalize" icon="search" type="submit" text="models.modals.download-models.search-btn" withBar={false} onClick={e => {e.preventDefault(); search()}}/>
<BsmSelect className="bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-1 pb-0.5 text-center capitalize" options={querySortsOptions} onChange={(value) => currentSort$.next(value)}/>
</div>
<ul className="w-full grow flex content-start flex-wrap gap-4 pt-1.5 px-2 overflow-y-scroll overflow-x-hidden scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900 z-0" >
{msModels.length === 0 ? (
<div className="w-full h-full flex flex-col items-center justify-center">
<img className={`w-32 h-32 ${isLoading && "spin-loading"}`} src={isLoading ? BeatWaitingImg : BeatConflictImg} alt=" "/>
<span className="text-lg">{t(isLoading ? "Chargement des modèles" : isOnline ? "Aucun modèles trouvés" : "Pas d'internet")}</span> {/** TODO TRANSLATE **/}
<img className={`w-32 h-32 ${isLoading && "spin-loading"}`} src={(isLoading && !error) ? BeatWaitingImg : BeatConflictImg} alt=" "/>
{renderLoadingStatus()}
</div>
) : (
<>
@@ -15,17 +15,17 @@ export const LinkModelsModal: ModalComponent<boolean, MSModelType> = ({resolver,
return (
<form className="text-gray-800 dark:text-gray-200">
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(`modals.models.link-models.${data}.title`)}</h1>
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(`models.modals.link-models.${data}.title`)}</h1>
<BsmImage className="mx-auto h-24" image={BeatRunning} />
<p className="max-w-sm w-full">{t(`modals.models.link-models.${data}.desc`)}</p>
<p className="max-w-sm w-full text-sm italic mt-2">{t(`modals.models.link-models.${data}.info`)}</p>
<p className="max-w-sm w-full">{t(`models.modals.link-models.${data}.desc`)}</p>
<p className="max-w-sm w-full text-sm italic mt-2">{t(`models.modals.link-models.${data}.info`)}</p>
<div className="relative h-5 flex my-3 items-center">
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1.5 z-[1]" checked={keepMaps} onChange={setKeepMaps}/>
<span className="text-sm mb-0.5 cursor-help" title={t(`modals.models.link-models.${data}.keep-models.title`)}>{t(`modals.models.link-models.${data}.keep-models.label`)}</span>
<span className="text-sm mb-0.5 cursor-help" title={t(`models.modals.link-models.${data}.keep-models.title`)}>{t(`models.modals.link-models.${data}.keep-models.label`)}</span>
</div>
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => resolver({exitCode: ModalExitCode.CANCELED})} withBar={false} text="misc.cancel"/>
<BsmButton typeColor="primary" className="rounded-md text-center transition-all" onClick={() => resolver({exitCode: ModalExitCode.COMPLETED, data: keepMaps})} withBar={false} text={`modals.models.link-models.${data}.valid-btn`}/>
<BsmButton typeColor="primary" className="rounded-md text-center transition-all" onClick={() => resolver({exitCode: ModalExitCode.COMPLETED, data: keepMaps})} withBar={false} text={`models.modals.link-models.${data}.valid-btn`}/>
</div>
</form>
)
@@ -14,16 +14,16 @@ export const UnlinkModelsModal: ModalComponent<boolean, MSModelType> = ({resolve
return (
<form className="text-gray-800 dark:text-gray-200">
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(`modals.models.unlink-models.${data}.title`)}</h1>
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(`models.modals.unlink-models.${data}.title`)}</h1>
<BsmImage className="mx-auto h-24" image={BeatConflict} />
<p className="max-w-sm w-full">{t(`modals.models.unlink-models.${data}.desc`)}</p>
<p className="max-w-sm w-full">{t(`models.modals.unlink-models.${data}.desc`)}</p>
<div className="relative h-5 flex my-3 items-center">
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1.5 z-[1]" checked={keepMaps} onChange={setKeepMaps}/>
<span className="text-sm mb-0.5 cursor-help" title={t(`modals.models.unlink-models.${data}.keep-models.title`)}>{t(`modals.models.unlink-models.${data}.keep-models.label`)}</span>
<span className="text-sm mb-0.5 cursor-help" title={t(`models.modals.unlink-models.${data}.keep-models.title`)}>{t(`models.modals.unlink-models.${data}.keep-models.label`)}</span>
</div>
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => resolver({exitCode: ModalExitCode.CANCELED})} withBar={false} text="misc.cancel"/>
<BsmButton typeColor="error" className="rounded-md text-center transition-all" onClick={() => resolver({exitCode: ModalExitCode.COMPLETED, data: keepMaps})} withBar={false} text={`modals.models.unlink-models.${data}.valid-btn`}/>
<BsmButton typeColor="error" className="rounded-md text-center transition-all" onClick={() => resolver({exitCode: ModalExitCode.COMPLETED, data: keepMaps})} withBar={false} text={`models.modals.unlink-models.${data}.valid-btn`}/>
</div>
</form>
)
@@ -15,6 +15,7 @@ import { BsmIconType } from "../svgs/bsm-icon.component";
import { BsmButton } from "../shared/bsm-button.component";
import { BsmBasicSpinner } from "../shared/bsm-basic-spinner/bsm-basic-spinner.component";
import { isValidUrl } from "shared/helpers/url.helpers";
import { useTranslation } from "renderer/hooks/use-translation.hook";
type Props<T> = {
selected?: boolean,
@@ -29,6 +30,7 @@ type Props<T> = {
function modelItem<T = unknown>(props: Props<T>) {
const t = useTranslation();
const color = useThemeColor("first-color");
const [hovered, setHovered] = useState(false);
const [idContentCopied, setIdContentCopied] = useState<string>(null);
@@ -59,19 +61,19 @@ function modelItem<T = unknown>(props: Props<T>) {
return [...new Set(props.tags)];
})();
const actionButtons = (): {text: string, icon: BsmIconType, action: () => void, iconColor?: string}[] => {
const buttons: {text: string, icon: BsmIconType, action: () => void, iconColor?: string}[] = [];
const actionButtons = (): {icon: BsmIconType, action: () => void, iconColor?: string}[] => {
const buttons: {icon: BsmIconType, action: () => void, iconColor?: string}[] = [];
if(props.onDownload && !props.onCancelDownload){
buttons.push({ text: "Download", icon: "download", action: () => props.onDownload(props.callbackValue) }); // TODO TRANSLATE
buttons.push({ icon: "download", action: () => props.onDownload(props.callbackValue) });
}
if(props.onDelete){
buttons.push({ text: "Delete", icon: "trash", action: () => props.onDelete(props.callbackValue) }); // TODO TRANSLATE
buttons.push({ icon: "trash", action: () => props.onDelete(props.callbackValue) });
}
if(props.onCancelDownload){
buttons.push({ text: "Cancel download", icon: "cross", action: () => props.onCancelDownload(props.callbackValue), iconColor: "red" }); // TODO TRANSLATE
buttons.push({ icon: "cross", action: () => props.onCancelDownload(props.callbackValue), iconColor: "red" });
}
return buttons;
@@ -86,7 +88,6 @@ function modelItem<T = unknown>(props: Props<T>) {
}
return (
// TODO TRANSLATE ALL TEXT
<motion.li className={`relative flex-grow min-w-[14rem] h-56 cursor-pointer ${props.className ?? ""}`} onHoverStart={() => setHovered(() => true)} onHoverEnd={() => setHovered(() => false)} onClick={props.onClick}>
<GlowEffect visible={props.selected || hovered}/>
<div className="absolute top-0 left-0 w-full h-full rounded-lg overflow-hidden blur-none bg-black shadow-sm shadow-black">
@@ -108,17 +109,17 @@ function modelItem<T = unknown>(props: Props<T>) {
<li key={tag} className="inline-block mr-1 text-xs bg-white text-black px-1 rounded-full p-0.5">{tag}</li>
))}
</ul>
<Tippy placement="top" content={idContentCopied === "hash" ? "Copied!" : "Copy hash"} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
<Tippy placement="top" content={idContentCopied === "hash" ? t("misc.copied") : t("misc.copy")} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
<span className="mt-0.5 cursor-copy block w-full max-w-fit overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-1 rounded-md p-1 uppercase text-xs" onClick={() => copyContent(`${props.hash}`, 'hash')}>{props.hash}</span>
</Tippy>
<div className="flex gap-1">
{props.id && (
<Tippy placement="top" content={idContentCopied === "id" ? "Copied!" : "Copy id"} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
<Tippy placement="top" content={idContentCopied === "id" ? t("misc.copied") : t("misc.copy")} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
<span className="cursor-copy w-fit shrink-0 max-w-full overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-1 rounded-md p-1 uppercase text-xs" onClick={() => copyContent(`${props.id}`, 'id')}>{props.id}</span>
</Tippy>
)}
{props.path && (
<Tippy placement="top" content={idContentCopied === "path" ? "Copied!" : "Copy path"} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
<Tippy placement="top" content={idContentCopied === "path" ? t("misc.copied") : t("misc.copy")} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
<span className="cursor-copy w-fit max-w-full overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-1 rounded-md p-1 text-xs" onClick={() => copyContent(`${props.path}`, 'path')}>{props.path}</span>
</Tippy>
)}
@@ -20,6 +20,7 @@ import { VersionLinkerAction } from "renderer/services/version-folder-linker.ser
import { MODEL_TYPE_FOLDERS } from "shared/models/models/constants";
import { useService } from "renderer/hooks/use-service.hook";
import { ModelsDownloaderService } from "renderer/services/models-management/models-downloader.service";
import { useTranslation } from "renderer/hooks/use-translation.hook";
type Props = {
className?: string,
@@ -34,6 +35,8 @@ export const ModelsGrid = forwardRef(({className, version, type, search, active}
const modelsManager = useService(ModelsManagerService);
const modelsDownloader = useService(ModelsDownloaderService);
const t = useTranslation();
const ref = useRef();
const [models, setModelsLoadObservable,, setModels] = useSwitchableObservable<BsmLocalModel[]>();
@@ -156,7 +159,7 @@ export const ModelsGrid = forwardRef(({className, version, type, search, active}
return (
<div className="h-full flex flex-col items-center justify-center flex-wrap gap-1 text-gray-800 dark:text-gray-200">
<BsmImage className="w-32 h-32 spin-loading" image={BeatWaitingImg}/>
<span className="font-bold">Chargement des models...</span> {/** TODO TRANSLATE **/}
<span className="font-bold">{t("models.panel.grid.loading")}</span>
<TextProgressBar value$={progress$}/>
</div>
)
@@ -166,8 +169,8 @@ export const ModelsGrid = forwardRef(({className, version, type, search, active}
return (
<div className="h-full flex flex-col items-center justify-center flex-wrap gap-1 text-gray-800 dark:text-gray-200">
<BsmImage className="h-32" image={BeatConflict}/>
<span className="font-bold">Aucun modelès</span> {/** TODO TRANSLATE **/}
<BsmButton className="font-bold rounded-md p-2" text="Télécharger des modèles" typeColor="primary" withBar={false} onClick={e => {e.preventDefault();}}/>
<span className="font-bold">{t("models.panel.grid.no-models")}</span>
<BsmButton className="font-bold rounded-md p-2" text="models.panel.grid.download-models" typeColor="primary" withBar={false} onClick={e => {e.preventDefault();}}/>
</div>
)
}
@@ -12,6 +12,7 @@ import { ModelsDownloaderService } from "renderer/services/models-management/mod
import { useOnUpdate } from "renderer/hooks/use-on-update.hook";
import { NotificationService } from "renderer/services/notification.service";
import { ConfigurationService } from "renderer/services/configuration.service";
import { useTranslation } from "renderer/hooks/use-translation.hook";
export function ModelsPanel({version, isActive, goToMods}: {version?: BSVersion, isActive: boolean, goToMods: () => void}) {
@@ -20,6 +21,8 @@ export function ModelsPanel({version, isActive, goToMods}: {version?: BSVersion,
const notification = useService(NotificationService);
const config = useService(ConfigurationService);
const t = useTranslation();
const ref = useRef();
const modelsGridRefs = [useRef(null), useRef(null), useRef(null), useRef(null)];
@@ -31,9 +34,9 @@ export function ModelsPanel({version, isActive, goToMods}: {version?: BSVersion,
useOnUpdate(() => {
if(!isActive){ return; }
//if(config.get("prevented-for-mods-models")){ return; }
if(config.get("prevented-for-mods-models")){ return; }
config.set("prevented-for-mods-models", true);
notification.notifyWarning({ title: "Mods needed", desc: "Be sure to have the mods installed to use models in BeatSaber", actions: [{id: "0", title: "Go to mods"}], duration: 9_000 }).then(res => {
notification.notifyWarning({ title: "models.notifications.prevent-for-mods.title", desc: "models.notifications.prevent-for-mods.desc", actions: [{id: "0", title: "models.notifications.prevent-for-mods.go-to-mods"}], duration: 9_000 }).then(res => {
if(res !== "0"){ return; }
goToMods();
});
@@ -55,8 +58,8 @@ export function ModelsPanel({version, isActive, goToMods}: {version?: BSVersion,
}
const threeDotsItems: DropDownItem[] = [
{text: "TODO TRANSLATE EXPORT", onClick: exportModels, icon: "export"},
{text: "TODO TRANSLATE DELETE", onClick: deleteModels, icon: "trash"},
{text: "models.panel.actions.drop-down.export", onClick: exportModels, icon: "export"},
{text: "models.panel.actions.drop-down.delete", onClick: deleteModels, icon: "trash"},
];
return (
@@ -64,7 +67,7 @@ export function ModelsPanel({version, isActive, goToMods}: {version?: BSVersion,
<div className="w-full shrink-0 flex h-9 justify-center px-40 gap-2 mb-3 text-main-color-1 dark:text-white">
<BsmButton className="flex items-center justify-center w-fit rounded-full px-2 py-1 font-bold" icon="add" text="misc.add" typeColor="primary" withBar={false} onClick={e => {e.preventDefault(); openDownloadModal()}}/>
<div className="h-full rounded-full bg-light-main-color-2 dark:bg-main-color-2 grow p-[6px]">
<input type="text" className="h-full w-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2" placeholder={"TODO TRANSLATE"} onChange={e => setSearch(e.target.value)} tabIndex={-1}/>
<input type="text" className="h-full w-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2" placeholder={t("models.panel.actions.search")} onChange={e => setSearch(e.target.value)} tabIndex={-1}/>
</div>
<BsmDropdownButton items={threeDotsItems} className="h-full flex aspect-square relative rounded-full z-[1] bg-light-main-color-1 dark:bg-main-color-3" buttonClassName="rounded-full h-full w-full p-[6px]" icon="three-dots" withBar={false} menuTranslationY="6px" align="center"/>
</div>
@@ -8,6 +8,8 @@ import { BsContentNavBar, BsContentNavBarTab } from "../shared/bs-content-nav-ba
import { useConstant } from "renderer/hooks/use-constant.hook"
import { BsmIcon } from "../svgs/bsm-icon.component"
import { MODEL_TYPES } from "../../../shared/models/models/constants";
import { useService } from "renderer/hooks/use-service.hook"
import { useTranslation } from "renderer/hooks/use-translation.hook"
type Props = {
className?: string,
@@ -20,7 +22,7 @@ export function ModelsTabsNavbar({className, version, tabIndex, onTabChange}: Pr
const tabs = useConstant<BsContentNavBarTab<MSModelType>[]>(() => {
return MODEL_TYPES.map(type => ({
text: type, // <= TODO: Translate
text: type,
extra: type
}))
})
@@ -40,7 +42,9 @@ type TabProps = {
function ModelTab({version, modelType, active, onClick}: TabProps){
const modelsManager = useConstant(() => ModelsManagerService.getInstance());
const modelsManager = useService(ModelsManagerService);
const t = useTranslation();
const [modelsAreLinked, setModelsAreLinked] = useState(false);
const [linkBtnDisabled, setLinkBtnDisabled] = useState(false);
@@ -73,7 +77,7 @@ function ModelTab({version, modelType, active, onClick}: TabProps){
<li className={`relative w-full cursor-pointer flex-1 text-center text-lg font-bold flex justify-center items-center content-center px-7 dim-on-hover ${active ? "dim" : ""}`} onClick={onClick}>
<div className="flex flex-col gap-0.5 justify-start items-center text-main-color-1 dark:text-gray-200">
<BsmIcon icon={modelType} className="w-7 h-7"/>
<span className=" font-thin italic text-xs">{modelType}</span>
<span className=" font-thin italic text-xs">{t(`models.types.plural.${modelType}`)}</span>
</div>
<div className="flex items-center absolute top-1.5 left-1.5">
{!!version && (
@@ -85,7 +89,7 @@ function ModelTab({version, modelType, active, onClick}: TabProps){
initial={{rotate: 0}}
className="block w-6 h-6 aspect-square blur-0 cursor-pointer hover:brightness-75"
linked={modelsAreLinked}
title={"aaa"} //TODO ADD TRANSLATION
title={modelsAreLinked ? "models.panel.actions.unlink-models" : "models.panel.actions.link-models"}
onClick={onClickLink}
/>
)}
@@ -100,7 +100,7 @@ export class ModelsDownloaderService {
}
public openDownloadModelsModal(version: BSVersion, type?: MSModelType, owned?: BsmLocalModel[]): Promise<ModalResponse<void>>{
return this.modal.openModal(DownloadModelsModal, {version, type, owned}); // TODO MODELS
return this.modal.openModal(DownloadModelsModal, {version, type, owned});
}
}
@@ -118,12 +118,11 @@ export class ModelsManagerService {
this.progressBar.show(exportProgress$, true);
lastValueFrom(exportProgress$).catch(e => {
this.notifications.notifyError({title: "notifications.types.error"});
}).then(() => {
// TODO TRANSLATE
this.notifications.notifySuccess({title: "TODO TRANSLATE SUCCESS", duration: 3000});
this.progressBar.complete();
lastValueFrom(exportProgress$).then(() => {
this.notifications.notifySuccess({title: "models.notifications.export-success.title", duration: 3000});
}).catch(e => {
this.notifications.notifyError({title: "notifications.types.error", desc: "notifications.common.msg.error-occurred", duration: 3000});
}).finally(() => {
this.progressBar.hide(true);
})
@@ -158,8 +157,11 @@ export class ModelsManagerService {
if(showProgressBar){ this.progressBar.show(progress$); }
return lastValueFrom(progress$)
.catch(() => false)
.then(() => true)
.catch(() => {
this.notifications.notifyError({title: "notifications.types.error", desc: "notifications.common.msg.error-occurred", duration: 3000});
return false;
})
.finally(() => this.progressBar.hide(true));
}