From 3203177d158615ba472d667f53becd7738f48334 Mon Sep 17 00:00:00 2001 From: Hactazia <113508403+hactazia@users.noreply.github.com> Date: Thu, 9 Jan 2025 00:44:15 +0100 Subject: [PATCH] Update fr localization, add localization for table filters and Improve biography change highlighting (#1064) * chore: add latest localization * chore: add locations for table filters * chore: Improve biography change highlighting * fix: displays highlights correctly. * fix: remove debug --- html/src/app.js | 278 +++++++++++------- html/src/app.scss | 15 + html/src/localization/en/en.json | 78 ++++- html/src/localization/fr/en.json | 390 +++++++++++++++++++++---- html/src/mixins/tabs/feed.pug | 9 +- html/src/mixins/tabs/friendLog.pug | 4 +- html/src/mixins/tabs/gameLog.pug | 6 +- html/src/mixins/tabs/moderation.pug | 4 +- html/src/mixins/tabs/notifications.pug | 10 +- 9 files changed, 606 insertions(+), 188 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index 6ba3b0df..c2281a98 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -192,8 +192,8 @@ speechSynthesis.getVoices(); this.loginForm.savedCredentials = (await configRepository.getString('savedCredentials')) !== null ? JSON.parse( - await configRepository.getString('savedCredentials') - ) + await configRepository.getString('savedCredentials') + ) : {}; this.loginForm.lastUserLoggedIn = await configRepository.getString('lastUserLoggedIn'); @@ -202,11 +202,11 @@ speechSynthesis.getVoices(); if ( !this.enablePrimaryPassword && (await configRepository.getString('lastUserLoggedIn')) !== - null + null ) { var user = this.loginForm.savedCredentials[ - this.loginForm.lastUserLoggedIn + this.loginForm.lastUserLoggedIn ]; if (user?.loginParmas?.endpoint) { API.endpointDomain = user.loginParmas.endpoint; @@ -2026,7 +2026,7 @@ speechSynthesis.getVoices(); if (object === Object(object)) { details = object; } - } catch (err) {} + } catch (err) { } } ref.details = details; } @@ -3835,7 +3835,7 @@ speechSynthesis.getVoices(); if (this.loginForm.lastUserLoggedIn) { var user = this.loginForm.savedCredentials[ - this.loginForm.lastUserLoggedIn + this.loginForm.lastUserLoggedIn ]; if (typeof user !== 'undefined') { await webApiService.clearCookies(); @@ -4737,7 +4737,7 @@ speechSynthesis.getVoices(); }); worldName = args.ref.name; } - } catch (err) {} + } catch (err) { } } return worldName; }; @@ -4760,7 +4760,7 @@ speechSynthesis.getVoices(); groupId }); groupName = args.ref.name; - } catch (err) {} + } catch (err) { } return groupName; }; @@ -5647,7 +5647,7 @@ speechSynthesis.getVoices(); props.currentAvatarThumbnailImageUrl[0] && props.currentAvatarThumbnailImageUrl[1] && props.currentAvatarThumbnailImageUrl[0] === - props.currentAvatarThumbnailImageUrl[1] + props.currentAvatarThumbnailImageUrl[1] ) { imageMatches = true; } @@ -5706,7 +5706,7 @@ speechSynthesis.getVoices(); avatarInfo = await $app.getAvatarName( currentAvatarImageUrl ); - } catch (err) {} + } catch (err) { } var previousAvatarInfo = { ownerId: '', avatarName: '' @@ -5715,7 +5715,7 @@ speechSynthesis.getVoices(); previousAvatarInfo = await $app.getAvatarName( previousCurrentAvatarImageUrl ); - } catch (err) {} + } catch (err) { } var feed = { created_at: new Date().toJSON(), type: 'Avatar', @@ -5824,67 +5824,52 @@ speechSynthesis.getVoices(); }; /** - * Function that find the differences between both strings, and return the differences and their position in the strings. - * @param {*} s1 String 1 - * @param {*} s2 String 2 - * @returns + * Function to find the longest common subsequence between two strings + * @param {string} str1 + * @param {string} str2 + * @returns {number[][]} A matrix that contains the longest common subsequence between both strings */ - $app.methods.findDifferences = function (s1, s2) { - const dp = $app.lcsMatrix(s1, s2); - const differencesS1 = []; - const differencesS2 = []; - let i = s1.length; - let j = s2.length; - - // Backtrack to find differences - while (i > 0 && j > 0) { - if (s1[i - 1] === s2[j - 1]) { - i--; - j--; - } else if (dp[i - 1][j] >= dp[i][j - 1]) { - differencesS1.push({ index: i - 1, char: s1[i - 1] }); // Deletion in s1 - i--; - } else { - differencesS2.push({ index: j - 1, char: s2[j - 1] }); // Insertion in s2 - j--; + $app.methods.longestCommonSubsequence = function longestCommonSubsequence(str1, str2) { + let lcs = []; + for (let i = 0; i <= str1.length; i++) { + lcs.push(new Array(str2.length + 1).fill(0)); + } + for (let i = str1.length - 1; i >= 0; i--) { + for (let j = str2.length - 1; j >= 0; j--) { + if (str1[i] == str2[j]) { + lcs[i][j] = lcs[i + 1][j + 1] + 1; + } else { + lcs[i][j] = Math.max(lcs[i + 1][j], lcs[i][j + 1]); + } } } + return lcs; + } - // Remaining characters in s1 (deletions) - while (i > 0) { - differencesS1.push({ index: i - 1, char: s1[i - 1] }); - i--; + /** + * Merge differences in both strings to get the longest common subsequence + * @param {{text: string, type: "add" | "remove" | "same"}[]} res + * @returns {{text: string, type: "add" | "remove" | "same"}[]} An array that contains the differences between both strings + */ + $app.methods.regoupDifferences = function regoupDifferences(res) { + let regrouped = []; + let text = ""; + let type = ""; + for (let i = 0; i < res.length; i++) { + if (i == 0) { + text = res[i].text; + type = res[i].type; + } else if (res[i].type == type) { + text += res[i].text; + } else { + regrouped.push({ text: text, type: type }); + text = res[i].text; + type = res[i].type; + } } - - // Remaining characters in s2 (insertions) - while (j > 0) { - differencesS2.push({ index: j - 1, char: s2[j - 1] }); - j--; - } - - return { - differencesS1: differencesS1.reverse(), // Reverse to maintain original order - differencesS2: differencesS2.reverse() - }; - }; - - $app.methods.findSequences = function (arr) { - if (arr.length === 0) return []; - return arr.reduce( - (p, c, i) => { - if (i === 0) return p; - let lastSeq = p.pop(); - p.push(lastSeq); - if (c - lastSeq[1] !== 1) { - p.push([c, c]); - } else { - lastSeq[1] = c; - } - return p; - }, - [[arr[0], arr[0]]] - ); - }; + regrouped.push({ text: text, type: type }); + return regrouped; + } /** * Function that format the differences between two strings with HTML tags @@ -5895,40 +5880,118 @@ speechSynthesis.getVoices(); * @param {*} markerEndTag * @returns An array that contains both the string 1 and string 2, which the differences are formatted with HTML tags */ - $app.methods.formatDifference = function ( - s1, - s2, - markerStartTag = '', - markerEndTag = '' + $app.methods.formatDifference = function getWordDifferences( + oldString, + newString, + markerAddition = '{{text}}', + markerDeletion = '{{text}}' ) { - const texts = [s1, s2]; - const differs = $app.findDifferences(s1, s2); - return Object.values(differs) - .map((i) => $app.findSequences(i.map((j) => j.index))) - .map((i, k) => { - let stringBuilder = []; - let lastPos = 0; - let key = Date.now(); - i.forEach((j) => { - stringBuilder.push(texts[k].substring(lastPos, j[0])); - stringBuilder.push( - `{{diffTag-${key}}}${texts[k].substring(j[0], j[1] + 1)}{{diffTagClose-${key}}}` - ); - lastPos = j[1] + 1; - }); - stringBuilder.push(texts[k].substr(lastPos, texts[k].length)); - let returnVal = stringBuilder - .join('') - .replaceAll(/&/g, '&') - .replaceAll(//g, '>') - .replaceAll(/"/g, '"') - .replaceAll(/'/g, ''') - .replaceAll(`{{diffTag-${key}}}`, markerStartTag) - .replaceAll(`{{diffTagClose-${key}}}`, markerEndTag); - return returnVal; - }); - }; + [oldString, newString] = [oldString, newString].map((s) => s + .replaceAll(/&/g, '&') + .replaceAll(//g, '>') + .replaceAll(/"/g, '"') + .replaceAll(/'/g, ''') + .replaceAll(/\n/g, '
') + ); + + const oldWords = oldString.split(/\s+/).flatMap((word) => word.split(/(
)/)); + const newWords = newString.split(/\s+/).flatMap((word) => word.split(/(
)/)); + + function findLongestMatch(oldStart, oldEnd, newStart, newEnd) { + let bestOldStart = oldStart; + let bestNewStart = newStart; + let bestSize = 0; + + const lookup = new Map(); + for (let i = oldStart; i < oldEnd; i++) { + const word = oldWords[i]; + if (!lookup.has(word)) lookup.set(word, []); + lookup.get(word).push(i); + } + + for (let j = newStart; j < newEnd; j++) { + const word = newWords[j]; + if (!lookup.has(word)) continue; + + for (const i of lookup.get(word)) { + let size = 0; + while ( + i + size < oldEnd && + j + size < newEnd && + oldWords[i + size] === newWords[j + size] + ) { + size++; + } + if (size > bestSize) { + bestOldStart = i; + bestNewStart = j; + bestSize = size; + } + } + } + + return { oldStart: bestOldStart, newStart: bestNewStart, size: bestSize }; + } + + function buildDiff(oldStart, oldEnd, newStart, newEnd) { + const result = []; + const match = findLongestMatch(oldStart, oldEnd, newStart, newEnd); + + if (match.size > 0) { + // Handle differences before the match + if (oldStart < match.oldStart || newStart < match.newStart) { + result.push( + ...buildDiff(oldStart, match.oldStart, newStart, match.newStart) + ); + } + + // Add the matched words + result.push(oldWords.slice(match.oldStart, match.oldStart + match.size).join(" ")); + + // Handle differences after the match + if (match.oldStart + match.size < oldEnd || match.newStart + match.size < newEnd) { + result.push( + ...buildDiff( + match.oldStart + match.size, + oldEnd, + match.newStart + match.size, + newEnd + ) + ); + } + } else { + function build(words, start, end, pattern) { + let r = []; + let ts = words.slice(start, end) + .filter((w) => w.length > 0) + .join(" ") + .split("
"); + for (let i = 0; i < ts.length; i++) { + if (i > 0) r.push("
"); + if (ts[i].length < 1) continue; + r.push(pattern.replace("{{text}}", ts[i])); + } + return r; + } + + // Add deletions + if (oldStart < oldEnd) + result.push(...build(oldWords, oldStart, oldEnd, markerDeletion)); + + // Add insertions + if (newStart < newEnd) + result.push(...build(newWords, newStart, newEnd, markerAddition)); + } + + return result; + } + + return buildDiff(0, oldWords.length, 0, newWords.length) + .join(" ") + .replace(/
[ ]+
/g, "

") + .replace(/
/g, "
"); + } // #endregion // #region | App: gameLog @@ -6178,8 +6241,8 @@ speechSynthesis.getVoices(); $app.methods.formatSeconds = function (duration) { var pad = function (num, size) { - return `000${num}`.slice(size * -1); - }, + return `000${num}`.slice(size * -1); + }, time = parseFloat(duration).toFixed(3), hours = Math.floor(time / 60 / 60), minutes = Math.floor(time / 60) % 60, @@ -8348,7 +8411,7 @@ speechSynthesis.getVoices(); speechSynthesis.cancel(); if ( (await configRepository.getString('VRCX_notificationTTS')) === - 'Never' && + 'Never' && this.notificationTTS !== 'Never' ) { this.speak('Notification text-to-speech enabled'); @@ -10617,9 +10680,8 @@ speechSynthesis.getVoices(); if (type === 'search') { try { var response = await webApiService.execute({ - url: `${ - this.avatarRemoteDatabaseProvider - }?${type}=${encodeURIComponent(search)}&n=5000`, + url: `${this.avatarRemoteDatabaseProvider + }?${type}=${encodeURIComponent(search)}&n=5000`, method: 'GET', headers: { Referer: 'https://vrcx.app' @@ -17472,7 +17534,7 @@ speechSynthesis.getVoices(); try { var args = await API.getFavoriteWorlds(params); worldLists.push([list.displayName, list.visibility, args.json]); - } catch (err) {} + } catch (err) { } } this.userFavoriteWorlds = worldLists; this.userDialog.isFavoriteWorldsLoading = false; @@ -17644,7 +17706,7 @@ speechSynthesis.getVoices(); $app.methods.sortCurrentUserGroups = async function () { var D = this.userDialog; - var sortMethod = function () {}; + var sortMethod = function () { }; switch (D.groupSorting.value) { case 'alphabetical': diff --git a/html/src/app.scss b/html/src/app.scss index 9d214a50..cfae3c9d 100644 --- a/html/src/app.scss +++ b/html/src/app.scss @@ -937,3 +937,18 @@ i.x-status-icon.red { .el-collapse-item .el-tag--mini { line-height: 17px; } + +.x-text-removed { + text-decoration: line-through; + color: #ff0000; + background-color: rgba(255, 0, 0, 0.2); + padding: 2px 2px; + border-radius: 4px; +} + +.x-text-added { + color: rgb(76, 255, 80); + background-color: rgba(76, 255, 80, 0.2); + padding: 2px 2px; + border-radius: 4px; +} \ No newline at end of file diff --git a/html/src/localization/en/en.json b/html/src/localization/en/en.json index 5dd18f5d..f0e7fc12 100644 --- a/html/src/localization/en/en.json +++ b/html/src/localization/en/en.json @@ -34,11 +34,30 @@ "feed": { "favorites_only_tooltip": "Filter favorites only", "filter_placeholder": "Filter", - "search_placeholder": "Search" + "search_placeholder": "Search", + "filters": { + "GPS": "GPS", + "Online": "Online", + "Offline": "Offline", + "Status": "Status", + "Avatar": "Avatar", + "Bio": "Bio" + } }, "game_log": { "filter_placeholder": "Filter", - "search_placeholder": "Search" + "search_placeholder": "Search", + "filters": { + "Location": "Location", + "OnPlayerJoined": "Player joined", + "OnPlayerLeft": "Player left", + "PortalSpawn": "Portal spawn", + "VideoPlay": "Video play", + "Event": "Event", + "External": "External", + "StringLoad": "String load", + "ImageLoad": "Image load" + } }, "player_list": { "photon": { @@ -125,17 +144,64 @@ }, "friend_log": { "filter_placeholder": "Filter", - "search_placeholder": "Search" + "search_placeholder": "Search", + "filters": { + "Friend": "New friend", + "Unfriend": "Unfriend", + "FriendRequest": "Friend request", + "CancelFriendRequest": "Cancel friend request", + "DisplayName": "Display name", + "TrustLevel": "Trust level" + } }, "moderation": { "filter_placeholder": "Filter", "search_placeholder": "Search", - "refresh_tooltip": "Refresh" + "refresh_tooltip": "Refresh", + "filters": { + "block": "Block", + "unblock": "Unblock", + "mute": "Mute", + "unmute": "Unmute", + "interactOn": "Interact On", + "interactOff": "Interact Off", + "muteChat": "Mute chat" + } }, "notification": { "filter_placeholder": "Filter", "search_placeholder": "Search", - "refresh_tooltip": "Refresh" + "refresh_tooltip": "Refresh", + "filters": { + "requestInvite": "Request Invite", + "invite": "Invite", + "requestInviteResponse": "Request Invite Response", + "inviteResponse": "Invite Response", + "friendRequest": "Friend Request", + "ignoredFriendRequest": "Ignored Friend Request", + "message": "Message", + "boop": "Boop", + "groupChange": "Group Change", + "group": { + "announcement": "Announcement", + "informative": "Informative", + "invite": "Invite", + "joinRequest": "Join Request", + "transfer": "Transfer", + "queueReady": "Queue Ready" + }, + "moderation": { + "warning": { + "group": "Moderation Warning Group" + }, + "report": { + "closed": "Moderation Report Closed" + } + }, + "instance": { + "closed": "Instance Closed" + } + } }, "friend_list": { "header": "Friend List", @@ -1942,4 +2008,4 @@ } } } -} +} \ No newline at end of file diff --git a/html/src/localization/fr/en.json b/html/src/localization/fr/en.json index 88023724..8322e264 100644 --- a/html/src/localization/fr/en.json +++ b/html/src/localization/fr/en.json @@ -21,7 +21,7 @@ "register": "Inscription", "forgotPassword": "Mot de passe oublié ?", "updater": "Updater", - "proxy_settings": "Proxy settings", + "proxy_settings": "Paramètres du proxy", "field": { "username": "Nom d'utilisateur ou Email", "password": "Mot de passe", @@ -34,11 +34,30 @@ "feed": { "favorites_only_tooltip": "Filtrer les favoris seulement", "filter_placeholder": "Filtrer", - "search_placeholder": "Rechercher" + "search_placeholder": "Rechercher", + "filters": { + "GPS": "GPS", + "Online": "En ligne", + "Offline": "Hors ligne", + "Status": "Statut", + "Avatar": "Avatar", + "Bio": "Bio" + } }, "game_log": { "filter_placeholder": "Filtrer", - "search_placeholder": "Rechercher" + "search_placeholder": "Rechercher", + "filters": { + "Location": "Localisation", + "Event": "Événement", + "StringLoad": "Texte téléchargé", + "ImageLoad": "Image téléchargée", + "OnPlayerJoined": "Rejoint", + "OnPlayerLeft": "Quitté", + "PortalSpawn": "Portail", + "VideoPlay": "Vidéo", + "External": "Externe" + } }, "player_list": { "photon": { @@ -90,17 +109,19 @@ "search": "Rechercher", "vrchat_favorites": "Favoris VRChat", "local_favorites": "Favoris locaux", - "new_group": "Nouveau groupe" + "new_group": "Nouveau groupe", + "refresh": "Réactualiser", + "cancel_refresh": "Annuler la réactualisation" }, "avatars": { "header": "Avatars", "search": "Rechercher", "vrchat_favorites": "Favoris VRChat", - "local_favorites": "Local Favorites (Requires VRC+)", - "new_group": "Nouveau groupe" + "local_favorites": "Favoris locaux (Requires VRC+)", + "new_group": "Nouveau groupe", + "refresh": "Réactualiser", + "cancel_refresh": "Annuler la réactualisation" }, - "bulk_unfavorite_mode": "Mode groupée de suppression des favoris", - "bulk_unfavorite_selection": "Sélection groupée des favoris", "refresh_tooltip": "Actualiser les favoris", "export": "Exporter", "import": "Importer", @@ -113,21 +134,74 @@ "unavailable_tooltip": "Indisponible", "private": "Privé", "sort_by": "Trier par", - "select_avatar_tooltip": "Sélectionner un avatar" + "select_avatar_tooltip": "Sélectionner un avatar", + "edit_mode": "Mode édition", + "copy": "Copier", + "clear": "Vider", + "bulk_unfavorite": "Mode suppression des favoris", + "copy_tooltip": "Copier", + "self_invite_tooltip": "S'inviter" }, "friend_log": { "filter_placeholder": "Filtrer", - "search_placeholder": "Rechercher" + "search_placeholder": "Rechercher", + "filters": { + "Friend": "Nouvel ami", + "Unfriend": "Ami supprimé", + "FriendRequest": "Demande d'ami envoyée", + "CancelFriendRequest": "Demande d'ami annulée", + "DisplayName": "Nom d'affichage", + "TrustLevel": "Niveau de confiance" + } }, "moderation": { "filter_placeholder": "Filtrer", "search_placeholder": "Rechercher", - "refresh_tooltip": "Actualiser" + "refresh_tooltip": "Actualiser", + "filters": { + "block": "Bloqué", + "unblock": "Débloqué", + "mute": "Sourdine", + "unmute": "Son rétabli", + "interactOn": "Interaction activée", + "interactOff": "Interaction désactivée", + "muteChat": "Boîtes de dialogue désactivées" + } }, "notification": { "filter_placeholder": "Filtrer", "search_placeholder": "Rechercher", - "refresh_tooltip": "Actualiser" + "refresh_tooltip": "Actualiser", + "filters": { + "requestInvite": "Demande d'invitation", + "invite": "Invitation", + "requestInviteResponse": "Réponse à une demande d'invitation", + "inviteResponse": "Réponse à une invitation", + "friendRequest": "Demande d'ami", + "ignoredFriendRequest": "Demande d'ami ignorée", + "message": "Message", + "boop": "Boop", + "groupChange": "Changement de groupe", + "group": { + "announcement": "Annonce de groupe", + "informative": "Publication de groupe", + "invite": "Invitation de groupe", + "joinRequest": "Demande de rejoindre le groupe", + "transfer": "Transfert de groupe", + "queueReady": "File d'attente prête" + }, + "moderation": { + "warning": { + "group": "Avertissement de modération de groupe" + }, + "report": { + "closed": "Rapport de modération fermé" + } + }, + "instance": { + "closed": "Instance fermée" + } + } }, "friend_list": { "header": "Liste des amis", @@ -207,18 +281,22 @@ "vrcx_updater": { "header": "Mise à jour de VRCX", "change_build": "Changer de build", - "auto_update": "Mise à jour automatique", "auto_update_off": "Désactivé", "auto_update_notify": "Me demander", "auto_update_download": "Téléchargement automatique", - "change_log": "Journal des modifications" + "change_log": "Journal des modifications", + "update_action": "Action de mise à jour" }, "application": { "header": "Application", "startup": "Lancer au démarrage de Windows", "minimized": "Lancer en arrière plan", "tray": "Minimiser dans la barre des tâches", - "proxy": "Proxy settings" + "proxy": "Proxy settings", + "disable_gpu_acceleration": "Désactiver l'accélération GPU", + "disable_gpu_acceleration_tooltip": "Ne changez cette option que si vous savez ce que vous faites, cette fonctionnalité casse la surcouche SteamVR, peut corriger des problèmes avec l'interface utilisateur, nécessite le redémarrage de VRCX.", + "disable_vr_overlay_gpu_acceleration": "Désactiver l'accélération GPU de l'overlay VR", + "disable_vr_overlay_gpu_acceleration_tooltip": "L'accélération GPU de l'overlay VR peut faire planter VRCX ou provoquer des fuites de mémoire VRAM, nécessite de redémarrer VRCX." }, "favorites": { "header": "Amis favoris", @@ -231,19 +309,20 @@ }, "automation": { "header": "Automatisation", - "auto_state_change": "Changement de statut automatique", "auto_state_change_tooltip": "Changer automatiquement de statut quand il y a d'autres personnes dans l'instance (Seul / Compagnie)", - "auto_state_change_off": "Désactivé", - "auto_state_change_active_or_ask_me": "Actif / Demandez-moi", - "auto_state_change_active_or_busy": "Actif / Occupé", - "auto_state_change_join_me_or_ask_me": "Rejoignez-moi / Demandez-moi", - "auto_state_change_join_me_or_busy": "Rejoignez-moi / Ne pas déranger", - "auto_state_change_ask_me_or_busy": "Demandez-moi / Occupé", "auto_invite_request_accept": "Accepter automatiquement les demandes d'invitation", "auto_invite_request_accept_tooltip": "Accepter automatiquement les demandes d'invitation des amis favoris", "auto_invite_request_accept_off": "Désactivé", "auto_invite_request_accept_favs": "Tous les favoris", - "auto_invite_request_accept_selected_favs": "Favoris sélectionnés" + "auto_invite_request_accept_selected_favs": "Favoris sélectionnés", + "auto_change_status": "Changer automatiquement de statut", + "alone_condition": "Condition de statut seul", + "alone": "Seul", + "no_friends": "Pas d'amis", + "alone_status": "Statut seul", + "company_status": "Statut en compagnie", + "allowed_instance_types": "Types d'instances autorisés", + "instance_type_placeholder": "Choisir des types d'instances" }, "legal_notice": { "header": "Mentions légales", @@ -292,23 +371,16 @@ "header": "Panneau latéral", "sorting": { "header": "Comportement du tri", - "sort_default": "Default", - "sort_private_to_bottom": "Joueurs en privé en fin de liste", - "sort_by_status": "Trier par statut", - "sort_by_status_and_private_to_bottom": "Sort by Status and Private to Bottom", - "sort_by_location": "Sort by Location", - "sort_favorite_by": "Trier les favoris par", - "sort_favorite_by_alphabet": "alphabétique", - "sort_favorite_by_online_time": "en ligne depuis", - "sort_online_by": "Trier les en ligne par", - "sort_online_by_alphabet": "alphabétique", - "sort_online_by_online_time": "en ligne depuis", - "sort_active_by": "Trier les actifs par", - "sort_active_by_alphabet": "alphabétique", - "sort_active_by_online_time": "en ligne depuis", - "sort_offline_by": "Trier les hors ligne par", - "sort_offline_by_alphabet": "alphabétique", - "sort_offline_by_offline_time": "hors ligne depuis" + "alphabetical": "Alphabétique", + "private_to_bottom": "Privé en bas", + "status": "Statut", + "status_and_private_to_bottom": "Statut et privé en bas", + "location": "Localisation", + "last_active": "Dernière activité", + "last_seen": "Vu la dernière fois", + "time_in_instance": "Temps dans l'instance", + "placeholder": "Type de tri", + "dropdown_header": "Choisi un type de tri" }, "width": "Largeur" }, @@ -348,7 +420,9 @@ "desktop": "Mode bureau", "inside_vr": "Durant la VR", "outside_vr": "En dehors de la VR", - "always": "En permanence" + "always": "En permanence", + "inside_vrchat": "Dans VRChat", + "outside_vrchat": "En dehors de VRChat" }, "desktop_notifications": { "header": "Notifications sur le bureau", @@ -358,7 +432,10 @@ "text_to_speech": { "header": "Text-To-Speech", "when_to_play": "Quand la jouer", - "tts_voice": "Voix de la synthèse vocale" + "tts_voice": "Voix de la synthèse vocale", + "use_memo_nicknames": "Utiliser les surnoms dans les mémos", + "play": "Jouer", + "tts_test_placeholder": "Cliquez pour tester" } } }, @@ -419,10 +496,7 @@ "header": "Correction de l'arrêt de VRChat", "description": "Arrêter VRChat après avoir quitté le jeu" }, - "vrchat_osc_fix": { - "header": "VRChat OSC Fix", - "description": "Resuming and terminating the suspended install.exe process after exiting game" - }, + "vrchat_osc_fix": {}, "auto_cache_management": { "header": "Gérer automatiquement le cache lors de la fermeture de VRChat", "description": "Suppression automatique des anciennes versions du cache" @@ -497,6 +571,22 @@ "portal_spawn": "Portail d'accès :", "video_play": "Lecture de vidéo :", "event": "Événement :" + }, + "save_instance_prints_to_file": { + "header": "Enregistrement des Impressions d'instance dans un fichier", + "header_tooltip": "Nécessite l'option de lancement du VRChat « --enable-sdk-log-levels ».", + "description": "Enregistre les impressions générées dans votre dossier VRChat Pictures" + }, + "save_instance_stickers_to_file": { + "header": "Enregistrer les autocollants d'instance dans un fichier", + "description": "Enregistrer les autocollants placés dans votre dossier VRChat Pictures" + }, + "user_generated_content": { + "header": "Contenu généré par l'utilisateur", + "folder": "Ouvrir le dossier", + "description": "Ouvrez ou définissez le dossier dans lequel sont stockés les contenus tels que « Impressions » et « Autocollants ».", + "set_folder": "Définir un dossier", + "reset_override": "Réinitialiser" } }, "photon": { @@ -590,7 +680,10 @@ "edit_pronouns": "Pronoms", "report_hacking": "Signaler un piratage", "unfriend": "Supprimer des amis", - "logout": "Se déconnecter" + "logout": "Se déconnecter", + "share": "Partager", + "moderation_enable_chatbox": "Rétablir les boites de dialogue", + "moderation_disable_chatbox": "Désactiver les boites de dialogue" }, "info": { "header": "Infos", @@ -636,14 +729,25 @@ "instance_full": "Complète", "instance_closed": "Instance fermée", "instance_hard_closed": "Instance fermée définitivement", - "close_instance": "Fermer l'instance" + "close_instance": "Fermer l'instance", + "instance_age_gated": "Limitation d'âge" }, "groups": { "header": "Groupes", "total_count": "{count} au total", "own_groups": "Propres groupes", "mutual_groups": "Groupes mutuels", - "groups": "Groupes" + "groups": "Groupes", + "sort_by": "Trier par", + "edit_mode": "Mode édition", + "exit_edit_mode": "Quitter le mode édition", + "hold_shift": "Maintenez Maj enfoncée pour sélectionner plusieurs groupes", + "sorting": { + "alphabetical": "Alphabétique", + "members": "Membres", + "in_game": "En jeu" + }, + "leave_group_tooltip": "Quitter le groupe" }, "worlds": { "header": "Mondes", @@ -676,6 +780,11 @@ }, "json": { "header": "JSON" + }, + "badges": { + "assigned": "Attribués", + "hidden": "Cachés", + "showcased": "Mise en avant" } }, "world": { @@ -690,7 +799,9 @@ "content_gore": "Gore", "content_violence": "Violence", "content_adult": "Adulte", - "content_sex": "Sexuel" + "content_sex": "Sexuel", + "focus_view_disabled": "Vue focale désactivée", + "stickers_disabled": "Autocollants désactivés" }, "actions": { "delete_cache_tooltip": "Supprimer le monde du cache", @@ -711,7 +822,11 @@ "download_package": "Télécharger le package Unity", "publish_to_labs": "Publier comme Labs", "unpublish": "Dépublier", - "delete": "Supprimer" + "delete": "Supprimer", + "share": "Partager", + "new_instance_and_self_invite": "Créer une nouvelle instance et s'inviter", + "change_allowed_video_player_domains": "Changer les domaines de lecteur vidéo autorisés", + "delete_persistent_data": "Supprimer les données persistantes" }, "instances": { "header": "Instances", @@ -780,7 +895,7 @@ "favorite_tooltip": "Favoris", "unfavorite_tooltip": "Supprimer des favoris", "refresh": "Actualiser", - "select": "Sélectionner un avatar", + "select": "Sélectionner cet avatar", "select_fallback": "Sélectionner l'avatar fallback", "block": "Bloquer l'avatar", "unblock": "Débloquer l'avatar", @@ -850,7 +965,8 @@ "moderation_tools": "Outils de modération", "leave": "Quitter le groupe", "block": "Bloquer le groupe", - "unblock": "Débloquer le groupe" + "unblock": "Débloquer le groupe", + "share": "Partager" }, "info": { "header": "Infos", @@ -990,7 +1106,8 @@ "normal": "Normale", "group": "Groupe", "legacy": "Legacy", - "roles": "Rôles" + "roles": "Rôles", + "ageGate": "Limitation d'âge" }, "launch_options": { "header": "Options de démarrage de VRChat", @@ -1022,7 +1139,8 @@ "disable_discord_presence": "Désactiver la présence Discord", "vrchat_docs": "Documentation de VRChat", "cancel": "Annuler", - "save": "Sauvegarder" + "save": "Sauvegarder", + "spout_resolution": "Résolution Spout" }, "youtube_api": { "header": "API YouTube", @@ -1043,7 +1161,9 @@ "content_adult": "Adulte", "content_sex": "Sexuel", "cancel": "Annuler", - "save": "Sauvegarder" + "save": "Sauvegarder", + "focus_view_disabled": "Désactiver la vue focale", + "stickers_disabled": "Désactiver les autocollants" }, "set_avatar_tags": { "header": "Modifier les tags de l'avatar", @@ -1243,7 +1363,6 @@ }, "gallery_icons": { "header": "Galerie et icônes", - "description": "Recommended image size: 1200x900px (4:3)", "gallery": "Galerie", "icons": "Icônes", "emojis": "Émojis", @@ -1255,7 +1374,11 @@ "emoji_animation_fps": "FPS", "emoji_animation_frame_count": "Nombre de frames", "emoji_loop_pingpong": "Animé en boucle", - "flipbook_info": "Sélectionnez une image PNG 1024x1024 pour l'utiliser comme emoji animé : 4, 16 ou 64 (64 FPS max, 64 frames max)." + "flipbook_info": "Sélectionnez une image PNG 1024x1024 pour l'utiliser comme emoji animé : 4, 16 ou 64 (64 FPS max, 64 frames max).", + "recommended_image_size": "Taille d'image recommandée", + "stickers": "Autocollants", + "prints": "Impressions", + "note": "Notes" }, "change_content_image": { "avatar": "Changer l'image de l'avatar", @@ -1388,6 +1511,11 @@ "default_emojis": "Emojis par défaut", "cancel": "Annuler", "send": "Envoyer" + }, + "allowed_video_player_domains": { + "header": "Domaines autorisés pour le lecteur vidéo", + "add_domain": "Ajouter un domaine", + "save": "Sauvegarder" } }, "prompt": { @@ -1736,5 +1864,149 @@ "cpu": "CPU :", "online": "En ligne :" } + }, + "confirm": { + "title": "Confirmation", + "confirm_button": "Confirmer", + "cancel_button": "Annuler", + "message": "Confirmer {command} ?" + }, + "message": { + "vrcx_updater": { + "failed": "Échec de la vérification de la mise à jour, {message}" + }, + "api_handler": { + "avatar_private_or_deleted": "Avatar privé ou supprimé" + }, + "badge": { + "updated": "Badge mis à jour" + }, + "instance": { + "closed": "Instance fermée", + "removed_form_queue": "Suppression de l'instance {worldName} de la file d'attente", + "not_allowed": "Vous n'êtes pas autorisé à rejoindre cette instance" + }, + "avatar": { + "change_moderation_failed": "Échec de la modération de l'avatar", + "image_changed": "Image de l'avatar changée", + "image_invalid": "Image de l'avatar invalide" + }, + "emoji": { + "uploaded": "Emoji envoyé" + }, + "file": { + "not_image": "Le fichier n'est pas une image", + "too_large": "Le fichier est trop volumineux" + }, + "print": { + "uploaded": "Impression envoyée" + }, + "sticker": { + "uploaded": "Autocollant envoyé" + }, + "gallery": { + "uploaded": "Image de la galerie envoyée", + "failed": "Échec de l'envoi de l'image de la galerie" + }, + "world": { + "image_changed": "Image du monde changée", + "image_invalid": "Image du monde invalide" + }, + "icon": { + "uploaded": "Icône envoyée" + }, + "user": { + "moderated": "Utilisateur modéré" + }, + "friend": { + "load_failed": "Échec du chargement de la liste d'amis, déconnexion..." + } + }, + "api": { + "status_code": { + "100": "Continuer", + "101": "Changement de protocole", + "102": "Traitement", + "103": "Premiers indices", + "200": "OK", + "201": "Créé", + "202": "Accepté", + "203": "Information non autorisée", + "204": "Pas de contenu", + "205": "Réinitialiser le contenu", + "206": "Contenu partiel", + "207": "Multi-état", + "208": "Déjà signalé", + "226": "IM utilisé", + "300": "Choix multiples", + "301": "Déplacé de façon permanente", + "302": "Trouvé", + "303": "Voir autre", + "304": "Non modifié", + "305": "Utiliser un proxy", + "306": "Changer de proxy", + "307": "Redirection temporaire", + "308": "Redirection permanente", + "400": "Mauvaise requête", + "401": "Non autorisé", + "402": "Paiement requis", + "403": "Interdit", + "404": "Introuvable", + "405": "Méthode non autorisée", + "406": "Non acceptable", + "407": "Authentification proxy requise", + "408": "Temps de requête écoulé", + "409": "Conflit", + "410": "Parti", + "411": "Longueur requise", + "412": "Condition préalable échouée", + "413": "Charge utile trop grande", + "414": "URI trop longue", + "415": "Type de média non pris en charge", + "416": "Plage non satisfaisable", + "417": "Attente échouée", + "418": "Je suis une théière", + "421": "Requête mal dirigée", + "422": "Entité non traitable", + "423": "Verrouillé", + "424": "Dépendance échouée", + "425": "Trop tôt", + "426": "Mise à niveau requise", + "428": "Condition préalable requise", + "429": "Trop de requêtes", + "431": "Champs d'en-tête de requête trop grands", + "451": "Indisponible pour des raisons légales", + "500": "Erreur interne du serveur", + "501": "Non implémenté", + "502": "Mauvaise passerelle", + "503": "Service indisponible", + "504": "Temps d'attente de la passerelle écoulé", + "505": "Version HTTP non prise en charge", + "506": "La variante négocie également", + "507": "Espace de stockage insuffisant", + "508": "Boucle détectée", + "510": "Non étendu", + "511": "Authentification réseau requise", + "520": "Le serveur web retourne une erreur inconnue", + "521": "Le serveur web est en panne", + "522": "Temps de connexion écoulé", + "523": "Origine inaccessible", + "524": "Un délai d'attente s'est produit", + "525": "Échec de l'établissement de la liaison SSL", + "526": "Certificat SSL invalide", + "527": "Erreur du Railgun Listener à l'origine" + }, + "error": { + "message": { + "error_message": "Message d'erreur", + "endpoint": "Endpoint", + "missing_credentials": "Credentials manquantes", + "avatar_private_or_deleted": "Avatar privé ou supprimé", + "vpn_in_use": "VRChat bloque actuellement la plupart des VPN. Veuillez désactiver tout VPN connecté et réessayer.", + "login_error": "Erreur de connexion", + "invalid_json_response": "Réponse JSON invalide", + "403_404_bailing_request": "En attente d'une autorisation dû à un récent 404/403" + } + } } -} +} \ No newline at end of file diff --git a/html/src/mixins/tabs/feed.pug b/html/src/mixins/tabs/feed.pug index c35f5c1d..6848093e 100644 --- a/html/src/mixins/tabs/feed.pug +++ b/html/src/mixins/tabs/feed.pug @@ -7,7 +7,7 @@ mixin feedTab() el-tooltip(placement="bottom" :content="$t('view.feed.favorites_only_tooltip')" :disabled="hideTooltips") el-switch(v-model="feedTable.vip" @change="feedTableLookup" active-color="#13ce66") el-select(v-model="feedTable.filter" @change="feedTableLookup" multiple clearable style="flex:1;height:40px;" :placeholder="$t('view.feed.filter_placeholder')") - el-option(v-once v-for="type in ['GPS', 'Online', 'Offline', 'Status', 'Avatar', 'Bio']" :key="type" :label="type" :value="type") + el-option(v-once v-for="type in ['GPS', 'Online', 'Offline', 'Status', 'Avatar', 'Bio']" :key="type" :label="$t('view.feed.filters.' + type)" :value="type") el-input(v-model="feedTable.search" :placeholder="$t('view.feed.search_placeholder')" @keyup.native.13="feedTableLookup" @change="feedTableLookup" clearable style="flex:none;width:150px;margin:0 10px") el-table-column(type="expand" width="20") template(v-once #default="scope") @@ -65,10 +65,7 @@ mixin feedTab() i.x-user-status(:class="statusClass(scope.row.status)" style="margin:0 5px") span(v-text="scope.row.statusDescription") template(v-else-if="scope.row.type === 'Bio'") - template(v-for="(bio,idx) in formatDifference(scope.row.previousBio, scope.row.bio)") - pre(v-html="bio" style="font-family:inherit;font-size:12px;white-space:pre-wrap;margin:0 0.5em 0 0") - span(v-if="idx === 0") - i.el-icon-right + pre(v-html="formatDifference(scope.row.previousBio, scope.row.bio)" style="font-family:inherit;font-size:12px;white-space:pre-wrap;line-height:25px;line-height: 22px;") el-table-column(:label="$t('table.feed.date')" prop="created_at" sortable="custom" width="120") template(v-once #default="scope") el-tooltip(placement="right") @@ -76,6 +73,8 @@ mixin feedTab() span {{ scope.row.created_at | formatDate('long') }} span {{ scope.row.created_at | formatDate('short') }} el-table-column(:label="$t('table.feed.type')" prop="type" width="70") + template(v-once #default="scope") + span.x-link(v-text="$t('view.feed.filters.' + scope.row.type)") el-table-column(:label="$t('table.feed.user')" prop="displayName" width="180") template(v-once #default="scope") span.x-link(v-text="scope.row.displayName" @click="showUserDialog(scope.row.userId)" style="padding-right:10px") diff --git a/html/src/mixins/tabs/friendLog.pug b/html/src/mixins/tabs/friendLog.pug index 6444f308..f8a0fc9e 100644 --- a/html/src/mixins/tabs/friendLog.pug +++ b/html/src/mixins/tabs/friendLog.pug @@ -4,12 +4,14 @@ mixin friendLogTab() template(#tool) div(style="margin:0 0 10px;display:flex;align-items:center") el-select(v-model="friendLogTable.filters[0].value" @change="saveTableFilters" multiple clearable style="flex:1" :placeholder="$t('view.friend_log.filter_placeholder')") - el-option(v-once v-for="type in ['Friend', 'Unfriend', 'FriendRequest', 'CancelFriendRequest', 'DisplayName', 'TrustLevel']" :key="type" :label="type" :value="type") + el-option(v-once v-for="type in ['Friend', 'Unfriend', 'FriendRequest', 'CancelFriendRequest', 'DisplayName', 'TrustLevel']" :key="type" :label="$t('view.friend_log.filters.' + type)" :value="type") el-input(v-model="friendLogTable.filters[1].value" :placeholder="$t('view.friend_log.search_placeholder')" style="flex:none;width:150px;margin-left:10px") el-table-column(:label="$t('table.friendLog.date')" prop="created_at" sortable="custom" width="200") template(v-once #default="scope") span {{ scope.row.created_at | formatDate('long') }} el-table-column(:label="$t('table.friendLog.type')" prop="type" width="150") + template(v-once #default="scope") + span(v-text="$t('view.friend_log.filters.' + scope.row.type)") el-table-column(:label="$t('table.friendLog.user')" prop="displayName") template(v-once #default="scope") span(v-if="scope.row.type === 'DisplayName'") {{ scope.row.previousDisplayName }} #[i.el-icon-right]  diff --git a/html/src/mixins/tabs/gameLog.pug b/html/src/mixins/tabs/gameLog.pug index 11f43e33..c77ce36c 100644 --- a/html/src/mixins/tabs/gameLog.pug +++ b/html/src/mixins/tabs/gameLog.pug @@ -7,7 +7,7 @@ mixin gameLogTab() el-tooltip(placement="bottom" :content="$t('view.feed.favorites_only_tooltip')" :disabled="hideTooltips") el-switch(v-model="gameLogTable.vip" @change="gameLogTableLookup" active-color="#13ce66") el-select(v-model="gameLogTable.filter" @change="gameLogTableLookup" multiple clearable style="flex:1" :placeholder="$t('view.game_log.filter_placeholder')") - el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'PortalSpawn', 'VideoPlay', 'Event', 'External', 'StringLoad', 'ImageLoad']" :key="type" :label="type" :value="type") + el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'PortalSpawn', 'VideoPlay', 'Event', 'External', 'StringLoad', 'ImageLoad']" :key="type" :label="$t('view.game_log.filters.' + type)" :value="type") el-input(v-model="gameLogTable.search" :placeholder="$t('view.game_log.search_placeholder')" @keyup.native.13="gameLogTableLookup" @change="gameLogTableLookup" clearable style="flex:none;width:150px;margin:0 10px") el-table-column(:label="$t('table.gameLog.date')" prop="created_at" sortable="custom" width="120") template(v-once #default="scope") @@ -17,8 +17,8 @@ mixin gameLogTab() span {{ scope.row.created_at | formatDate('short') }} el-table-column(:label="$t('table.gameLog.type')" prop="type" width="120") template(v-once #default="scope") - span.x-link(v-if="scope.row.location && scope.row.type !== 'Location'" v-text="scope.row.type" @click="showWorldDialog(scope.row.location)") - span(v-else v-text="scope.row.type") + span.x-link(v-if="scope.row.location && scope.row.type !== 'Location'" v-text="$t('view.game_log.filters.' + scope.row.type)" @click="showWorldDialog(scope.row.location)") + span(v-else v-text="$t('view.game_log.filters.' + scope.row.type)") el-table-column(:label="$t('table.gameLog.icon')" prop="isFriend" width="70") template(v-once #default="scope") template(v-if="gameLogIsFriend(scope.row)") diff --git a/html/src/mixins/tabs/moderation.pug b/html/src/mixins/tabs/moderation.pug index 6850448e..eeab9dbe 100644 --- a/html/src/mixins/tabs/moderation.pug +++ b/html/src/mixins/tabs/moderation.pug @@ -4,7 +4,7 @@ mixin moderationTab() template(#tool) div(style="margin:0 0 10px;display:flex;align-items:center") el-select(v-model="playerModerationTable.filters[0].value" @change="saveTableFilters" multiple clearable style="flex:1" :placeholder="$t('view.moderation.filter_placeholder')") - el-option(v-once v-for="type in ['block', 'unblock', 'mute', 'unmute', 'interactOn', 'interactOff', 'muteChat']" :key="type" :label="type" :value="type") + el-option(v-once v-for="type in ['block', 'unblock', 'mute', 'unmute', 'interactOn', 'interactOff', 'muteChat']" :key="type" :label="$t('view.moderation.filters.' + type)" :value="type") el-input(v-model="playerModerationTable.filters[1].value" :placeholder="$t('view.moderation.search_placeholder')" style="flex:none;width:150px;margin:0 10px") el-tooltip(placement="bottom" :content="$t('view.moderation.refresh_tooltip')" :disabled="hideTooltips") el-button(type="default" :loading="API.isPlayerModerationsLoading" @click="API.refreshPlayerModerations()" icon="el-icon-refresh" circle style="flex:none") @@ -15,6 +15,8 @@ mixin moderationTab() span {{ scope.row.created | formatDate('long') }} span {{ scope.row.created | formatDate('short') }} el-table-column(:label="$t('table.moderation.type')" prop="type" width="100") + template(v-once #default="scope") + span(v-text="$t('view.moderation.filters.' + scope.row.type)") el-table-column(:label="$t('table.moderation.source')" prop="sourceDisplayName") template(v-once #default="scope") span.x-link(v-text="scope.row.sourceDisplayName" @click="showUserDialog(scope.row.sourceUserId)") diff --git a/html/src/mixins/tabs/notifications.pug b/html/src/mixins/tabs/notifications.pug index 11b31111..3564ab15 100644 --- a/html/src/mixins/tabs/notifications.pug +++ b/html/src/mixins/tabs/notifications.pug @@ -4,7 +4,7 @@ mixin notificationsTab() template(#tool) div(style="margin:0 0 10px;display:flex;align-items:center") el-select(v-model="notificationTable.filters[0].value" @change="saveTableFilters" multiple clearable style="flex:1" :placeholder="$t('view.notification.filter_placeholder')") - el-option(v-once v-for="type in ['requestInvite', 'invite', 'requestInviteResponse', 'inviteResponse', 'friendRequest', 'ignoredFriendRequest', 'message', 'boop', 'groupChange', 'group.announcement', 'group.informative', 'group.invite', 'group.joinRequest', 'group.transfer', 'group.queueReady', 'moderation.warning.group', 'moderation.report.closed', 'instance.closed']" :key="type" :label="type" :value="type") + el-option(v-once v-for="type in ['requestInvite', 'invite', 'requestInviteResponse', 'inviteResponse', 'friendRequest', 'ignoredFriendRequest', 'message', 'boop', 'groupChange', 'group.announcement', 'group.informative', 'group.invite', 'group.joinRequest', 'group.transfer', 'group.queueReady', 'moderation.warning.group', 'moderation.report.closed', 'instance.closed']" :key="type" :label="$t('view.notification.filters.' + type)" :value="type") el-input(v-model="notificationTable.filters[1].value" :placeholder="$t('view.notification.search_placeholder')" style="flex:none;width:150px;margin:0 10px") el-tooltip(placement="bottom" :content="$t('view.notification.refresh_tooltip')" :disabled="hideTooltips") el-button(type="default" :loading="API.isNotificationsLoading" @click="API.refreshNotifications()" icon="el-icon-refresh" circle style="flex:none") @@ -19,14 +19,14 @@ mixin notificationsTab() el-tooltip(v-if="scope.row.type === 'invite'" placement="top") template(#content) location(v-if="scope.row.details" :location="scope.row.details.worldId" :hint="scope.row.details.worldName" :grouphint="scope.row.details.groupName" :link="false") - span.x-link(v-text="scope.row.type" @click="showWorldDialog(scope.row.details.worldId)") + span.x-link(v-text="$t('view.notification.filters.' + scope.row.type)" @click="showWorldDialog(scope.row.details.worldId)") el-tooltip(v-else-if="scope.row.type === 'group.queueReady' || scope.row.type === 'instance.closed'" placement="top") template(#content) location(v-if="scope.row.location" :location="scope.row.location" :hint="scope.row.worldName" :grouphint="scope.row.groupName" :link="false") - span.x-link(v-text="scope.row.type" @click="showWorldDialog(scope.row.location)") + span.x-link(v-text="$t('view.notification.filters.' + scope.row.type)" @click="showWorldDialog(scope.row.location)") el-tooltip(v-else-if="scope.row.link" placement="top" :content="scope.row.linkText" :disabled="hideTooltips") - span.x-link(v-text="scope.row.type" @click="openNotificationLink(scope.row.link)") - span(v-else v-text="scope.row.type") + span.x-link(v-text="$t('view.notification.filters.' + scope.row.type)" @click="openNotificationLink(scope.row.link)") + span(v-else v-text="$t('view.notification.filters.' + scope.row.type)") el-table-column(:label="$t('table.notification.user_group')" prop="senderUsername" width="150") template(v-once #default="scope") template(v-if="scope.row.type === 'groupChange'")