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
This commit is contained in:
Hactazia
2025-01-09 00:44:15 +01:00
committed by GitHub
parent fd4d980d89
commit 3203177d15
9 changed files with 606 additions and 188 deletions

View File

@@ -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 = '<u><font color="yellow">',
markerEndTag = '</font></u>'
$app.methods.formatDifference = function getWordDifferences(
oldString,
newString,
markerAddition = '<span class="x-text-added">{{text}}</span>',
markerDeletion = '<span class="x-text-removed">{{text}}</span>'
) {
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, '&amp;')
.replaceAll(/</g, '&lt;')
.replaceAll(/>/g, '&gt;')
.replaceAll(/"/g, '&quot;')
.replaceAll(/'/g, '&#039;')
.replaceAll(`{{diffTag-${key}}}`, markerStartTag)
.replaceAll(`{{diffTagClose-${key}}}`, markerEndTag);
return returnVal;
});
};
[oldString, newString] = [oldString, newString].map((s) => s
.replaceAll(/&/g, '&amp;')
.replaceAll(/</g, '&lt;')
.replaceAll(/>/g, '&gt;')
.replaceAll(/"/g, '&quot;')
.replaceAll(/'/g, '&#039;')
.replaceAll(/\n/g, '<br>')
);
const oldWords = oldString.split(/\s+/).flatMap((word) => word.split(/(<br>)/));
const newWords = newString.split(/\s+/).flatMap((word) => word.split(/(<br>)/));
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("<br>");
for (let i = 0; i < ts.length; i++) {
if (i > 0) r.push("<br>");
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(/<br>[ ]+<br>/g, "<br><br>")
.replace(/<br> /g, "<br>");
}
// #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':

View File

@@ -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;
}

View File

@@ -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 @@
}
}
}
}
}

View File

@@ -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"
}
}
}
}
}

View File

@@ -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")

View File

@@ -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]&nbsp;

View File

@@ -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)")

View File

@@ -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)")

View File

@@ -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'")