- Click "Register in Entra" to auto-create the app registration in the client tenant.
- You'll sign in with a client admin account — no secrets, no pre-existing app needed.
- Or enter an existing public client App Registration ID manually.
+ @T["profiles.register.hint"]
@if (_deviceCode is not null)
{
-
Sign in to the client tenant to authorize app creation:
diff --git a/Components/Shared/FolderTreeNode.razor b/Components/Shared/FolderTreeNode.razor
index 95f0789..1675db6 100644
--- a/Components/Shared/FolderTreeNode.razor
+++ b/Components/Shared/FolderTreeNode.razor
@@ -1,17 +1,18 @@
@* Recursive editor row for one folder in the visual builder. *@
@using SharepointToolbox.Web.Core.Models
+@inject TranslationSource T
📁
@if (Depth < FolderNode.MaxDepth)
{
-
+
}
-
+
@foreach (var child in Node.Children)
diff --git a/Components/Shared/LibraryPicker.razor b/Components/Shared/LibraryPicker.razor
index 1753233..a9b5abc 100644
--- a/Components/Shared/LibraryPicker.razor
+++ b/Components/Shared/LibraryPicker.razor
@@ -1,4 +1,5 @@
@inject ILibraryDiscoveryService LibraryDiscovery
+@inject TranslationSource T
@* Library name field with a picker: type a title, or click Browse to load and
choose from the libraries on the selected site. *@
@@ -13,7 +14,7 @@
- Authenticate to access @Session.CurrentProfile?.Name.
- Your session token is stored in your browser only — never saved to disk.
+ @T["connect.subtitle.prefix"] @Session.CurrentProfile?.Name.
+ @T["connect.token.note"]
@@ -23,14 +24,14 @@
}
- You will be redirected to Microsoft login. MFA is supported.
+ @T["connect.redirect.note"]
@@ -54,7 +55,7 @@
private async Task ConnectAsync()
{
var profile = Session.CurrentProfile;
- if (profile is null) { _error = "No client profile selected."; return; }
+ if (profile is null) { _error = T["connect.err.noprofile"]; return; }
_connecting = true;
_error = string.Empty;
diff --git a/Components/Shared/SitePicker.razor b/Components/Shared/SitePicker.razor
index cf74739..db12fc2 100644
--- a/Components/Shared/SitePicker.razor
+++ b/Components/Shared/SitePicker.razor
@@ -1,13 +1,14 @@
@inject ISiteDiscoveryService SiteDiscovery
+@inject TranslationSource T
- You have read-only access (Tech-N0). Contact an Admin to request write access.
+ @T["writeguard.readonly.before"] @T["writeguard.readonly.emphasis"] @T["writeguard.readonly.after"]
}
}
diff --git a/Components/_Imports.razor b/Components/_Imports.razor
index b526846..01c2079 100644
--- a/Components/_Imports.razor
+++ b/Components/_Imports.razor
@@ -19,3 +19,4 @@
@using SharepointToolbox.Web.Components
@using SharepointToolbox.Web.Components.Shared
@using SharepointToolbox.Web.Core.Helpers
+@using SharepointToolbox.Web.Localization
diff --git a/Core/Models/AppSettings.cs b/Core/Models/AppSettings.cs
index 1401701..2826c97 100644
--- a/Core/Models/AppSettings.cs
+++ b/Core/Models/AppSettings.cs
@@ -3,8 +3,8 @@ namespace SharepointToolbox.Web.Core.Models;
public class AppSettings
{
public string DataFolder { get; set; } = string.Empty;
- public string Lang { get; set; } = "en";
- public bool AutoTakeOwnership { get; set; } = false;
+ public string Lang { get; set; } = "fr";
+ public bool AutoTakeOwnership { get; set; } = true;
public string Theme { get; set; } = "System";
/// MSP logo shown top-left on exported reports. Null = none.
diff --git a/Localization/Strings.fr.resx b/Localization/Strings.fr.resx
index 0a6f348..44bc1cb 100644
--- a/Localization/Strings.fr.resx
+++ b/Localization/Strings.fr.resx
@@ -867,4 +867,1000 @@ Cet onglet fait l'inverse : vous sélectionnez un ou plusieurs utilisateurs et i
Analyse les bibliothèques SharePoint cachées dans la navigation normale du site (ex. Site Assets, Style Library, Form Templates). Elles peuvent consommer beaucoup d'espace et sont souvent oubliées dans les audits de routine.Bibliothèque de conservationBibliothèque SharePoint cachée qui stocke les versions de documents modifiés ou supprimés pendant qu'une politique de rétention Microsoft Purview / Microsoft 365 Compliance est active. Elle peut croître considérablement sans être visible pour les utilisateurs du site.
+
+ Accès refusé. Rôle administrateur requis.
+
+
+ Action
+
+
+ Client
+
+
+ Détails
+
+
+ Rôle
+
+
+ Filtrer par action...
+
+
+ Filtrer par client...
+
+
+ Filtrer par utilisateur...
+
+
+ Chargement du journal d'audit...
+
+
+ Aucune entrée d'audit trouvée.
+
+
+ Affichage de {0} sur {1} entrées
+
+
+ Toutes les actions des techniciens et administrateurs dans l'application.
+
+
+ Journaux d'audit
+
+
+ Auditer les utilisateurs
+
+
+ Audit en cours…
+
+
+ Charger les utilisateurs
+
+
+ Chargement… ({0})
+
+
+ Tout sélectionner ({0})
+
+
+ Élevé
+
+
+ Inclure les autorisations héritées
+
+
+ Autorisation
+
+
+ Sélectionnez au moins un utilisateur ou saisissez une adresse e-mail.
+
+
+ E-mails supplémentaires (un par ligne)
+
+
+ {0} sélectionné(s)
+
+
+ Utilisateurs
+
+
+ Affichage des 500 premiers. Exportez pour obtenir tous les résultats.
+
+
+ Affichage des 500 premiers. Affinez le filtre pour réduire la liste.
+
+
+ Filtrer par nom ou e-mail…
+
+
+ Résultats de l'audit
+
+
+ Annulé.
+
+
+ {0} entrée(s) d'accès trouvée(s).
+
+
+ Recherchez toutes les autorisations d'un ou plusieurs utilisateurs sur plusieurs sites.
+
+
+ Terminé : {0} ajouté(s), {1} en échec.
+
+
+ Fichier CSV (GroupName, GroupUrl, Email, Role)
+
+
+ Veuillez sélectionner un site.
+
+
+ Exporter les erreurs (CSV)
+
+
+ Traité : {0} / {1}. Échecs : {2}
+
+
+ Traitement en cours…
+
+
+ Statut
+
+
+ Ajoutez des utilisateurs à des groupes SharePoint à partir d'un fichier CSV.
+
+
+ {0} lignes valides, {1} erreurs.
+
+
+ URL du centre d'administration
+
+
+ Statut
+
+
+ Création en cours…
+
+
+ Fichier CSV (Name, Alias, Type, Template, Owners, Members)
+
+
+ Exporter les erreurs CSV
+
+
+ Créez plusieurs sites SharePoint à partir dun fichier CSV.
+
+
+ Création de sites en masse
+
+
+ Annulé.
+
+
+ Terminé : {0} créés, {1} en échec.
+
+
+ Créés : {0} / {1}. Échecs : {2}
+
+
+ {0} valides, {1} erreurs.
+
+
+ Confirmer le nouveau mot de passe
+
+
+ Mot de passe actuel
+
+
+ Votre compte se connecte avec Microsoft (Entra). Gérez son mot de passe dans votre compte Microsoft.
+
+
+ Le mot de passe actuel est incorrect.
+
+
+ Les nouveaux mots de passe ne correspondent pas.
+
+
+ Chargement…
+
+
+ Vous devez être connecté.
+
+
+ Nouveau mot de passe
+
+
+ Changer le mot de passe
+
+
+ Mot de passe modifié.
+
+
+ Changer le mot de passe
+
+
+ Se connecter via Microsoft
+
+
+ Aucun profil client sélectionné.
+
+
+ Vous serez redirigé vers la connexion Microsoft. L'authentification multifacteur est prise en charge.
+
+
+ Redirection…
+
+
+ Authentifiez-vous pour accéder à
+
+
+ Se connecter à Microsoft
+
+
+ Votre jeton de session est stocké uniquement dans votre navigateur — jamais enregistré sur le disque.
+
+
+ Charger les utilisateurs
+
+
+ Chargement… ({0} utilisateurs)
+
+
+ Filtrer par nom ou e-mail…
+
+
+ Affichage des 500 premiers sur {0} filtrés.
+
+
+ {0} utilisateurs chargés.
+
+
+ Parcourir tous les utilisateurs du locataire via Microsoft Graph.
+
+
+ Utilisateurs
+
+
+ Membre
+
+
+ Rechercher les doublons
+
+
+ Analyse en cours…
+
+
+ Correspondance date de création
+
+
+ Correspondance nombre de fichiers
+
+
+ Correspondance nombre de sous-dossiers
+
+
+ Correspondance date de modification
+
+
+ Correspondance taille
+
+
+ Bibliothèque (facultatif)
+
+
+ Mode
+
+
+ Fichiers
+
+
+ Dossiers
+
+
+ Détection des doublons
+
+
+ Groupes de doublons
+
+
+ Affichage des 100 premiers groupes. Exportez pour tout obtenir.
+
+
+ + Ajouter un dossier de premier niveau
+
+
+ Construire visuellement
+
+
+ Création en cours…
+
+
+ Importer un CSV
+
+
+ Aucun dossier pour l'instant. Ajoutez un dossier de premier niveau pour commencer.
+
+
+ Veuillez sélectionner un site.
+
+
+ Fichier CSV (Niveau1, Niveau2, Niveau3, Niveau4)
+
+
+ Titre de la bibliothèque
+
+
+ Source
+
+
+ Créés : {0} dossiers. Échecs : {1}
+
+
+ {0} lignes valides, {1} erreurs.
+
+
+ Terminé : {0} dossiers créés.
+
+
+ Créez des hiérarchies de dossiers dans une bibliothèque de documents à partir d'un modèle CSV.
+
+
+ + Sous-dossier
+
+
+ Ajouter un sous-dossier
+
+
+ Supprimer
+
+
+ Nom du dossier
+
+
+ Connecté : {0}
+
+
+ Ajouter des utilisateurs aux groupes via CSV
+
+
+ Créer des sites à partir d'un CSV
+
+
+ Rechercher les fichiers/dossiers en double
+
+
+ Créer des dossiers à partir d'un modèle CSV
+
+
+ Analyser les attributions d'autorisations du site
+
+
+ Recherche de fichiers basée sur KQL
+
+
+ Analyser l'utilisation du stockage des bibliothèques
+
+
+ Capturer et appliquer des modèles de site
+
+
+ Copier/déplacer des fichiers entre bibliothèques
+
+
+ Transfert de fichiers
+
+
+ Auditer les autorisations des utilisateurs sur plusieurs sites
+
+
+ Parcourir les utilisateurs du locataire via Graph
+
+
+ Supprimer les anciennes versions de fichiers
+
+
+ Nettoyage des versions
+
+
+ Audit des autorisations
+
+
+ Métriques de stockage
+
+
+ Locataire :
+
+
+ Bienvenue
+
+
+ Sélectionnez un profil de locataire pour commencer à utiliser SharePoint Toolbox.
+
+
+ Parcourir
+
+
+ Chargement…
+
+
+ Aucune bibliothèque de documents trouvée sur ce site.
+
+
+ Sélectionnez d'abord un site.
+
+
+ Impossible de lire l'image : {0}
+
+
+ Fichier trop volumineux ({0} Ko). Max {1} Ko.
+
+
+ PNG, JPEG, SVG ou GIF — max {0} Ko.
+
+
+ Supprimer
+
+
+ Plusieurs documents (ZIP)
+
+
+ Un seul document, sans onglets
+
+
+ Un seul document, onglets (HTML)
+
+
+ Comment regrouper les rapports lorsque plusieurs sites sont analysés
+
+
+ Journaux d'audit
+
+
+ Changer le mot de passe
+
+
+ Effacer
+
+
+ Profils clients
+
+
+ Mode sombre
+
+
+ Transfert de fichiers
+
+
+ Accueil
+
+
+ Mode clair
+
+
+ Chargement…
+
+
+ Déconnexion
+
+
+ Aucun résultat
+
+
+ Reconnecter
+
+
+ Rechercher…
+
+
+ Administration
+
+
+ Audit
+
+
+ En masse
+
+
+ Configuration
+
+
+ Afficher/masquer la barre latérale
+
+
+ Annuaire des utilisateurs
+
+
+ Gestion des utilisateurs
+
+
+ Sélectionnez ou créez un profil de locataire pour commencer.
+
+
+ Accéder aux profils
+
+
+ Aucun profil sélectionné
+
+
+ Retour à l'accueil
+
+
+ La page demandée n'existe pas ou a été déplacée.
+
+
+ Page introuvable
+
+
+ Page introuvable — SharePoint Toolbox
+
+
+ Analyser les sites
+
+
+ Analyse en cours…
+
+
+ Autorisation
+
+
+ Utilisateurs
+
+
+ Résultats
+
+
+ Analyse terminée : {0} entrées sur {1} site(s).
+
+
+ Analyse de {0} ({1}/{2})…
+
+
+ Affichage des 500 premières lignes sur {0}. Exportez pour obtenir tous les résultats.
+
+
+ Audit des autorisations
+
+
+ Aucun profil configuré.
+
+
+ Gérer les profils
+
+
+ Sélectionner un profil
+
+
+ Actif
+
+
+ ID client :
+
+
+ pour autoriser la création de l'application :
+
+
+ Connectez-vous au
+
+
+ tenant du client
+
+
+ Approuvez les autorisations demandées avec un compte administrateur.
+
+
+ Saisissez le code :
+
+
+ Ouvrez
+
+
+ Modifier
+
+
+ Aucun profil configuré. Créez-en un pour commencer.
+
+
+ L'ID client est obligatoire.
+
+
+ Le nom est obligatoire.
+
+
+ L'ID de tenant est obligatoire.
+
+
+ L'URL du tenant est obligatoire.
+
+
+ ID client (inscription d'application)
+
+
+ Rempli automatiquement après l'inscription, ou saisissez-le manuellement
+
+
+ Modifier le profil
+
+
+ Logo du client (facultatif)
+
+
+ Affiché en haut à droite des rapports exportés pour ce client.
+
+
+ Nom du profil *
+
+
+ ex. Contoso Production
+
+
+ Nouveau profil
+
+
+ ID de tenant (GUID ou domaine) *
+
+
+ contoso.onmicrosoft.com ou GUID
+
+
+ URL du tenant *
+
+
+ + Nouveau profil
+
+
+ Inscription annulée.
+
+
+ Création de l'inscription d'application…
+
+
+ Échec de l'inscription : {0}
+
+
+ Application inscrite. Vérifiez et enregistrez le profil.
+
+
+ Demande d'un code de connexion…
+
+
+ En attente de la fin de la connexion…
+
+
+ Inscrire dans Entra
+
+
+ Cliquez sur « Inscrire dans Entra » pour créer automatiquement l'inscription d'application dans le tenant du client. Vous vous connecterez avec un compte administrateur du client — aucun secret, aucune application préexistante requise. Ou saisissez manuellement l'ID d'une inscription d'application de client public existante.
+
+
+ Renseignez d'abord l'URL du tenant, l'ID de tenant et le nom du profil
+
+
+ Inscrire l'application dans l'Entra ID du client (nécessite un administrateur pouvant créer des inscriptions d'application)
+
+
+ En attente…
+
+
+ La gestion des profils est réservée aux administrateurs. Sélectionnez un profil ci-dessous pour travailler sur un client.
+
+
+ Sélectionner
+
+
+ Sélectionné
+
+
+ Gérez les connexions aux tenants SharePoint. Les identifiants sont saisis par session — aucun secret n'est stocké sur le disque.
+
+
+ ID de tenant :
+
+
+ Profils clients
+
+
+ Élever automatiquement les droits de propriété lorsque l'analyse des autorisations est refusée
+
+
+ Affiché en haut à gauche des rapports HTML exportés. Le logo du client (en haut à droite) est défini par profil.
+
+
+ Paramètres enregistrés.
+
+
+ Comportement
+
+
+ Image de marque des rapports
+
+
+ Affichage
+
+
+ Effacer
+
+
+ Recharger les sites
+
+
+ Tout sélectionner ({0})
+
+
+ Aucun site ne correspond au filtre.
+
+
+ Aucun site renvoyé. Le compte ne dispose peut-être pas de Sites.Read.All.
+
+
+ Cliquez sur « Charger les sites » pour lister les sites SharePoint du locataire, puis cochez ceux à analyser.
+
+
+ Cliquez sur « Charger les sites » pour lister les sites SharePoint du locataire, puis sélectionnez-en un.
+
+
+ Site
+
+
+ Sites
+
+
+ Filtrer les sites chargés par nom ou URL…
+
+
+ Chargement…
+
+
+ {0} sélectionné(s)
+
+
+ Taille (Ko)
+
+
+ Veuillez sélectionner au moins un site.
+
+
+ Extensions de fichier (séparées par des virgules)
+
+
+ Bibliothèque (facultatif)
+
+
+ Options de recherche
+
+
+ Résultats
+
+
+ {0} fichier(s) trouvé(s) sur {1} site(s).
+
+
+ Recherche dans {0} ({1}/{2})…
+
+
+ Affichage des 500 premiers sur {0}. Exportez pour obtenir tous les résultats.
+
+
+ {0} bibliothèques
+
+
+ Analyser le stockage
+
+
+ Analyse en cours…
+
+
+ Inclure les bibliothèques masquées
+
+
+ Inclure la corbeille
+
+
+ Total (Mo)
+
+
+ Versions (Mo)
+
+
+ Veuillez sélectionner au moins un site.
+
+
+ 0 = bibliothèques uniquement. 1+ = explorer les sous-dossiers sur ce nombre de niveaux.
+
+
+ Profondeur d'analyse des dossiers
+
+
+ Mesures de stockage
+
+
+ Rapport de stockage
+
+
+ Annulé.
+
+
+ Terminé : {0} nœuds sur {1} site(s).
+
+
+ Analyse de {0} ({1}/{2})…
+
+
+ URL du centre d'administration
+
+
+ Modèle :
+
+
+ Sélectionnez un modèle dans la liste ci-dessous.
+
+
+ Application en cours…
+
+
+ Capturer
+
+
+ Capture en cours…
+
+
+ Utiliser
+
+
+ {0} bibliothèques
+
+
+ Mon modèle
+
+
+ Capturez la structure d'un site et appliquez-la à de nouveaux sites.
+
+
+ Modèles de site
+
+
+ Annulé.
+
+
+ Modèle « {0} » enregistré.
+
+
+ Site créé : {0}
+
+
+ Inclure le dossier source
+
+
+ Renommer
+
+
+ Dossier de destination (facultatif)
+
+
+ Destination
+
+
+ Veuillez sélectionner un site de destination.
+
+
+ Veuillez sélectionner un site source.
+
+
+ Transfert de fichiers
+
+
+ Échecs : {0}
+
+
+ Transférés : {0} / {1} fichiers.
+
+
+ Source
+
+
+ Dossier source (facultatif)
+
+
+ Sous-dossier/Chemin
+
+
+ Annulé.
+
+
+ Terminé : {0} transférés.
+
+
+ Transfert en cours…
+
+
+ Accès refusé. Le rôle Administrateur est requis.
+
+
+ Vous
+
+
+ Créer l'utilisateur
+
+
+ Supprimer
+
+
+ Réinitialiser le mot de passe
+
+
+ Définir le mot de passe
+
+
+ Actions
+
+
+ E-mail
+
+
+ Dernière connexion
+
+
+ Rôle
+
+
+ Source
+
+
+ Utilisateur
+
+
+ Créer un utilisateur local
+
+
+ Aucun utilisateur approvisionné pour le moment.
+
+
+ Jamais
+
+
+ Nom d'affichage
+
+
+ Nouveau mot de passe
+
+
+ Mot de passe
+
+
+ Utilisateur local {0} créé.
+
+
+ Erreur : {0}
+
+
+ Mot de passe réinitialisé pour {0}.
+
+
+ Utilisateur {0} supprimé.
+
+
+ Rôle mis à jour pour {0}.
+
+
+ Réinitialiser le mot de passe — {0}
+
+
+ Entra
+
+
+ Local
+
+
+ Gérez les comptes et rôles des techniciens. Les utilisateurs Entra sont approvisionnés automatiquement lors de la première connexion OIDC ; les utilisateurs locaux sont créés ici.
+
+
+ Gestion des utilisateurs
+
+
+ Nettoyage en cours…
+
+
+ Charger les bibliothèques
+
+
+ Chargement…
+
+
+ Conserver la première version
+
+
+ Conserver les N dernières versions
+
+
+ Bibliothèques (aucune = toutes)
+
+
+ Nettoyage des versions
+
+
+ (Tech-N0). Contactez un administrateur pour demander un accès en écriture.
+
+
+ Vous disposez d'un accès en
+
+
+ lecture seule
+
diff --git a/Localization/Strings.resx b/Localization/Strings.resx
index 403a60e..7938ffc 100644
--- a/Localization/Strings.resx
+++ b/Localization/Strings.resx
@@ -867,4 +867,1000 @@ This tab does the reverse: you select one or more users and it finds every objec
Scans SharePoint libraries hidden from the site's normal navigation (e.g. Site Assets, Style Library, Form Templates). These can consume significant storage and are often overlooked in routine audits.Preservation Hold LibraryA hidden SharePoint library that stores versions of documents modified or deleted while a Microsoft Purview / Microsoft 365 Compliance retention policy is active. It can grow very large over time without being visible to normal site users.
+
+ Access denied. Admin role required.
+
+
+ Action
+
+
+ Client
+
+
+ Details
+
+
+ Role
+
+
+ Filter by action...
+
+
+ Filter by client...
+
+
+ Filter by user...
+
+
+ Loading audit log...
+
+
+ No audit entries found.
+
+
+ Showing {0} of {1} entries
+
+
+ All technician and admin actions within the application.
+
+
+ Audit Logs
+
+
+ Audit Users
+
+
+ Auditing…
+
+
+ Load Users
+
+
+ Loading… ({0})
+
+
+ Select all ({0})
+
+
+ High
+
+
+ Include inherited
+
+
+ Permission
+
+
+ Select at least one user or enter an email.
+
+
+ Additional emails (one per line)
+
+
+ {0} selected
+
+
+ Users
+
+
+ Showing first 500. Export for full results.
+
+
+ Showing first 500. Refine filter to narrow.
+
+
+ Filter by name or email…
+
+
+ Audit Results
+
+
+ Cancelled.
+
+
+ Found {0} access entries.
+
+
+ Find all permissions for one or more users across multiple sites.
+
+
+ Complete: {0} added, {1} failed.
+
+
+ CSV File (GroupName, GroupUrl, Email, Role)
+
+
+ Please select a site.
+
+
+ Export Errors CSV
+
+
+ Processed: {0} / {1}. Failures: {2}
+
+
+ Processing…
+
+
+ Status
+
+
+ Add users to SharePoint groups from a CSV file.
+
+
+ {0} valid rows, {1} errors.
+
+
+ Admin Center URL
+
+
+ Status
+
+
+ Creating…
+
+
+ CSV File (Name, Alias, Type, Template, Owners, Members)
+
+
+ Export Errors CSV
+
+
+ Create multiple SharePoint sites from a CSV file.
+
+
+ Bulk Site Creation
+
+
+ Cancelled.
+
+
+ Complete: {0} created, {1} failed.
+
+
+ Created: {0} / {1}. Failures: {2}
+
+
+ {0} valid, {1} errors.
+
+
+ Confirm new password
+
+
+ Current password
+
+
+ Your account signs in with Microsoft (Entra). Manage its password in your Microsoft account.
+
+
+ Current password is incorrect.
+
+
+ New passwords do not match.
+
+
+ Loading…
+
+
+ You must be signed in.
+
+
+ New password
+
+
+ Change password
+
+
+ Password changed.
+
+
+ Change Password
+
+
+ Connect via Microsoft
+
+
+ No client profile selected.
+
+
+ You will be redirected to Microsoft login. MFA is supported.
+
+
+ Redirecting…
+
+
+ Authenticate to access
+
+
+ Connect to Microsoft
+
+
+ Your session token is stored in your browser only — never saved to disk.
+
+
+ Load Users
+
+
+ Loading… ({0} users)
+
+
+ Filter by name or email…
+
+
+ Showing first 500 of {0} filtered.
+
+
+ Loaded {0} users.
+
+
+ Browse all tenant users via Microsoft Graph.
+
+
+ Users
+
+
+ Member
+
+
+ Find Duplicates
+
+
+ Scanning…
+
+
+ Match created
+
+
+ Match file count
+
+
+ Match subfolder count
+
+
+ Match modified
+
+
+ Match size
+
+
+ Library (optional)
+
+
+ Mode
+
+
+ Files
+
+
+ Folders
+
+
+ Duplicate Detection
+
+
+ Duplicate Groups
+
+
+ Showing first 100 groups. Export for all.
+
+
+ + Add top-level folder
+
+
+ Build visually
+
+
+ Creating…
+
+
+ Upload CSV
+
+
+ No folders yet. Add a top-level folder to start.
+
+
+ Please select a site.
+
+
+ CSV File (Level1, Level2, Level3, Level4)
+
+
+ Library Title
+
+
+ Source
+
+
+ Created: {0} folders. Failures: {1}
+
+
+ {0} valid rows, {1} errors.
+
+
+ Complete: {0} folders created.
+
+
+ Create folder hierarchies in a document library from a CSV template.
+
+
+ + Sub
+
+
+ Add subfolder
+
+
+ Remove
+
+
+ Folder name
+
+
+ Connected: {0}
+
+
+ Add users to groups via CSV
+
+
+ Create sites from CSV
+
+
+ Find duplicate files/folders
+
+
+ Create folders from CSV template
+
+
+ Scan site permission assignments
+
+
+ KQL-based file search
+
+
+ Analyze library storage usage
+
+
+ Capture and apply site templates
+
+
+ Copy/move files between libraries
+
+
+ File Transfer
+
+
+ Audit user permissions cross-site
+
+
+ Browse tenant users via Graph
+
+
+ Delete old file versions
+
+
+ Version Cleanup
+
+
+ Permissions Audit
+
+
+ Storage Metrics
+
+
+ Tenant:
+
+
+ Welcome
+
+
+ Select a tenant profile to start using SharePoint Toolbox.
+
+
+ Browse
+
+
+ Loading…
+
+
+ No document libraries found on this site.
+
+
+ Select a site first.
+
+
+ Could not read image: {0}
+
+
+ File too large ({0} KB). Max {1} KB.
+
+
+ PNG, JPEG, SVG or GIF — max {0} KB.
+
+
+ Remove
+
+
+ Multiple documents (ZIP)
+
+
+ One document, no tabs
+
+
+ One document, tabs (HTML)
+
+
+ How to bundle reports when multiple sites are scanned
+
+
+ Audit Logs
+
+
+ Change Password
+
+
+ Clear
+
+
+ Client Profiles
+
+
+ Dark Mode
+
+
+ File Transfer
+
+
+ Home
+
+
+ Light Mode
+
+
+ Loading…
+
+
+ Logout
+
+
+ No match
+
+
+ Reconnect
+
+
+ Search…
+
+
+ Admin
+
+
+ Audit
+
+
+ Bulk
+
+
+ Config
+
+
+ Toggle sidebar
+
+
+ User Directory
+
+
+ User Management
+
+
+ Select or create a tenant profile to get started.
+
+
+ Go to Profiles
+
+
+ No profile selected
+
+
+ Back to Home
+
+
+ The page you requested doesn't exist or has moved.
+
+
+ Page not found
+
+
+ Page not found — SharePoint Toolbox
+
+
+ Scan Sites
+
+
+ Scanning…
+
+
+ Permission
+
+
+ Users
+
+
+ Results
+
+
+ Scan complete: {0} entries across {1} site(s).
+
+
+ Scanning {0} ({1}/{2})…
+
+
+ Showing first 500 of {0} rows. Export for full results.
+
+
+ Permissions Audit
+
+
+ No profiles configured.
+
+
+ Manage profiles
+
+
+ Select a profile
+
+
+ Active
+
+
+ Client ID:
+
+
+ to authorize app creation:
+
+
+ Sign in to the
+
+
+ client tenant
+
+
+ Approve the requested permissions with an admin account.
+
+
+ Enter code:
+
+
+ Open
+
+
+ Edit
+
+
+ No profiles configured. Create one to get started.
+
+
+ Client ID is required.
+
+
+ Name is required.
+
+
+ Tenant ID is required.
+
+
+ Tenant URL is required.
+
+
+ Client ID (App Registration)
+
+
+ Auto-filled after registration, or enter manually
+
+
+ Edit Profile
+
+
+ Client logo (optional)
+
+
+ Shown top-right on exported reports for this client.
+
+
+ Profile Name *
+
+
+ e.g. Contoso Production
+
+
+ New Profile
+
+
+ Tenant ID (GUID or domain) *
+
+
+ contoso.onmicrosoft.com or GUID
+
+
+ Tenant URL *
+
+
+ + New Profile
+
+
+ Registration cancelled.
+
+
+ Creating the app registration…
+
+
+ Registration failed: {0}
+
+
+ App registered. Review and Save the profile.
+
+
+ Requesting a sign-in code…
+
+
+ Waiting for sign-in to complete…
+
+
+ Register in Entra
+
+
+ Click "Register in Entra" to auto-create the app registration in the client tenant. You'll sign in with a client admin account — no secrets, no pre-existing app needed. Or enter an existing public client App Registration ID manually.
+
+
+ Fill Tenant URL, Tenant ID and Profile Name first
+
+
+ Register app in client Entra ID (requires an admin who can create app registrations)
+
+
+ Waiting…
+
+
+ Profile management is restricted to Admins. Select a profile below to work on a client.
+
+
+ Select
+
+
+ Selected
+
+
+ Manage SharePoint tenant connections. Credentials are entered per session — no secrets stored on disk.
+
+
+ Tenant ID:
+
+
+ Client Profiles
+
+
+ Auto-elevate ownership when permission scan is denied
+
+
+ Shown top-left on exported HTML reports. The client's logo (top-right) is set per profile.
+
+
+ Settings saved.
+
+
+ Behavior
+
+
+ Report Branding
+
+
+ Display
+
+
+ Clear
+
+
+ Reload sites
+
+
+ Select all ({0})
+
+
+ No sites match the filter.
+
+
+ No sites returned. The account may lack Sites.Read.All.
+
+
+ Click “Load sites” to list the tenant’s SharePoint sites, then tick the ones to scan.
+
+
+ Click “Load sites” to list the tenant’s SharePoint sites, then pick one.
+
+
+ Site
+
+
+ Sites
+
+
+ Filter loaded sites by name or URL…
+
+
+ Loading…
+
+
+ {0} selected
+
+
+ Size (KB)
+
+
+ Please select at least one site.
+
+
+ File Extensions (comma-separated)
+
+
+ Library (optional)
+
+
+ Search Options
+
+
+ Results
+
+
+ Found {0} files across {1} site(s).
+
+
+ Searching {0} ({1}/{2})…
+
+
+ Showing first 500 of {0}. Export for full results.
+
+
+ {0} libraries
+
+
+ Scan Storage
+
+
+ Scanning…
+
+
+ Include hidden libs
+
+
+ Include recycle bin
+
+
+ Total (MB)
+
+
+ Versions (MB)
+
+
+ Please select at least one site.
+
+
+ 0 = libraries only. 1+ = drill into subfolders that many levels deep.
+
+
+ Folder scan depth
+
+
+ Storage Metrics
+
+
+ Storage Report
+
+
+ Cancelled.
+
+
+ Complete: {0} nodes across {1} site(s).
+
+
+ Scanning {0} ({1}/{2})…
+
+
+ Admin Center URL
+
+
+ Template:
+
+
+ Select a template from the list below.
+
+
+ Applying…
+
+
+ Capture
+
+
+ Capturing…
+
+
+ Use
+
+
+ {0} libraries
+
+
+ My Template
+
+
+ Capture site structure and apply to new sites.
+
+
+ Site Templates
+
+
+ Cancelled.
+
+
+ Template '{0}' saved.
+
+
+ Site created: {0}
+
+
+ Include source folder
+
+
+ Rename
+
+
+ Destination Folder (optional)
+
+
+ Destination
+
+
+ Please select a destination site.
+
+
+ Please select a source site.
+
+
+ File Transfer
+
+
+ Failures: {0}
+
+
+ Transferred: {0} / {1} files.
+
+
+ Source
+
+
+ Source Folder (optional)
+
+
+ SubFolder/Path
+
+
+ Cancelled.
+
+
+ Complete: {0} transferred.
+
+
+ Transferring…
+
+
+ Access denied. Admin role required.
+
+
+ You
+
+
+ Create user
+
+
+ Remove
+
+
+ Reset password
+
+
+ Set password
+
+
+ Actions
+
+
+ Email
+
+
+ Last Login
+
+
+ Role
+
+
+ Source
+
+
+ User
+
+
+ Create local user
+
+
+ No users provisioned yet.
+
+
+ Never
+
+
+ Display name
+
+
+ New password
+
+
+ Password
+
+
+ Local user {0} created.
+
+
+ Error: {0}
+
+
+ Password reset for {0}.
+
+
+ User {0} removed.
+
+
+ Role updated for {0}.
+
+
+ Reset password — {0}
+
+
+ Entra
+
+
+ Local
+
+
+ Manage technician accounts and roles. Entra users are auto-provisioned on first OIDC login; local users are created here.
+
+
+ User Management
+
+
+ Cleaning…
+
+
+ Load Libraries
+
+
+ Loading…
+
+
+ Keep first version
+
+
+ Keep last N versions
+
+
+ Libraries (none = all)
+
+
+ Version Cleanup
+
+
+ access (Tech-N0). Contact an Admin to request write access.
+
+
+ You have
+
+
+ read-only
+
diff --git a/Localization/TranslationSource.cs b/Localization/TranslationSource.cs
index a149bd3..6740bb0 100644
--- a/Localization/TranslationSource.cs
+++ b/Localization/TranslationSource.cs
@@ -4,8 +4,17 @@ using System.Resources;
namespace SharepointToolbox.Web.Localization;
///
-/// Singleton string lookup backed by Strings.resx / Strings.fr.resx.
-/// Web version: no INotifyPropertyChanged — culture switching is per-request.
+/// String lookup backed by Strings.resx / Strings.fr.resx.
+///
+/// Registered as Scoped: in Blazor Server each circuit gets its own instance with its own
+/// explicit . The culture is stored as a field (not read from the
+/// ambient ) because the interactive circuit does
+/// NOT inherit the request/middleware culture, and ambient culture does not reliably flow
+/// across render batches and SPA navigations. An explicit per-circuit field is deterministic:
+/// set it once at circuit start and every page in that circuit renders in the same language.
+///
+/// The static (used by the export services) has no explicit culture and
+/// falls back to the ambient culture.
///
public class TranslationSource
{
@@ -16,31 +25,30 @@ public class TranslationSource
// name ("SharepointToolbox.Strings") from before the project was renamed to
// *.Web, so its lookups throw MissingManifestResourceException. The embedded
// resource is "SharepointToolbox.Web.Localization.Strings".
- private ResourceManager _resourceManager =
+ private readonly ResourceManager _resourceManager =
new ResourceManager("SharepointToolbox.Web.Localization.Strings", typeof(TranslationSource).Assembly);
- private CultureInfo _currentCulture = CultureInfo.CurrentUICulture;
- private TranslationSource() { }
+ private CultureInfo? _culture;
+
+ public TranslationSource() { }
+
+ /// Explicit lookup culture. When unset, falls back to the ambient UI culture.
+ public CultureInfo Culture
+ {
+ get => _culture ?? CultureInfo.CurrentUICulture;
+ set => _culture = value;
+ }
public string this[string key] =>
- _resourceManager.GetString(key, _currentCulture) ?? $"[{key}]";
+ _resourceManager.GetString(key, Culture) ?? $"[{key}]";
- public CultureInfo CurrentCulture
- {
- get => _currentCulture;
- set
- {
- if (Equals(_currentCulture, value)) return;
- _currentCulture = value;
- }
- }
+ /// Sets this instance's culture from a language code ("fr" → French, else English/invariant).
+ public void SetCulture(string lang) => Culture = Resolve(lang);
- public void SetCulture(string lang)
+ /// "fr" → French; anything else → invariant (the base Strings.resx, i.e. English).
+ public static CultureInfo Resolve(string lang) => lang switch
{
- CurrentCulture = lang switch
- {
- "fr" => new CultureInfo("fr"),
- _ => CultureInfo.InvariantCulture
- };
- }
+ "fr" => new CultureInfo("fr"),
+ _ => CultureInfo.InvariantCulture
+ };
}
diff --git a/Program.cs b/Program.cs
index 4028402..7d43f01 100644
--- a/Program.cs
+++ b/Program.cs
@@ -41,6 +41,9 @@ builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddHttpContextAccessor();
+// Localization string source — Scoped: one per circuit, with its own explicit culture.
+builder.Services.AddScoped();
+
// ── Authentication ────────────────────────────────────────────────────────────
if (builder.Environment.IsDevelopment())
{
diff --git a/Services/Session/UserSessionService.cs b/Services/Session/UserSessionService.cs
index d7ec810..0fb0dd2 100644
--- a/Services/Session/UserSessionService.cs
+++ b/Services/Session/UserSessionService.cs
@@ -23,7 +23,15 @@ public class UserSessionService : IUserSessionService
{
_sessionManager = sessionManager;
_settingsRepo = settingsRepo;
- _ = LoadSettingsAsync();
+ // Load synchronously so Settings (esp. Lang) are available the moment the circuit
+ // starts — culture is applied in MainLayout.OnInitialized before any page renders,
+ // so a fire-and-forget load here would race and lose.
+ //
+ // Run on the thread pool (Task.Run) so LoadAsync's await continuation does NOT post
+ // back to the circuit's SynchronizationContext. Blocking that context here with a plain
+ // GetResult() deadlocks: the continuation can never resume on the thread we're blocking.
+ try { _settings = Task.Run(() => _settingsRepo.LoadAsync()).GetAwaiter().GetResult(); }
+ catch { /* use defaults */ }
}
public void SetProfile(TenantProfile profile)
@@ -45,10 +53,4 @@ public class UserSessionService : IUserSessionService
_settings = settings;
_ = _settingsRepo.SaveAsync(settings);
}
-
- private async Task LoadSettingsAsync()
- {
- try { _settings = await _settingsRepo.LoadAsync(); }
- catch { /* use defaults */ }
- }
}